Практика и краткое изложение решения qiankun micro front-end

Vue.js

Что такое микрофронтенд?

Давайте сначала рассмотрим два практических сценария:

1. Повторно используйте другие страницы проекта

Обычно наши фоновые проекты выглядят так:

Если нашему проекту необходимо разработать новую функцию, а эта функция была разработана в другом проекте, мы хотим использовать ее напрямую. PS: Все, что нам нужно, это эта функция страницы чужих проектовРаздел контента, который не требует верхней навигации и меню для чужих проектов.

Относительно глупый способ - напрямую копировать код этой страницы чужих проектов, но на случай, если другие неvueразвитый, илиvueВерсия,UIБиблиотеки бывают разные, как и чужие операции перед загрузкой страницы (перехват роутинга, аутентификация и т.д.) нам нужно ее копировать.Более важный вопрос, как мы синхронизируем обновления при обновлении чужого кода.

В долгосрочной перспективе копирование кода невозможно, корень проблемы в том, что нам нужно заставить их код работать в их собственной среде, а мы всего лишь «ссылки» на их страницы. Эта среда включает в себя различные плагины (vue,vuex,vue-routerд.), но и логику предварительной загрузки (читайcookie, аутентификация, перехват маршрутизации и т.д.). частныйnpmКомпоненты могут быть общими, но все еще есть проблемы, такие как разные технологические стеки / разные библиотеки пользовательского интерфейса.

2. Бесплатное разделение и объединение проектов Big Mac

  • Кодов становится все больше и больше, упаковка становится все медленнее и медленнее, развертывание и обновление вызывают затруднения, а обновление некоторых плагинов и модификацию общих компонентов нужно учитывать больше, и это легко повлиять на все. тело.
  • Чем больше проект, тем больше людей задействовано, тем сложнее управлять спецификациями кода и тем чаще возникают конфликты кода.
  • Продукт полностью функционален, но клиентам часто нужна только его часть. После зачистки лишнего кода приходится самостоятельно формулировать версию и самостоятельно ее поддерживать, что увеличивает трудозатраты.

Например, ваш продукт имеет сотни страниц с полными функциями и мощными функциями.Покупателям нужны только некоторые страницы, и им нужно, чтобы вы предоставили исходный код.В настоящее время точно невозможно дать все коды, и вы можно только выбрать их.Потребителям нужно, эту часть кода нужно разрабатывать отдельно для поддержки версий, что очень расточительно.

Общие решения для микроинтерфейса

Рождение микрофронтенда также должно решить две вышеупомянутые проблемы:

  1. Повторное использование (встраивание) страниц проектов других людей, но проекты других людей выполняются поверх его собственной среды.
  2. Приложение Big Mac разделено на небольшие проекты, эти небольшие проекты разрабатываются и развертываются независимо друг от друга и могут свободно объединяться для продажи.

Преимущества использования микрофронтендов:

  1. Независимо от стека технологий каждый подпроект может свободно выбирать фреймворк и разрабатывать собственные спецификации разработки.
  2. Быстрая упаковка, независимое развертывание, независимо друг от друга, простота обновления.
  3. Существующие функциональные модули можно легко использовать повторно, чтобы избежать повторной разработки.

На данный момент существует два основных решения для микрофронтендов:iframeпрограмма иsingle-spaплан

iframeплан

iframeС ним знаком каждый, он прост и удобен в использовании, обеспечивает естественнуюjs/cssИзоляция также создает неудобства при передаче данных.Некоторые данные не могут использоваться совместно (в основном локальное хранилище, глобальные переменные и общедоступные плагины).Когда два проекта имеют разные источники (междоменные), передача данных должна опираться наpostMessage.

iframeЕсть много ям, но у большинства из них есть решения:

  1. проблема с загрузкой страницы

iframeПул соединений общий с главной страницей, а в браузере есть ограничения на подключение одного и того же домена, поэтому это повлияет на параллельную загрузку страницы, блокируяonloadсобытие. Каждый щелчок требует перезагрузки, хотя можно использоватьdisplay:noneСделать кеш, но слишком большой кеш страниц приведет к зависанию компьютера.(не может быть решен)

  1. проблема макета

iframeДолжна быть указана заданная высота, иначе он рухнет.

Решение: Подпроект вычисляет высоту в режиме реального времени и передаетpostMessageОтправить на главную страницу, главная страница устанавливается динамическиiframeвысокий. В некоторых случаях появляется несколько полос прокрутки, что не очень удобно для пользователя.

  1. Проблема со всплывающим окном и маскирующим слоем

Всплывающие окна могут быть толькоiframeДиапазон центрируется по вертикали и горизонтали, но не может быть центрирован по вертикали и горизонтали на всей странице.

  • Решение 1: При синхронизации с сообщением страницы фрейма сообщение всплывающего окна отправляется на главную страницу, а на главной странице всплывает окно, что сильно меняет исходный проект и влияет на использование исходного проекта.
  • Решение 2. Измените стиль всплывающего окна: скройте слой маски и измените положение всплывающего окна.
  1. iframeвнутриdivНе могу перейти в полноэкранный режим

Полный экран всплывающего окна относится к полному экрану в видимой области браузера. Этот полный экран относится к занятию экрана пользователя.

Полноэкранная схема, нативный метод используетElement.requestFullscreen(), плагин:vue-fullscreen. когда страницаiframeКогда внутри, на весь экран сообщит об ошибке, иdomНеорганизованная структура.

решение:iframeНастройки ярлыкаallow="fullscreen"характеристики

  1. Проблема с браузером вперед/назад

iframeДелитесь историей посещений с главной страницей,iframeЭто повлияет на перемотку вперед и назад страницы. нормальный в большинстве случаев,iframeМножественные перенаправления приведут к тому, что функции браузера вперед и назад не будут работать должным образом. иiframeОбновление страницы будет сброшено (например, перейти со страницы списка на страницу сведений, а затем обновить, он вернется на страницу списка), потому что адресная строка браузера не изменилась,iframeизsrcИзменений тоже нет.

  1. iframeСбой нагрузки непрост в обращении

негомологичныйiframeв фаерфоксе иchormeне поддерживаютonerrorсобытие.

  • Решение 1:onloadО названии страницы судят в том случае, если оно404или500
  • Решение 2. Используйтеtry catchЧтобы исправить это, попробуйте получитьcontentDocumentвызовет исключение.

Ссылка на решение:Вопрос о stackoverflow: поймать ошибку, если iframe src не загружается

single-spaМикро интерфейсное решение

spaВ эпоху одностраничных приложений наши страницы имеют толькоindex.htmlВот этотhtmlфайл, и в этом файле есть только один тег содержимого<div id="app"></div>, который выступает в качестве контейнера для другого контента,jsСгенерировано. Другими словами, нам нужно только получить контейнер подпроекта<div id="app"></div>и сгенерированный контентjs, вставленный в основной проект, может быть представлен контент подпроекта.

<link href=/css/app.c8c4d97c.css rel=stylesheet>
<div id=app></div>
<script src=/js/chunk-vendors.164d8230.js> </script>
<script src=/js/app.6a6f1dda.js> </script> 

Нам нужно только получить четыре верхних тега подпроекта и вставить их в основной проект.HTML, вы можете отображать дочерние проекты в родительском проекте.

Здесь есть проблема, так как теги контента подпроектов генерируются динамически, гдеimg/video/audioИ т. д. файлы ресурсов и загрузка страниц маршрутизации по запросуjs/cssявляются относительными путями в подпроектеindex.htmlвнутри можно запросить корректно, в то время как в основном проектеindex.htmlВнутри нельзя.

В качестве примера предположим, что URL-адрес нашего основного проектаwww.baidu.com, URL-адрес подпроектаwww.taobao.com, в подпроектеindex.htmlВ нем есть картинка<img src="./logo.jpg">, то полный адрес этого образаwww.taobao.com/logo.jpg, теперь преобразуйте это изображение вimgТеги генерируются для родительского проектаindex.html, то адрес запроса изображенияwww.baidu.com/logo.jpg, очевидно, на сервере родительского проекта такой картинки нет.

Решения:

  1. здесьjs/css/img/videoи т. д. - все относительные пути, можете ли вы пройтиwebpackУпаковать, упаковать все эти пути в абсолютные пути? Это решает проблему сбоев файловых запросов.
  2. Можно ли это сделать вручную (или с помощьюnode), чтобы скопировать все файлы подпроекта на главный сервер проекта,nodeКогда файл подпроекта мониторинга будет обновлен, он будет скопирован автоматически, и нажмитеjs/css/imgслияние папок
  3. Может ли это быть похожеCDNТочно так же, если сервер зависнет, он перейдет к другим серверам для запроса соответствующих файлов. Другими словами, для обмена файлами между серверами, если запрос файла в основном проекте не удался, он будет автоматически найден на подсервере и возвращен.

Обычной практикой является динамическое изменениеwebpackв упаковкеpublicPath, а затем вы можете автоматически внедрять префиксы в эти ресурсы.

single-spaЭто микроинтерфейсный фреймворк.Основной принцип такой же, как и выше.На основе вышеуказанных подпроектов новыйbootstrap,mount,unmountи другой жизненный цикл.

относительноiframe,single-spaСделать родительский и дочерний проекты одним и тем жеdocument, что имеет как преимущества, так и недостатки. Преимущество в том, что данные/файлы могут быть общими, общедоступные плагины являются общими, а подпроекты загружаются быстрее.js/cssЗагрязнение.

single-spaНачать работу непросто, и его нельзя использовать сразу, а разработка и развертывание требуют множества модификаций.webpackКонфигурация, трансформация подпроектов тоже очень много.

qiankunплан

qiankunЭто фреймворк Ant Financial с открытым исходным кодом, основанный наsingle-spaиз. онsingle-spaНа основе реализовано готовое использование.За исключением некоторых необходимых модификаций, в подпроекты нужно внести лишь несколько изменений, чтобы их можно было легко подключить. если мы предположимsingle-spaЕсли это велосипед,qiankunПросто машина.

Существует два распространенных способа входа в файл входа подпроекта в микро-интерфейсе:JS entryиHTML entry

чистыйsingle-spaиспользуетсяJS entryqiankunоба поддерживаютJS entryи поддерживаетHTML entry.

JS entryТребования более жесткие:

(1) будетcssупакован вjsв

(2) удалитьchunk-vendors.js,

(3) Удалить имя файлаhashценность

(4) будетsingle-spaВходной файл для схемы (app.js) помещается вindex.htmlДиректория, остальные файлы остаются без изменений, причина в перехватеapp.jsпуть какpublicPath

APP entry преимущество недостаток
JS entry может сотрудничатьsystemJs, который загружает общедоступные зависимости по запросу (vue , vuex , vue-routerЖдать) Требуются различные конфигурации упаковки, и предварительная загрузка не может быть достигнута
HTML entry Конфигурация упаковки не требует особых изменений и может быть предварительно загружена Еще один уровень запроса, вам нужно запроситьHTMLфайл, а затем использовать обычное сопоставление с нимjsиcss

фактическиqiankunтакже поддерживаетconfig entry:

{
   entry: {
        scripts: [
          "app.3249afbe.js"
          "chunk-vendors.75fba470.js",
        ],
        styles: [
          "app.3249afbe.css"
          "chunk.75fba470.css",
        ],
        html: `<!doctype html>
        		  <html>
        			...
               `
    }
}

Рекомендуется использоватьHTML entry, который используется иiframeТак же просто, как пользовательский интерфейсiframeГораздо сильнее.qiankunзапрос на подпроектindex.htmlПосле этого он сначала будет соответствоватьjs/cssсвязанные теги, а затем заменить его, он должен загрузить себяjsи запустить, затем удалитьhtml/head/bodyи другие теги, остальной контент вставляется как есть в контейнер подэлемента:

использоватьqiankunпреимущества:

  1. qiankunПринести свой собственныйjs/cssфункция песочницы,singles-spaМожет быть решенcssЗагрязнение, но требует сотрудничества подпроектов
  2. single-spaПрограмма поддерживает толькоJS entryфункции, которые ограничивают его только поддержкойvue,react,angularи другие проекты технического развития, для некоторыхjQueryСтарые проекты бессильны.qiankunнет предела
  3. qiankunПоддержка функции предварительного запроса подпроекта.

jsпесочница

js/cssЗагрязнение неизбежно и является проблемой, которая может быть большой или маленькой. Подобно бомбе замедленного действия, вы не знаете, когда что-то пойдет не так, а устранение неполадок сопряжено с трудностями. В качестве базовой структуры очень важно разрешить эти два загрязнения, и ее нельзя разработать только на основе «спецификации».

jsПринцип песочницы заключается в том, что перед загрузкой подпроектаwindowОбъект делает снимок, и снимок восстанавливается при удалении подпроекта, как показано на рисунке:

Итак, как контролироватьwindowКак насчет смены объекта, непосредственноwindowОчевидно, что невозможно выполнить глубокое копирование объекта, а затем детально сравнить каждый атрибут.qiankunФреймворк используетES6новые возможности,proxyметод прокси. Как работать, написано в предыдущей статье (ссылка в конце статьи), поэтому повторяться не буду.

ноproxyнесовместимоIE11Да, для совместимости более низкая версияIEУсыновленныйdiffМетод: поверхностное копированиеwindowобъект, а затем сравните каждое свойство.

css-песочница

qiankunизcssПринцип песочницы заключается в переписыванииHTMLHeadElement.prototype.appendChildСобытия, которые добавляются при запуске подпроектаstyle/linkтеги, которые удаляются при выгрузке подпроектов.

single-spaВ схеме я использовал идею скиннинга для решенияcssЗагрязнение: первоеcss-scopedРешите большую часть загрязнения, для некоторых глобальных стилей отдайте в подпроектыbody/htmlдобавить уникальныйid/class(для обычной разработки и развертывания), затем добавьте это перед глобальным стилемid/classsingle-spaрежимmountцикл, чтобы датьbody/htmlплюс это толькоid/class,существуетunmountЦикл удаляется, так что этот глобальныйcssДействует только для этого проекта.

Фатальная точка этих двух решений заключается в том, что они не могут решить проблему одновременного выполнения нескольких подпроектов.cssзагрязнение и влияние подпроектов на основной проектcssЗагрязнение.

Хотя не принято говорить, что два проекта выполняются одновременно, если вы хотите достичьkeep-alive, вам нужно использоватьdisplay: noneСкройте подпроект, и подпроект не нужно удалять.В это время одновременно будут работать два подпроекта, но один из них не виден пользователю.

cssДругая идея песочницы заключается в том, чтобы ограничить стили подпроектов, чтобы они вступали в силу в рамках контейнера подпроекта, так что вам нужно только предоставить разным подпроектам разные контейнеры. Но это также будет иметь новые проблемы, в подпроектеappendприбытьbody, стиль не вступит в силу. Поэтому необходимо сформулировать спецификации для стилевого загрязнения.classНазовите префикс.

Практика микро-интерфейсных решений

В моих предыдущих статьях (ссылка в конце статьи),single-spaиqiankunизdemoОна реализована, и процессы разработки и развертывания также доступны.Следующим шагом является практика и использование ее в реальных проектах, чтобы знать эти ямы.

Модернизация существующего проекта дляqiankunподпроект

Так как мыvueстек технологий, поэтому я преобразовываюvueПроект используется в качестве примера, чтобы проиллюстрировать, что принципы других технологических стеков одинаковы.

  1. существуетsrcДобавить файлы в каталогpublic-path.js:
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. Исправлятьindex.htmlВ контейнере, инициализированном проектом, не используйте#app, во избежание конфликтов с другими проектами рекомендуется заменить проектnameCamelCase

  2. Изменить файл записиmain.js:

import './public-path';
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import store from './store';

Vue.use(VueRouter)
Vue.config.productionTip = false

let router = null;
let instance = null;
function render(parent = {}) {
  const router = new VueRouter({
    // histroy模式的路由需要设置base,app-history-vue根据项目名称来定
    base: window.__POWERED_BY_QIANKUN__ ? '/app-history-vue' : '/',
    mode: 'history',
    // hash模式不需要上面两行
    routes: []
  })
  instance = new Vue({
    router,
    store,
    render: h => h(App),
    data(){
      return {
        parentRouter: parent.router,
        parentVuex: parent.store,
      }
    },
  }).$mount('#appVueHistory');
}
//全局变量来判断环境,独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log('vue app bootstraped');
}
export async function mount(props) {
  console.log('props from main framework', props);
  render(props.data);
}
export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

Основные изменения: Внести измененияpublicPathфайлы иexportТри жизненных цикла.

Уведомление:

  • webpackизpublicPathЗначение может быть изменено только в файле ввода, причина, по которой оно записывается в отдельный файл и импортируется в начало файла ввода, заключается в том, что это позволяет всему приведенному ниже коду использовать это.
  • требуется файл маршрутаexportДанные маршрутизации, а не экземпляры объектов маршрутизации, функции маршрутизации также необходимо переместить в файл записи.
  • существуетmountЖизненный цикл, вы можете получить данные, переданные родительским проектом,routerМаршруты для перехода к основному проекту/другим подпроектам,storeявляется экземпляром родительского проектаVuex(Другие данные также могут быть переданы).
  1. Изменить конфигурацию упаковкиvue.config.js:
const { name } = require('./package');

module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  // 自定义webpack配置
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd',// 把子应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

Примечание: этоnameПо умолчанию отpackage.jsonПолучить, можно настроить, если он зарегистрирован в родительском проекте.nameПросто будьте последовательны.

В этой конфигурации в основном есть две конфигурации, одна из которых позволяет междоменное использование, а другая должна быть упакована вumdФормат. Зачем собиратьсяumdЧто насчет формата? это позволитьqiankunполучить егоexportфункция жизненного цикла. Можем посмотреть в упаковкеapp.jsПросто знаю:

rootВ среде браузера этоwindow , qiankunВозьмите эти три жизненных цикла, которые основаны на том, что вы указали при регистрации приложения.nameценность,nameНесоответствие приведет к невозможности получить функцию жизненного цикла.

Некоторые соображения по разработке подпроектов

  1. Все ресурсы (картинки/аудио/видео и т.д.) должны быть размещены вsrcкаталог, не ставитьpublicилиstatic

Релиз ресурсаsrcкаталог, будет проходитьwebpackобработка, может быть введена равномерноpublicPath. В противном случае это будет 404 в основном проекте.

Ссылаться на:Официальная документация vue-cli3 вводит: когда использовать -public-folder

Файлы конфигурации, открытые для операторовconfig.js, можно разместить вpublicкаталог, потому что вindex.htmlсерединаurlдля относительных ссылокjs/cssресурс,qiankunВ него будет введен префикс.

  1. Пожалуйста, дайтеaxiosЭкземпляр вместо этого добавить перехватчикaxiosобъект

В будущем предполагается, что подпроекты будут использовать общие плагины.В настоящее время необходимо избегать загрязнения общедоступными плагинами.

// 正确做法:给 axios 实例添加拦截器
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});
// 错误用法:直接给 axios 对象添加拦截器
axios.interceptors.request.use(function () {/*...*/});
  1. избегатьcssЗагрязнение

стилизован внутри компонентаcss-scopedЭто необходимо.

Для некоторых вставок вbodyвсплывающее окно, недоступноscoped, пожалуйста, не используйте оригиналclassИзмените стиль, добавьте свой собственныйclass, чтобы изменить стиль.

.el-dialog{
  /* 不推荐使用组件原有的class */
}
.my-el-dialog{
  /* 推荐使用自定义组件的class */
}
  1. Используйте с осторожностьюposition:fixed

В родительском проекте это позиционирование может быть неточным и его следует по возможности избегать.Там действительно есть необходимость в позиционировании относительно окна браузера.Вы можете использоватьposition: sticky, но будут проблемы с совместимостью (не поддерживается IE). Если таргетинг используетbottomиright, то проблем нет.

Есть и другой способ, локацию можно записать как динамическую привязкуstyleформа:

<div :style="{ top: isQiankun ? '10px' : '0'}">
  1. даватьbody,documentи другие связанные события, пожалуйстаunmountОчистить период

jsПесочница только угналиwindow.addEventListener,использоватьdocument.body.addEventListenerилиdocument.body.onClickДобавленное событие не будет удалено песочницей и повлияет на другие страницы.unmountОчистить период

qiankunОбщие проблемы и решения

qiankunРаспространенная ошибка

  1. Подпроект неexportНеобходимые функции жизненного цикла

Сначала проверьте, есть ли входной файл подпроекта.exportФункция жизненного цикла, затем проверьте упаковку подпроекта и, наконец, проверьте правильность запрошенного файла подпроекта.

  1. Контейнеры плохо отображаются при загрузке подэлементов

Проверьте контейнерdivТо ли в маршруте написано, то ли маршрут не совпадает со всеми выгруженными. Если вы загружаете подэлементы только на определенной странице маршрутизации, вы можетеmountedЗарегистрируйте подпроект в цикле и запустите его.

Маршрут основного элемента можно использовать толькоhistoryРежим?

так какqiankunчерезlocation.pathnameзначение, чтобы определить, какой подпроект должен быть загружен в данный момент, поэтому вам нужно ввести разные маршруты в каждый подпроект.pathhashСкачок маршрутизации подпункта режима не меняетсяpath, так что это не имеет никакого эффекта,historyНастройки маршрутизации подпроекта режимаbaseхарактеристики.

Если основной проект используетhashрежим, затем используйтеlocation.hashзначение, чтобы определить, какой подпроект должен быть загружен в данный момент, и все подпроекты должны бытьhashрежиме, вам также необходимо добавить префикс ко всем маршрутам подпроекта.Если переход маршрута подпроекта используется доpathтакже необходимо изменить, сnameПрыгать не обязательно.

Если основной элементhashПодпункт режимаhistoryрежим, после перехода к подпункту нельзя перейти к другомуhistoryПодпроекты модального окна не могут вернуться на главную страницу проекта.

vueпроектhashИзменение режимаhistoryСхема тоже проста:

  1. new Routerнастройки времениmodeзаhistory:

  1. webpackзапакованный конфиг(vue.config.js ) :

  1. Некоторые ресурсы будут сообщать об ошибке 404, а относительный путь будет изменен на абсолютный:<img src="./img/logo.jpg">изменить на<img src="/img/logo.jpg">Просто

cssПроблемы загрязнения и загрузкиbug

  1. qiankunОн может решить только взаимное загрязнение стилей между подпроектами, но не может решить загрязнение стилей подпроектов, которые загрязняют стили основного проекта.

Если основной проект не должен быть загрязнен стилем подпроекта, подпроектvueтехнология, стиль может быть написанcss-scoped, если подпроектjQueryКак насчет технологий? Итак, сам основной проектid/classОн должен быть особенным, он не может быть слишком простым, он соответствует подпунктам.

  1. При переходе со страницы подпроекта на собственную страницу основного проекта главная страница проектаcssне загруженbug

Причина этой проблемы: при переходе дочернего проекта к родительскому выгрузка дочернего проекта занимает немного времени, за это время происходит загрузка и вставка родительского проекта.css, но элемент одеялаcssПесочница была записана, а затем удалена. Мониторинг событий родительского проекта такой же, поэтому переходить нужно после выгрузки дочернего проекта. Я изначально хотел судить в функции хука маршрутизации, удален ли подпроект, а затем перейти к маршруту после завершения удаления.Однако, если маршрут не перескакивает, подпроект вообще не будет удален.

Временное решение: сначала сделайте копиюHTMLHeadElement.prototype.appendChildиwindow.addEventListener, функция маршрутизацииbeforeEachЕсли текущий маршрут является дочерним проектом, а маршрут, к которому он ведет, является родительским проектом, восстановите эти два объекта.

const childRoute = ['/app-vue-hash','/app-vue-history'];
const isChildRoute = path => childRoute.some(item => path.startsWith(item))
const rawAppendChild = HTMLHeadElement.prototype.appendChild;
const rawAddEventListener = window.addEventListener;
router.beforeEach((to, from, next) => {
  // 从子项目跳转到主项目
  if(isChildRoute(from.path) && !isChildRoute(to.path)){
    HTMLHeadElement.prototype.appendChild = rawAppendChild;
    window.addEventListener = rawAddEventListener;
  }
  next();
});

Проблема перехода маршрута

Как перейти к другому подпроекту/основной странице проекта в подпроекте, написать напрямую<router-link>или использоватьrouter.push/router.replaceНет, причина в этомrouterЭто маршрут подпроекта, все прыжки будут основаны на подпроекте.base. Писать<a>Ссылка может перейти в прошлое, но страница будет обновлена, а взаимодействие с пользователем не очень хорошее.

Решение также относительно простое: при регистрации подпроекта объект экземпляра маршрутизации основного проекта передается, а подпроект монтируется в глобальный, используя этот объект родительского проекта.routerПросто прыгай.

Но есть немного несовершенства, так что это может только пройтиjsДля перехода ссылка перехода не может использовать контекстное меню, которое поставляется вместе с браузером (как показано на рисунке:ChromeВстроенное контекстное меню ссылок)

Проблемы коммуникации проекта

Не допускайте слишком большого количества зависимостей данных между проектами, ведь проекты по-прежнему должны выполняться независимо. Коммуникационные операции должны определить,qiankunрежим, сделайте совместимую обработку.

пройти черезpropsПередать родительский проектVuex, если подпроектvueСтек технологий будет работать хорошо. Если подпроектjQuery/react/angular, он не может хорошо отслеживать изменения данных.

qiakunобеспечивает глобальноеGlobalStateдля обмена данными. После инициализации основного проекта подпроекты могут отслеживать изменения этих данных, а также могут отправлять эти данные.

// 主项目初始化
import { initGlobalState } from 'qiankun';
const actions = initGlobalState(state);
// 主项目项目监听和修改
actions.onGlobalStateChange((state, prev) => {
  // state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
actions.setGlobalState(state);

// 子项目监听和修改
export function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);
  });
  props.setGlobalState(state);
}

vueПри передаче данных между проектами по-прежнему используются общие родительские компоненты.VuexУдобнее общаться с другими проектами стека технологийqiankunкоторый предоставилGlobalState.

Как общие плагины распределяются между подпроектами

Если и основной проект, и подпроекты используют одну и ту же версиюVue/Vuex/Vue-RouterПодождите, после того, как основной проект загружен один раз, подпроекты загружаются снова, что очень расточительно.

Для повторного использования общих зависимостей предварительным условием является то, что подпроекты должны быть настроеныexternals, чтобы зависимости не были упакованы вchunk-vendors.js, чтобы повторно использовать существующие общедоступные зависимости.

Внедрение публичных зависимостей по требованию имеет два уровня:

  1. Неиспользуемые зависимости не загружаются
  2. Большие плагины загружают только те части, которые им нужны, например.UIЗагрузка библиотек компонентов по требованию,echarts/lodashзагрузка по запросу.

webpackизexternalsВведено по запросу для поддержки больших плагинов:

subtract : {
   root: ['math', 'subtract']
}

subtractчерез глобальныйmathсвойства под объектомsubtractдоступ (например,window['math']['subtract']).

single-spaОбщие зависимости подпроектов могут быть импортированы по запросу

single-spaэто использоватьsystemJsЗагружайте подпроекты и общедоступные зависимости, настраивайте общедоступные зависимости и подпроекты вместе, чтобыsystemJsфайл конфигурацииimportmap.json, вы можете добиться загрузки общедоступных зависимостей по требованию:

{
 "imports": {
   "appVueHash": "http://localhost:7778/app.js",
   "appVueHistory": "http://localhost:7779/app.js",
   "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js",
   "vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
   "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js",
   "echarts": "https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js"
 }
}

qiankunКак ввести общие зависимости по требованию

Публичные зависимости и публичные функции приложения Big Mac используются слишком большим количеством страниц, что затрудняет обновление и изменение.Использование микрофронтенда позволяет каждому подпроекту независимо иметь свои зависимости, не мешая друг другу. И мы хотим повторно использовать публичные зависимости, что противоречит концепции микрофронтендов.

Итак, моя идея такова: родительский проект предоставляет общедоступные зависимости, а дочерний проект может свободно выбирать, использовать его или нет.

Это также очень легко реализовать: родительский проект сначала загружает зависимости, а затем при регистрации дочернего проектаVue/Vuex/Vue-RouterпереждатьpropsВ прошлом подпроекты могли выбирать, использовать их или нет.

Основной проект:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun';
import Vuex from 'vuex';
import VueRouter from 'vue-router';

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

registerMicroApps([
  { 
    name: 'app-vue-hash', 
    entry: 'http://localhost:1111', 
    container: '#appContainer', 
    activeRule: '/app-vue-hash', 
    props: { data : { store, router, Vue, Vuex, VueRouter } }
  },
]);

start();

Подпроект:

import Vue from 'vue'

export async function bootstrap() {
  console.log('vue app bootstraped');
}

export async function mount(props) {
  console.log('props from main framework', props);
  const { VueRouter, Vuex } = props.data;
  Vue.use(VueRouter);
  Vue.use(Vuex);
  render(props.data);
}

export async function unmount() {
  instance.$destroy();
  instance = null;
  router = null;
}

Это невозможно по двум причинам:

  1. Когда подпроекты выполняются независимо,Vue-Router/VuexОткуда берутся эти зависимости? Подпроекты развертываются только один раз, и их можно запускать независимо илиqiankunинтегрированный.
  2. Родительский проект может передавать только те зависимости, которые у него уже есть. Как определить, какие зависимости нужны дочернему проекту? Не отвечает потребностям внедрения по требованию

настроитьwebpackизexternalsПосле этого при независимом запуске подпроекта источник этих зависимостейесть и только index.htmlвнешние ссылки вscriptЭтикетка.

В соответствии с этой предпосылкой подпроект и основной проектvueЕсли версии одинаковые, используйте один и тот же файл сервера. Даже если вы не можете поделиться, вы можетеhttpкэшировано.

ТакqiankunМожно ли сделать так, чтобы после загрузки определенной зависимости она не загружалась и не использовалась напрямую? Скажем, подпроект A запрашивает версию 2.6 на сервере.vue, переключитесь на подпроект B, который также использует этотvueМожно ли повторно использовать файл напрямую, не загружая его снова?

На самом деле, это возможно, вы можете видетьqiankunВнешняя ссылка подпроектаscriptПосле того, как содержимое будет запрошено, оно будет записано в глобальную переменную, и при следующем повторном использовании оно будет сначала взято из этой глобальной переменной. Это позволит реализовать повторное использование контента, если гарантированы две ссылки.urlВы можете согласиться.

const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
		(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => response.text()));

Итак, пока подпроект настроенwebpackизexternals, И вindex.htmlИспользуйте внешние ссылки вscriptКогда эти общедоступные зависимости вводятся, пока эти общедоступные зависимости находятся на одном сервере, общедоступные зависимости подпроектов могут быть импортированы по запросу.После использования одного проекта другой проект не будет загружать его повторно, и файл могут быть повторно использованы напрямую.

qiankunБолее совершенное введение по запросу

Несмотря на то чтоqiankunне будет повторять тот же запросurlобщедоступные зависимости, но это также толькоhttpКэш немного мощнее.

Недостатки:

  1. Общие зависимости в основном проекте не записываются в этот кеш и не будут повторно использоваться другими проектами.
  2. Просто повторного запроса нет, и его все равно нужно повторить один раз. Можно ли его использовать повторно без исполнения? .jsПесочница удаляется при удалении подпроектаwindowновые переменные наwebpackизexternalsИменно для монтирования этих публичных зависимостей вwindowМожете ли вы удалить эти общедоступные зависимости в зависимости от ситуации?
  3. Зависимости одной и той же версии будут использоваться повторно. Версии разные, но использование одинаковое. Можно ли использовать повторно? (разные версииurlОн тоже другой, переиспользоваться не будет) Но тут могут быть некоторые сомнения, раз разницы в использовании нет, то почему бы не обновить плагин?

Эти вопросы, возможно, должны быть измененыqiankunисходный код.

jQueryПроблема с загрузкой ресурсов для старых проектов

Тег содержимого дочернего проекта вставляется в тег содержимого родительского проекта.index.htmlПосле этого ресурсы в нем (img/video/audioи т. д.) все пути являются относительными, что приводит к неправильному отображению ресурсов. Выше я перечислил три решения.

Вообще говоря,jQueryПроект не проходитwebpackУпакован, поэтому его нельзя изменитьpublicPathввести префикс пути. Последние два метода более хлопотны в эксплуатации, иначе мы должныприоритет от самого фреймворкарешить эту проблему, а не наоборот. Поэтому я подумал о следующих трех вариантах:

Вариант 1: динамическая вставка<base>Этикетка

htmlимеет родной тег<base>, эта метка может быть размещена только в<head>внутри, егоhrefсобственность - этоurlценность.mdnадрес:корневой URL-элемент базового документа

уже настроен<base>После тега все ссылки на странице иurlна его основеhref. Например, адрес доступа к страницеhttps://www.taobao.com,настраивать<base href="https://www.baidu.com">После этого исходное изображение на странице<img src="./img/jQuery1.png" alt="">Фактический адрес запроса станетhttps://www.baidu.com/img/jQuery1.png, на странице<a>Ссылка на сайт:<a href="/about"></a>, после нажатия страница перейдет на:https://www.baidu.com/about

можно увидеть,<base>этикетки иwebpackизpublicPathимеют тот же эффект, то может лиjQueryПеред загрузкой проекта поместитеjQueryАдрес проекта закреплен за<base>Отметьте, затем вставьте<head>? это решитjQueryПроблема загрузки ресурсов проекта.

Это также очень просто, т.qiankunкоторый предоставилbeforeLoadЖизненный цикл, чтобы определить, является ли текущийjQueryпроект:

beforeLoad: app => {
   if(app.name === 'purehtml'){
       const baseTag = document.createElement('base');
       baseTag.setAttribute('href',app.entry);
       console.log(baseTag);
       document.head.appendChild(baseTag);
   }
},
beforeUnmount: app => {
   if(app.name === 'purehtml'){
      const baseTag = document.head.querySelector('base');
      document.head.removeChild(baseTag);
   }
}

При этом ресурсы подпроекта загружаются корректно, но<base>Сила метки слишком сильна, что приведет к тому, что все маршруты не смогут нормально переходить.<a>ссылка основана на<base>Да, будет прыгатьjQueryНесуществующие маршруты для подпроектов. решил одинbug, новыйbug, это невозможно. Поэтому целесообразность этой схемы очень мала.

Вариант 2: взломать функцию вставки этикетки

Эта схема состоит из двух шагов:

  1. заHTMLсуществующий вimg/audio/videoэтикетка и т. д.,qiankunпереписатьgetTemplateфункция, вы можете поместить файл вводаindex.htmlЗамените путь к статическому ресурсу в
  2. для динамически вставленныхimg/audio/videoтэги, угонappendChild,innerHTML,insertBeforeи другие события, замените относительный путь ресурса абсолютным путем

Как мы упоминали ранее, для подпроектовHTML entryиз,qiankunполучить входной файлindex.htmlПосле этого он будет сопоставлен с обычным<body>этикетки и их содержимое,<head>серединаlink/style/script/metaт. д. теги, затем вставляются в контейнер родительского элемента.

мы можем передатьgetTemplateфункция для преобразования относительного пути изображения в абсолютный путь, который будет использоваться при обработке шаблонов:

start({
  getTemplate(tpl,...rest) {
    // 为了直接看到效果,所以写死了,实际中需要用正则匹配
    return tpl.replace('<img src="./img/jQuery1.png">', '<img src="http://localhost:3333/img/jQuery1.png">');
  }
});

Для динамически вставляемых тегов перехватите их вставкуDOMфункция, введите префикс.

Если подпроект динамически вставляет картинку:

const render = $ => {
  $('#purehtml-container').html('<p>Hello, render with jQuery</p><img src="./img/jQuery2.png">');
  return Promise.resolve();
};

Угон основного предметаjQueryизhtmlметод:

beforeMount: app => {
   if(app.name === 'purehtml'){
       // jQuery 的 html 方法是一个挺复杂的函数,这里只是为了看效果,简写了
       $.prototype.html = function(value){
          const str = value.replace('<img src="/img/jQuery2.png">', '<img src="http://localhost:3333/img/jQuery2.png">')
          this[0].innerHTML = str;
       }
   }
}

Конечно, есть и простой и грубый способ письма, дающийjQueryПуть к образу проекта записывается как абсолютный путь, но это не рекомендуется, и это не сработает, если вы измените развертывание сервера.

Вариант третий: датьjQueryпункт плюсwebpackБэйл

Реализуемость этого плана не высока.Это старый-престарый проект, так что не стоит делать такой жеребьевки.

Сводка загрузки ресурсов старых проектов

qiankunполучить доступ к себеjQueryМногостраничные приложения относительно слабы, общий сценарий использования — большой проект обращается только к одной/нескольким страницам, в этом случае более разумен второй вариант.

qiankunИспользовать сводку

  1. Чтобы включить предварительную загрузку, когда есть только один подпроект, вы должны использоватьstart({ prefetch: 'all' })

  2. jsПесочница не решает всегоjsзагрязнение, например, я используюonclickилиaddEventListenerдавать<body>Добавлено событие клика,jsПесочница не исключает его влияния, поэтому приходится полагаться на спецификацию кода и самосознание

  3. qiankunФреймворк не очень прост в реализацииkeep-aliveпотребности, потому что решениеcss/jsСпособ загрязнения - удалить вставленный подпроектcssТегирование и угонwindowобъекта, при выгрузке он восстанавливается до того, каким он был до загрузки подпроекта, что аналогичноkeep-aliveПротиворечие:keep-aliveПросьба сохранить их — это просто стилистическое сокрытие.

  4. qiankunНе могу хорошо встроить некоторые старые проекты

Несмотря на то чтоqiankunслужба поддержкиjQueryСтарый проект, но вроде правильныймногостраничное приложениеНет хорошего решения. Модифицировать каждую страницу очень дорого и хлопотно, но с помощьюiframeЭти старые проекты удобнее встраивать.

  1. проблемы с безопасностью и производительностью

qiankunкаждого подпроектаjs/cssСодержимое файла записывается в глобальную переменную.Если подпроектов слишком много или размер файла большой, это может привести к чрезмерному использованию памяти и зависанию страницы.

Кроме того,qiankunзапустить подпроектjs, не черезscriptтеги вставляются, но черезevalфункция реализована,evalБезопасность и выполнение функций несколько спорны:Предварительное введение MDN

  1. При отладке микрофронтенда вам нужно вводить подпроект и основной проект для запуска и упаковки каждый раз отдельно, что очень хлопотно и может быть использованоnpm-run-allПлагины для достижения: одна команда для запуска всех проектов.
{
  "scripts": {
    "install:hash": "cd app-vue-hash && npm install",
    "install:history": "cd app-vue-history && npm install",
    "install:main": "cd main && npm install",
    "install:purehtml": "cd purehtml && npm install",
    "install-all": "npm-run-all install:*",
    "start:hash": "cd app-vue-hash && npm run serve ",
    "start:history": "cd app-vue-history && npm run serve",
    "start:main": "cd main && npm run serve",
    "start:purehtml": "cd purehtml && npm run serve",
    "start-all": "npm-run-all --parallel start:*",
    "serve-all": "npm-run-all --parallel start:*",
    "build:hash": "cd app-vue-hash && npm run build",
    "build:history": "cd app-vue-history && npm run build",
    "build:main": "cd main && npm run build",
    "build-all": "npm-run-all --parallel build:*"
  }
}

в--parallelПараметр указывает на параллелизм, без этого параметра следующая команда будет выполняться после выполнения предыдущей команды.

конец

неiframeПредубеждение, это тоже реализация микро-фронтенда, если нет всплывающего окна, нет полноэкранного режима и прочих операций на странице,iframeЭто также очень полезно. настроить кеш иcdnУскорение, если это интранет-доступ, будет не очень медленным.

iframeиqiankunмогут сосуществовать,jQueryИспользование многостраничного приложенияiframeДоступ очень хороший.Когда и какой сценарий следует использовать, какое решение следует использовать, и конкретная ситуация будет подробно проанализирована.

Наконец, если есть какие-либо проблемы или ошибки в статье, пожалуйста, укажите, спасибо!

В содержании статьи есть некоторые дополнения, но эта статья не может быть записана, см.:Краткое изложение практики qiankun micro front-end (2)

приложение

single-spaиqiankunизdemoКак реализовать и некоторые принципы, вы можете увидеть в этих трех моих статьях:

  1. Внедрение внешнего микросервиса с 0 (на)
  2. Внедрение внешнего микросервиса single-spa с 0 (средний уровень)
  3. Внедрение внешнего микросервиса single-spa с 0 (ниже)

PS: Третья статья была написана в марте этого года, в ней участвовалиqiankunИсходный код версии 1.0,qiankunВерсия 2.0 была выпущена в апреле, но ее основные принципы практически не изменились.

Взгляды и практика других фронтенд-команд в отрасли на микрофронтенды:

  1. Преобразование микроинтерфейса команды фронтенда ежедневной цепочки поставок превосходных свежих продуктов
  2. Практика микро-интерфейса на вынос Meituan
  3. Полировка и применение интерфейсных микросервисов в ByteDance
  4. Практика микроинтерфейса в CRM-системе Xiaomi
  5. Реализация стандартной архитектуры микроинтерфейса в Ant

qiankunонлайн-кейсов

  1. tech.antfin.com/partners
  2. www.zstack.io/