предисловие
В микроинтерфейсном решении мы наконец выбралиqiankun
, предыдущая статьяПрактика и краткое изложение решения qiankun micro front-endтакже подведены итогиqiankun
Есть много "ям" и решений. Эта статья является дополнением к предыдущей статье. Я хотел отредактировать предыдущую статью напрямую, но я не ожидал, что сработает ограничение на максимальную длину содержания статьи Nuggets:
Поэтому мне пришлось переставить и записать некоторые из ям и расследований, на которые я наступил, в надежде быть полезным для всех. Содержание этой статьи основано наqiankun
2.х версия .
Возможные ямы и решения
Некоторые проблемы обнаружены и решены мной самостоятельно, некоторыеqiankun
складissue
видна область. Для этих проблем я стараюсь объяснить причины и решения как можно яснее.
Не удалось загрузить файл шрифта подпроекта
qiankun
для подпроектовjs/css
обработка
Сказал раньше:qiankun
запрос на подпроектindex.html
После этого он сначала будет соответствоватьjs/css
связанные теги, а затем заменить его, он должен загрузить себяjs/css
И запустить, затем удалитьhtml/head/body
и другие теги, остальной контент вставляется как есть в контейнер подэлемента:
дляjs
( <script>
теги) обработка
в линиюjs
Содержимое будет записано непосредственно в объект, внешняя ссылкаjs
буду использоватьfetch
Перейдите к содержимому (строка), а затем войдите в этот объект.
if (isInlineCode(script)) {
return getInlineCode(script);
} else {
return fetchScript(script);
}
const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => response.text()));
При запуске подпроекта выполните этиjs
Только что:
//内联js
eval(`;(function(window){;${inlineScript}\n}).bind(window.proxy)(window.proxy);`)
//外链js
eval(`;(function(window){;${downloadedScriptText}\n}).bind(window.proxy)(window.proxy);`))
Загружать и запускать внешние ссылкиjs
Трудность здесь в том, как обеспечитьjs
правильный порядок выполнения?
<script>
помеченasync
а такжеdefer
Атрибуты:
-
defer
: Эквивалент внешней цепочкиjs
внизу страницы -
async
: выполняется асинхронно относительно остальной части страницы и выполняется при загрузке. обычно используетсяGoogle Analytics
Итак, внешняя цепьjs
Просто различайте междуasync
,имеютasync
из<script>
использоватьpromise
Асинхронная загрузка, вы можете выполнить ее после загрузки, нетasync
Свойства выполняются по порядку.
Предположим, что в HTML есть следующееjs
Этикетка:
<script src="./a.js" onload="console.log('a load')"></script>
<script src="./b.js" onload="console.log('b load')"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
<script src="./c.js" onload="console.log('c load')"></script>
Обычная логика загрузки и выполнения браузераЗагружать параллельно, но выполнять последовательноНо пока загружается первая загрузка, она сразу выполнит первую. Если третья не загружена, четвертая, даже если загрузка завершится, не будет выполняться первой.
Как показано на рисунке, я ограничиваю скорость интернета до5kb/s
,Третийjs
Еще не загрузился, но четвертыйjs
Он был загружен, но не будет выполняться.
qiankun
загружается параллельно, ноПодождите, пока все js загрузятся, а затем выполнить последовательно. Он немного отличается от родного порядка выполнения загрузки браузера, но эффект тот же.
Здесь есть место для оптимизации: пока предыдущийjs
После того, как он загружен и выполнен, его можно выполнить сразу же после загрузки, не дожидаясь последующегоjs
загрузка завершена.
дляcss
( <style>
а также<link>
теги) обработка
Логика загрузки осталась прежней: inlinecss
(<style>
Содержимое метки) будет записано непосредственно в объект, внешняя ссылкаcss
(<link>
ярлык) будет использоватьfetch
Перейдите к содержимому (строка), а затем войдите в этот объект.
Но при исполнении такжеjs
«Похожие»: Контент, размещенный в<style>
теги, затем вставляются на страницу, подпункты выгружаются, удаляются эти<style>
Этикетка.
Это свяжетcss
стать встроеннымcss
, преимущество заключается в том, чтобы переключить подсистему, не повторяя запрос, напрямую применитьcss
стили, чтобы подэлементы загружались быстрее.
Но принесет скрытую яму,css
Если используется файл шрифта, и это относительный путь, он изначальноlink
Стиль внешней ссылки, относительный путь относится к этой внешней ссылкеcss
путь, теперь становится встроенным стилем, относительный путь становится относительнымindex.html
путь, приведет к ошибке 404 для файла шрифта.
Что более жалко, так этоВ режиме разработки такой проблемы нет, этот путь будет введен в режиме разработкиpublicPath
, будет эта проблема после упаковки.
Как решить проблему сбоя загрузки файла шрифта подпроекта
Хотя это потому чтоqiankun
подпроект<link>
изменить на<style>
Выполнение , вызвало проблему, но кажется, что это нормально и разумно.
Фундаментальная причина в том, что файл шрифта даже послеwebpack
Обработано, но префикс пути не введен. Так изменитьwebpack
конфигурации, пусть файл шрифта пройдетurl-loader
обработка, упаковка вbase64
, эту проблему можно решить.
подпроектwebpack
Добавьте в конфигурацию следующее:
module.exports = {
chainWebpack: (config) => {
config.module
.rule('fonts')
.test(/.(ttf|otf|eot|woff|woff2)$/)
.use('url-loader')
.loader('url-loader')
.options({})
.end()
},
}
Посмотреть источник найденvue-cli4
Это также обрабатывается таким образом, но он ограничивает шрифты в пределах 4 КБ для упаковки в base64.
Подпроекты развернуты во вторичных каталогах
Первыйvue
Чтобы развернуть проект во вторичный каталог, его необходимо настроитьpublicPath
,Описание официального сайта vue-cli3:
Тогда следует отметить, что при регистрации подпроектовзапись адреса входаЗаполнить.
Предположим, что подпроект развернут вapp-vue-hash
Под содержанием,entry
написать напрямуюhttp://localhost/app-vue-hash
приведет кqiankun
ВыбиратьpublicPath
ошибка. Адрес входа в подпроектhttp://localhost/app-vue-hash
Относительный путьhttp://localhost
, и мы хотим, чтобы относительный путь подпроекта былhttp://localhost/app-vue-hash
, то нам нужно только написатьhttp://localhost/app-vue-hash/
Вот и все,Последнее / не может быть опущено.
qiankun
ВыбиратьpublicPath
Исходный код:
function defaultGetPublicPath(url) {
try {
// URL 构造函数不支持使用 // 前缀的 url
const { origin, pathname } = new URL(url.startsWith('//') ? `${location.protocol}${url}` : url, location.href);
const paths = pathname.split('/');
// 移除最后一个元素
paths.pop();
return `${origin}${paths.join('/')}/`;
} catch (e) {
console.warn(e);
return '';
}
}
Тестируя, мы можем найтиhttp://localhost/app
а такжеhttp://localhost/app/
два разных путиserver
, тот самыйhtml
, затем вhtml
Ввести относительный путь к ресурсам. Адрес разрешения браузера:
иллюстрироватьqiankun
ВыбиратьpublicPath
обработка правильная.
Совместимость с IE11
qiankun
При загрузке подпроекта используйтеfetch
, поэтому для IE11 вам нужно ввестиfetch
изpolyfill
, но представилfetch-polyfill
, все равно выдает ошибку под IE11.
интуитивноsingle-spa.min.js
Сообщается об ошибке, но этот код представляет собой сжатый код, который нелегко устранить.
оказатьсяnode_modules\single-spa\package.json
,оказаться"module": "lib/esm/single-spa.min.js"
Измените эту строку на"module": "lib/esm/single-spa.dev.js"
, затем перезапустите проект.
Обнаружитьsingle-spa
Причина ошибкиAPP
Загрузка не удалась, код такой:
function handleAppError(err, app, newStatus) {
var transformedErr = transformErr(err, app, newStatus);
if (errorHandlers.length) {
errorHandlers.forEach(function (handler) {
return handler(transformedErr);
});
} else {
setTimeout(function () {
throw transformedErr; // 这一行抛出的错误,也就是控制台显示的错误信息
});
}
}
Распечатайте информацию об ошибке загрузки приложения следующим образом:
После расследования этоfetch
изpolyfill
Проблема в том, что он сообщает об ошибке.
Однако только импортfetch-polyfill
, а затем использовать в проектеfetch
, не сообщит об этой ошибке.
импортировать одновременноqiankun
а такжеfetch-polyfill
, он сообщит об этой ошибке:
import 'fetch-polyfill';
import { registerMicroApps, start } from 'qiankun';
console.log(fetch);
console.log(fetch("http://localhost:1111"));
Расследование и причины долгое время эти два плагина не совместимы. наконец,qiankun
Обходной путь автора заключается в использованииwhatwg-fetch
, то отображаемый списокpromise
,symbol
ожидающийpolyfill
.
Введите следующее содержимое в основной файл записи проекта (не забудьте установить зависимости):
import 'whatwg-fetch';
// import 'custom-event-polyfill'; // 如果还报错,需要引入这个
import 'core-js/stable/promise';
import 'core-js/stable/symbol';
import 'core-js/stable/string/starts-with';
import 'core-js/web/url';
Я попробовал, IE11 может работать отлично, но иногда будут возникать некоторые ошибки, связанные с сетью, которые не влияют на работу страницы, это должно бытьwebsocket
В результате производственная среда не сообщит об ошибке.
Статьи автора qiankun:Как изящно обеспечить совместимость с IE в 2020 году
vue
Проблема с утечкой памяти подпроекта
Эту проблему очень трудно найти, вqiankun
изissue
Район видит:GitHub.com/UfanAccept/Kg…, процесс расследования выкладывать не буду, решение достаточно простое.
Пусто, когда подпроект уничтоженdom
Только что:
export async function unmount() {
instance.$destroy();
+ instance.$el.innerHTML = ""; //新增这一行代码
instance = null;
router = null;
}
Но на самом деле переключение подпроектов туда-сюда не заставляет память продолжать увеличиваться. То есть, даже если память, занимаемая подпроектом, не освобождается при выгрузке подпроекта, эта память будет повторно использована при следующей загрузке подпроекта. загружаться быстрее? (Еще не проверено)
Исследования и мышление с особыми потребностями
После удовлетворения базового использования микроинтерфейса были исследованы некоторые оптимизации и специальные требования, чтобы облегчить его использование.
keep-alive
нужно
подпроектkeep-alive
На самом деле, я просто хочу не удалять его при переключении подпроекта, просто скрыть стиль (display: none
), поэтому в следующий раз вы откроете его быстрее.
keep-alive
Необходимо использовать с осторожностью, загрузка и запуск нескольких подпроектов одновременно увеличиваетjs/css
риск загрязнения.
несмотря на то чтоqiankun
изproxy
манераjs
Песочница может прекрасно поддерживать многопроектную работу, но не стоит забывать о опухоли IE11.Песочница под IE11 используетdiff
метод, который позволит нескольким проектам совместно использовать песочницу, что эквивалентно отсутствию песочницы. Также могут быть конфликты между маршрутами.
многопроектный запускcss
Не существует особенно хорошего способа справиться с песочницами, в настоящее время более надежными являютсяclass
пространство имен +css-scoped
.
выполнитьkeep-alive
Существует множество способов удовлетворения потребностей, и рекомендуется использовать схему 1.
Вариант 1: С помощьюloadMicroApp
функция
попробуй уже использоватьAPI
реализоватьkeep-alive
Требование: с помощьюloadMicroApp
Функции ручной загрузки и выгрузки подпроектов обычно имеютkeep-alive
то, что нужноtab
страницу, добавить новуюtab
Этот подпроект загружается при открытии страницы, закрытьtab
Удалите этот подпроект, когда будете пейджинговать.
из-заdemo
Нет вtab
страницу, я напрямую загружаю все подпроекты, а потом смотрю на эффект, изменений не много.
основной проектAPP.vue
документ:
<template>
<div id="app">
<header>
<router-link to="/app-vue-hash/">app-vue-hash</router-link>
<router-link to="/app-vue-history/">app-vue-history</router-link>
<router-link to="/about">about</router-link>
</header>
<div id="appContainer1" v-show="$route.path.startsWith('/app-vue-hash/')"></div>
<div id="appContainer2" v-show="$route.path.startsWith('/app-vue-history/')"></div>
<router-view></router-view>
</div>
</template>
<script>
import { loadMicroApp } from 'qiankun';
const apps = [
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer1',
props: { data : { store, router } }
},
{
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer2',
props: { data : store }
}
]
export default {
mounted() {
// 优先加载当前的子项目
const path = this.$route.path;
const currentAppIndex = apps.findIndex(item => path.includes(item.name));
if(currentAppIndex !== -1){
const currApp = apps.splice(currentAppIndex, 1)[0];
apps.unshift(currApp);
}
// loadMicroApp 返回值是 app 的生命周期函数数组
const loadApps = apps.map(item => loadMicroApp(item))
// 当 tab 页关闭时,调用 loadApps 中 app 的 unmount 函数即可
},
}
</script>
Переключить подпроект, подпроектDOM
Не опустошено:
Вариант 2: изменитьqiankun
исходный код
Это также относительно просто реализовать: выгрузка подсистемы не приводит к опорожнению контейнера.dom
не удалятьvue
например, сdisplay: none
прятаться. При загрузке подсистемы сначала определите, есть ли в контейнере контент, и если он уже существует, он не будет повторно вставлен в подсистему.HTML
.
В основном делится на 4 этапа:
- Изменить подпроект
render
функция, не созданная повторноvue
function render() {
if(!instance){
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue-history' : '/',
mode: 'history',
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount('#appVueHistory');
}
}
- Изменить подпроект
unmount
Жизненный цикл, подпроектunmount
не удалятьvue
Пример
export async function unmount() {
// instance.$destroy();
// instance = null;
// router = null;
}
- Измените регистрацию и контейнер подпроектов в основном проекте, и поставьте отдельный контейнер для каждого подпроекта (конечно, вы можете поместить его в контейнер, с которым более хлопотно иметь дело). Затем переключите подсистему, чтобы скрыть другую
<div id="appContainer1" v-show="$route.path && $route.path.startsWith('/app-vue-hash')"></div>
<div id="appContainer2" v-show="$route.path && $route.path.startsWith('/app-vue-history')"></div>
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer1',
activeRule: '/app-vue-hash',
props: { data : { store, router } }
},
{
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer2',
activeRule: '/app-vue-history',
props: { data : store }
},
]);
- Исправлять
qiankun
Исходный код подпроекта: судить о наличии контента перед загрузкой подпроекта, если есть контент, он не будет обработан, при выгрузке подсистемы он не будет очищенdom
Просто доступно здесьpatch-package
плагин, прямая модификацияqiankun/es/loader.js
, изменения следующие:
Исправлятьqiankun/es/sandbox/patchers/dynamicHeadAppend.js
:
На этом этапе вы можете добиться эффекта переключения подэлементов без опустошения и ввода второго эффекта загрузки в следующий раз:
Эта программа предназначена только для справки, углубить пониманиеqiankun
понимание.
Вариант 3: кэшировать подпроектыdom
Источник программы:Проблемы в репозитории qiankun
Это решение более хлопотное, общий принцип - кешvue
примерdom
, изменяется входной файл подпроекта:
let instance = null;
let router = null;
function render() {
// 这里必须要new一个新的路由实例,否则无法响应URL的变化。
router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
});
if (window.__POWERED_BY_QIANKUN__ && window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__) {
const cachedInstance = window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__;
// 从最初的Vue实例上获得_vnode
const cachedNode =
// (cachedInstance.cachedInstance && cachedInstance.cachedInstance._vnode) ||
cachedInstance._vnode;
// 让当前路由在最初的Vue实例上可用
router.apps.push(...cachedInstance.catchRoute.apps);
instance = new Vue({
router,
store,
render: () => cachedNode
});
// 缓存最初的Vue实例
instance.cachedInstance = cachedInstance;
router.onReady(() => {
const { path } = router.currentRoute;
const { path: oldPath } = cachedInstance.$router.currentRoute;
// 当前路由和上一次卸载时不一致,则切换至新路由
if (path !== oldPath) {
cachedInstance.$router.push(path);
}
});
instance.$mount('#appVueHash');
} else {
console.log('正常实例化');
// 正常实例化
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount('#appVueHash');
}
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render();
}
export async function unmount() {
console.log('[vue] vue app unmount');
const cachedInstance = instance.cachedInstance || instance;
window.__CACHE_INSTANCE_BY_QIAN_KUN_FOR_VUE__ = cachedInstance;
const cachedNode = cachedInstance._vnode;
if (!cachedNode.data.keepAlive) cachedNode.data.keepAlive = true;
cachedInstance.catchRoute = {
apps: [...instance.$router.apps]
}
instance.$destroy();
router = null;
instance.$router.apps = [];
}
Повторное использование общих зависимостей (схема)
Чтобы повторно использовать общие зависимости в подпроектах, настройтеwebpack
изexternals
Это необходимо, и после настройки этого, когда подпроект работает самостоятельно, источники этих зависимостей имеют и толькоindex.html
Внешние ссылки Inscript
Этикетка.
Есть два случая:
- Зависимость «повторное использование» между подпроектами
С этим легко справиться, вам нужно только убедиться, что зависимыйurl
Вы можете согласиться. Например, подпроект A используетvue
, подпроект B также использует ту же версиюvue
, если оба проекта используют одну и ту же копиюCND
файл, который сначала читается из кеша при загрузке:
const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => response.text()));
- Подпроекты повторно используют зависимости основного проекта
Просто дайте подпроектindex.html
общественный иждивенецscript
а такжеlink
тег плюсignore
Атрибуты (это настраиваемый атрибут, а не стандартный атрибут).
С этим свойством,qiankun
не буду загружать это сноваjs/css
, в то время как подпроекты выполняются независимо, этиjs/css
Его все еще можно загрузить Таким образом, «подпроект повторно использует зависимости основного проекта».
<link ignore rel="stylesheet" href="//cnd.com/antd.css">
<script ignore src="//cnd.com/antd.js"></script>
Следует отметить, что: основной проект используетexternals
После этого подпроекты могут повторно использовать свои зависимости, но подпроекты, которые не используют повторно зависимости, сообщат об ошибке.
прочитай этоqiankun
Официальный сайт написал этот вопрос:Vue Router сообщает об ошибке Uncaught TypeError: невозможно переопределить свойство: $router
Причина ошибки
Подпроекты не настроеныexternals
, в проектеVue
является глобальной переменной, ноЭто не принадлежит окну, поэтому, когда подпроекты выполняются независимо, этоif
Решение недействительно:
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
а такжеqiankun
При запуске этого подпроекта сначала найдитеwindow
, а затем найдите родительский проектwindow
, затем вwindow
найти наvue
.if
решение вступит в силу, тоwindow
родительского проектаVue
установленыVueRouter
, собственный глобальныйvue
Не устанавливается, что приводит к ошибке.
Решение 1. Обработайте глобальные переменные перед загрузкой подпроектов
Предположениеapp-vue-hash
Подпроекты повторно используют основные зависимости проекта,app-vue-history
Подпроекты не используют повторно основные зависимости проекта.
При регистрации подпроектов в основном проекте добавьте следующий код для решения проблемы:
registerMicroApps(apps,
{
beforeLoad(app){
if(app.name === 'app-vue-hash'){
// 如果直接在 app-vue-hash 子项目刷新页面,此时 window.Vue2 是 undefined
// 所以先判断下 window.Vue2 是否存在
if(window.Vue2){
window.Vue = window.Vue2;
window.Vue2 = undefined;
}
}else if(app.name === 'app-vue-history'){
window.Vue2 = window.Vue;
window.Vue = undefined
}
},
});
Решение второе: пройтиprops
транзитивные зависимости
Вышеупомянутые проблемы совместимости могут быть рассмотреныОсновной проект передает зависимости подпроектам через пропсы, без настройки внешнихрешать.
Когда основной проект зарегистрирован, передайте зависимости подпроектам (некоторый ненужный код опущен):
import VueRouter from 'vue-router'
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: '/app-vue-hash',
props: { data : { VueRouter } }
},
]);
Конфигурация подпроектаexternals
и зависимости внешней цепи плюсignore
Атрибуты:
function render(parent = {}) {
if(!instance){
// 当它独立运行时,使用自己的外链依赖 window.VueRoute
const VueRouter = parent.VueRouter || window.VueRouter;
Vue.use(VueRouter);
router = new VueRouter({
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount('#appVueHash');
}
}
export async function mount(props) {
render(props.data);
}
Решение 3. Измените имена зависимостей основного проекта и подпроектов.
Измените имя зависимостей, которые повторно используются основным приложением и подчиненными приложениями, чтобы это не повлияло на другие вспомогательные приложения, которые не используют повторно зависимости. Конкретные изменения:
- Изменить вспомогательное приложение и основное приложение
externals
Настроить, изменить имя зависимости, не использоватьVue
externals: {
'vue': 'Vue2' , // 这个的意思是告诉 webpack 去把 winodw.Vue2 当做 vue 这个模块
}
- Импорт внешних ссылок в основном приложении
vue.js
После этого измените имя наVue2
<script src="https://unpkg.com/vue@2.5.16/dist/vue.runtime.min.js"></script>
<script>
window.Vue2 = winow.Vue;
window.Vue = undefined;
</script>
Из трех схем рекомендуется третья, более лаконичная и удобная.
основной проект свернутhash
а такжеhistory
боевой
Некоторый контент, который я написал ранее, был не очень исчерпывающим, поэтому я реорганизовал его. Есть три случая для обсуждения:
Маршрутизация основного проектаhistory
модель
Основное использование проектаhistory
режим, вам нужно использоватьlocation.pathname
различать различные подпроекты, что такжеqiankun
Форма рекомендации. При регистрации подпроектаactiveRule
Просто напишите путь:
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: '/app-vue-hash',
},
])
преимущество:
- Подпроекты могут использовать
history
режим, вы также можете использоватьhash
модель . Таким образом, все старые проекты могут быть напрямую связаны, и совместимость будет сильной. - правильно
hash
Подпункты Mode не действуют и не нуждаются в изменении
недостаток:
-
history
Маршрутизация шаблона должна быть установленаbase
- Переход между подпроектами требует использования родительского проекта.
router
предмет (без<a>
Причина, по которой ссылка переходит напрямую, заключается в том, что<a>
ссылка обновляет страницу).
На самом деле не проходитrouter
объект, используя собственныйhistory
Прыжки с объектов также работают:history.pushState(null, 'name', '/app-vue-hash/#/about')
, снова страница не будет обновляться.
Является ли это родительским проектомrouter
объект, еще роднойhistory
объект, прыжокjs
Путь.这里有一个小小的用户体验问题:标签(<router-link>
а также<a>
) в виде прыжка поддерживает контекстное меню браузера по умолчанию.js
Выхода нет:
Маршрутизация основного проектаhash
режим и подпроекты не имеютhistory
маршрутизация по шаблону
То есть основной проект и все подпроектыhash
режиме, в этом случае тоже есть два подхода:
- использовать
path
различать подпроекты
Я не буду вдаваться в подробности того, как
Преимущества: нет необходимости модифицировать внутренний код подпроекта
Недостатки: переход между проектами должен полагаться на роднойhistory
объект
- использовать
hash
различать подпроекты
Таким образом, основной проект и подпроект будут совместно выполнять маршрутизацию, например:
-
/#/vue/home
: будет загружатьсяvue
подпроектhome
страницу, но на самом деле получить доступ только к этому подпроектуhome
Полный маршрут для страницы/#/vue/home
-
/#/react/about
: будет загружатьсяreact
подпроектabout
страницу, опять же, чтобы получить доступ к этому подпроектуabout
Полный маршрут для страницы/#/react/about
-
/#/about
: загрузит основной проектabout
страница
Практика заключается в настройкеactiveRule
:
const getActiveRule = hash => location => location.hash.startsWith(hash);
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: getActiveRule('#/app-vue-hash'),
},
])
Затем вам нужно добавить этот префикс ко всем маршрутам подпроекта или установить корневой маршрут подпроекта с этим префиксом.
const routes = [
{
path: '/app-vue-hash',
name: 'Home',
component: Home,
children: [
// 其他的路由都写到这里
]
}
]
Если подпроект является новым проектом, все в порядке.Если это старый проект, влияние все еще относительно велико.Маршрутизация в подпроекте скачет (<router-link>
,router.push()
,router.repace()
) при использованииpath
, вам нужно изменить, вы должны добавить этот префикс, если вы используетеname
Перейти, не нужно менять:router.push({ name: 'user'})
.
Плюсы: переход между всеми проектами можно использовать напрямуюrouter
объект или<router-link>
, нет необходимости полагаться на объект маршрутизации родительского проекта или собственныйhistory
объект
Недостатки: Навязчивые изменения в подпроектах, никакого влияния, если это совершенно новый проект.
Маршрутизация основного проектаhash
режим и подпроекты имеютhistory
маршрутизация по шаблону
Главный элементhash
режиме переход между подпроектами возможен только с помощью родногоhistory
объект, мы можем либо использоватьpath
также можно использоватьhash
Чтобы различать подпроекты:
- использовать
path
различать подпроекты
с основным проектомhistory
Разницы особой нет, плюсы и минусы одни и те же.
-
/vue-hash/#/home
: будет загружатьсяvue
подпроектhome
страница -
/vue-history/about
: будет загружатьсяvue-history
подпроектabout
страница -
/#/about
: загрузит основной проектabout
страница
- использовать
hash
различать подпроекты
Это на самом деле не очень хорошо и немного нестандартно, но тоже можно использовать:
-
/home/#/vue
: будет загружатьсяvue
подпроектhome
страница -
/#/vue-hash/about
: будет загружатьсяvue-hash
подпроектabout
страница -
/#/about
: загрузит основной проектabout
страница
Достоинства: нет
Минусы: даhash
Подпроекты являются инвазивными модификациями и не имеют никакого эффекта, если они являются совершенно новыми проектами.
Суммировать
основной проект свернутhash
а такжеhistory
Можно использовать все режимы, каждый со своими преимуществами и недостатками, в зависимости от ситуации.
Совместное использование компонентов между проектами
Совместное использование компонентов, желательноnpm
метод package, но если private не развернутnpm
, проект также предполагает конфиденциальность и не может быть размещен вGitHub
,илиБизнес-компоненты с даннымиРазделяя, вы можете рассмотреть следующие способы.
Совместное использование компонентов между родительским и дочерним проектами
Поскольку основной проект будет загружен первым, а затем будут загружены подпроекты, поэтому обычно подпроект повторно использует компоненты основного проекта (случай, когда основной проект повторно использует подпроект, будет обсуждаться позже).
Практика также очень проста: когда загрузится основной проект, монтируйте компонент вwindow
, подпроекты могут быть зарегистрированы напрямую.
Основной файл входа в проект:
import HelloWorld from '@/components/HelloWorld.vue'
window.commonComponent = { HelloWorld };
Подпроекты используют напрямую:
components: {
HelloWorld: window.__POWERED_BY_QIANKUN__ ? window.commonComponent.HelloWorld :
import('@/components/HelloWorld.vue'))
}
Совместное использование компонентов между подпроектами (слабая зависимость)
Что такое слабая зависимость? Даже в самом подпроекте есть этот компонент.Когда другие подпроекты были загружены, он повторно использует чужие компоненты.Если другие подпроекты не загружены, он использует свой собственный компонент.
Применимый сценарий — избежать повторной загрузки компонентов, этот компонент может быть не глобальным, а использоваться только определенной страницей. Это делается в три шага:
-
Поскольку глобальные переменные не являются общими для подпроектов, основной проект предоставляет глобальную переменную для хранения компонентов через
props
Передано подпроектам, которым необходимо совместно использовать компоненты. -
Подпроект получает эту переменную и монтирует ее в
window
начальство
export async function mount(props) {
window.commonComponent = props.data.commonComponent;
render(props.data);
}
- Общие компоненты в подпроектах записываются как асинхронные компоненты.
components: {
HelloWorld: () => {
if(!window.commonComponent){
// 独立运行时
window.commonComponent = {};
}
const HelloWorld = window.commonComponent.HelloWorld;
return HelloWorld || (window.commonComponent.HelloWorld =
import('@/components/HelloWorld.vue'));
}
}
вотbug
: Стили общих компонентов не загружаются при переключении туда и обратно. Почувствуй этоbug
То же, что и "Подпроект переходит на главную страницу проекта, стили основного проекта не загружаются"bug
, хорошего решения пока нет.
Другой подпроект также может быть написан таким же образом.Пока подпроект загружается один раз, другой проект может быть повторно использован напрямую без повторной загрузки.
Совместное использование компонентов между подпроектами (сильная зависимость)
В отличие от слабых зависимостей, у самого подпроекта нет этого компонента, поэтому другой подпроект должен быть загружен, прежде чем он сможет получить этот компонент.
Так как же обеспечить порядок загрузки между подпроектами? Решение: Прежде чем подпроект будет использовать этот компонент, вручную загрузите другой подпроект, чтобы убедиться, что компонент может быть получен (в случае разрешения загрузка не будет выполнена без разрешения).
qiankun
также можно использоватьloadMicroApp
вручную загружать подпроекты. Основные шаги заключаются в следующем:
- Точно так же, поскольку глобальные переменные не являются общими для подпроектов, основной проект предоставляет глобальную переменную для хранения компонентов через
props
для подпроектов, использующих компонент, а также прохождениеloadMicroApp
функция прошла.
const commonComponent = {};
registerMicroApps(
[
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: '/app-vue-hash',
props: { data : { loadMicroApp, commonComponent } }
},
],
)
- Подпроекты подключают функции загрузки и общедоступные переменные к глобальному
export async function mount(props) {
window.commonComponent = props.data.commonComponent;
window.loadMicroApp = props.data.loadMicroApp;
render();
}
- Подпроект вручную загружает подпроект, предоставляющий компонент, на странице, которой необходимо использовать компонент, и когда он загружается, вы можете получить компонент.
Здесь следует отметить: поскольку подпроекты, предоставляющие компоненты, предоставляютсяloadMicroApp
загружены, поэтому общедоступные переменные, в которых хранятся компоненты, должны бытьloadMicroApp
передай. Также: Загрузка подпроектов требует предоставления контейнера вAPP.vue
Предоставьте скрытый контейнер.
const app = window.loadMicroApp({
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer2',
props: { data: { commonComponent: window.commonComponent } }
})
await app.mountPromise;
Из-за сильной зависимости компонентов подпроекты здесь не могут работать независимо.Здесь есть два варианта регистрации компонентов:
- Одним из них является использование асинхронных компонентов:
components: {
HelloWorld: async () => {
// 这个 app 最好写成当前页的全局变量
// 因为卸载当前页时需要调用他的 `unmount` 来卸载子项目
const app = window.loadMicroApp({
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer2',
props: { data: { commonComponent: window.commonComponent } }
})
await app.mountPromise;
return window.commonComponent.HelloWorld
}
}
- Второй — динамическая регистрация, первое использование
v-if
чтобы скрыть метку:
<HelloWorld v-if="loadingEnd"/>
async created() {
const app = window.loadMicroApp({
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer2',
props: { data: { commonComponent: window.commonComponent } }
})
await app.mountPromise;
Vue.component('HelloWorld', window.commonComponent.HelloWorld)
this.loadingEnd = true;
},
vue
В документации описано, как обрабатывать состояние загрузки асинхронных компонентов:Обработка состояния загрузки
- Другой подпроект разделяет компоненты, следует отметить, что
loadMicroApp
К роутингу это не имеет никакого отношения, поэтому общие компоненты нужно монтировать в публичные переменные в файле входа, а не на странице роутинга.
Если общий компонент и его подкомпоненты не зависят отstore
а такжеi18n
Дождитесь глобального плагина, вот одинпредположениеОбработать: не создавать экземплярVue
, только для монтажа компонентов.
export async function mount(props) {
// 如果有commonComponent变量,说明是另一个子项目通过 loadMicroApp 加载的
// 他此时只需要挂载组件
if(props.data.commonComponent){
props.data.commonComponent.HelloWorld = HelloWorld;
}else{
// 没有 commonComponent 变量,说明是主项目通过 registerMicroApps 加载的
// 当让这里只是一个简单的判断,也可以传递其他参数判断
render();
}
}
Если этот компонент и его подкомпоненты зависят отstore
а такжеi18n
Для глобальных плагинов вам нужно вернуть функцию, а при вызове выполнить функцию напрямую:
export async function mount(props) {
// 如果有commonComponent变量,说明是另一个子项目通过 loadMicroApp 加载的
// 他此时只需要挂载组件
if(props.data.commonComponent){
const createHelloWorld = container => new Vue({
el: container,
store,
i18n,
render: h => h(HelloWorld)
});
props.data.commonComponent.createHelloWorld = createHelloWorld;
}else{
// 没有 commonComponent 变量,说明是主项目通过 registerMicroApps 加载的
// 当让这里只是一个简单的判断,也可以传递其他参数判断
render();
}
}
Родительский проект, повторно использующий компоненты подпроекта, также применяется в случае, когда компоненты являются общими для подпроектов. Если вы хотите реализовать «виджеты» (некоторые с диаграммами данных) для основного проекта, чтобы использовать подпроекты, вам необходимо использовать схему строгой зависимости.
гнездование цянькуня
Во-первых, поставитьqiankun
Проект может быть изменен в соответствии с требованиями подпроекта, прежде чем к нему можно будет получить доступ.Основные изменения заключаются в следующем:
- Измените конфигурацию упаковки, чтобы разрешить междоменное и
umd
- Измените корневой идентификатор, не используйте
#app
- Измените файл маршрутизации и создайте экземпляр маршрута в файле записи.
- Исправлять
public-path
документ - Измените входной файл на
qiankun
Выставлен требуемый жизненный цикл
Вариант 1: подпроект выполняется сам по себеqiankun
Пример
Существующие проблемы:
- Подпроекты не могут судить о том, работают ли они независимо или интегрированы на основе существующей информации.
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
Поскольку сам подпроект также являетсяqiankun
проект, поэтому автономная среда выполненияwindow.__POWERED_BY_QIANKUN__
дляtrue
, при интегрировании илиtrue
.
Решение: определить еще одну глобальную переменную в файле ввода основного проекта.window.__POWERED_BY_QIANKUN_PARENT__ = true;
, используйте эту переменную, чтобы определить, интегрирована ли она или работает независимо
- Изменение файла записи подпроекта
Основные моменты, на которые следует обратить внимание:
- При переключении подпроектов избегайте дублирования регистрации дочерних проектов,
- Поскольку подпроект будет введен с префиксом, маршрут проекта-внука также должен добавить этот префикс.
- Помните о конфликтах контейнеров, подпроекты и проекты-внуки используют разные контейнеры
let router = null;
let instance = null;
let flag = false;
function render() {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN_PARENT__ ? '/app-qiankun' : '/',
mode: 'history',
routes,
});
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();
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount('#appQiankun');
if(!flag){
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: window.__POWERED_BY_QIANKUN_PARENT__ ? '/app-qiankun/app-vue-hash' : '/app-vue-hash',
props: { data : { store, router } }
},
{
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer',
activeRule: window.__POWERED_BY_QIANKUN_PARENT__ ? '/app-qiankun/app-vue-history' : '/app-vue-history',
props: { data : store }
},
]);
start();
flag = true
}
}
if (!window.__POWERED_BY_QIANKUN_PARENT__) {
render();
}
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
render();
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
-
history
Внучатый проект шаблонной маршрутизацииbase
Исправлять
base: window.__POWERED_BY_QIANKUN_PARENT__ ? '/app-qiankun/app-vue-history' :
(window.__POWERED_BY_QIANKUN__ ? '/app-vue-history' : '/'),
- Изменение конфигурации упаковки
После завершения вышеуказанных операций вы можете поместить это в основной проект.qiankun
Подпроект загружается, но щелкнуть его внук, ошибка, жизненный цикл не может быть найден.
Измените конфигурацию упаковки внучатого проекта:
- library: `${name}-[name]`,
+ library: `${name}`,
Затем перезапустите его.
Причина в том,qiankun
Возьмите жизненный цикл подпроекта и отдайте приоритет последнему подключенному к подпроекту, когда он работает.window
По переменной, если это не функция жизненного цикла, то согласноappName
Выбирать. Позволятьwebpack
изlibrary
соответствие стоимостиappName
Вот и все.
Вариант 2: Основной проект будетqiankun
Функция регистрации передается подпроекту
Основные шаги такие же, как и выше, но вотbug
: внучатый проект не загружается. маршрутизация/app-qiankun
нагрузкаqiankun
Подпроекты, проектные проекты Grandson зарегистрированные подпроекты/app-qiankun/app-vue-hash
, но этоqiankun
Подпроект может загружаться нормально, а дочерний проект не загружается и об ошибке не сообщается.Я чувствую, что этоqiankun
один изbug
, два проекта имеют общую часть префикса маршрутизации, а проект с более длинным путем не загружается.
Если проект внука не согласенqiankun
Если подпроекты разделяют префикс маршрутизации, они могут быть загружены нормально, поэтому этот практический сценарий имеет тенденцию:Вложенные подпроекты регистрируются как родственный подпроект, прямое использование основного проекта контейнера, которые разделяют функцию регистрации основного проекта, и эти внучатые проекты сами являются подпроектами основного проекта.
qiankun
Код, когда подпроект регистрирует подпроект, выглядит следующим образом:
if(!flag){
let registerMicroApps = parentData.registerMicroApps;
let start = parentData.start;
if(!window.__POWERED_BY_QIANKUN_PARENT__){
const model = await import('qiankun');
registerMicroApps = model.registerMicroApps;
start = model.start;
}
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: window.__POWERED_BY_QIANKUN_PARENT__ ? '#appContainerParent' : '#appContainer',
activeRule: '/app-vue-hash',
props: { data : { store, router } }
},
{
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: window.__POWERED_BY_QIANKUN_PARENT__ ? '#appContainerParent' : '#appContainer',
activeRule: '/app-vue-history',
props: { data : store }
},
]);
start();
flag = true;
}
qiankun
Использовать сводку
- Наиболее распространенные проблемы: Иногда при обновлении страницы сообщается об ошибке, и контейнер не может быть найден.
Решение 1. В компонентеmounted
Регистрация и запуск циклаqiankun
Решение 2:new Vue()
После этого подождитеDOM
После загрузки зарегистрируйтесь и начнитеqiankun
const vueApp = new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
vueApp.$nextTick(() => {
//在这里注册并启动 qiankun
})
- Мои предыдущие заботы: все
js
сценарий иcss
Все файлы кэшируются в памяти. Не приведет ли слишком много подпроектов к зависанию браузера?
пилаissue
Ответ автора области:
После повторного использования зависимостей основного проектаjs
а такжеcss
объем в2M - 5M
Так что в принципе не беспокойтесь об этом.
-
qiankun
Запускайте несколько приложений одновременноjs
Обработка песочницы
Существуют два поддержания одновременно, а добавлены две глобальные переменныеwindow.a
, как сделать так, чтобы эти два могли работать одновременно, не мешая друг другу?
Усыновленныйproxy
После проксирования изменения глобальных переменных всех подприложений генерируются в закрытии и фактически не будут записываться обратно вwindow
Таким образом, можно избежать загрязнения между несколькими экземплярами.
конец
Если есть какие-либо проблемы или ошибки в статье, пожалуйста, укажите на это, спасибо!