
image.png
上圖描述了qiankun微前端的運(yùn)行過(guò)程,父子應(yīng)用之間可以獨(dú)立運(yùn)行.沒(méi)有子應(yīng)用,不影響父應(yīng)用的運(yùn)行.沒(méi)有父應(yīng)用,子應(yīng)用也是可以獨(dú)立運(yùn)行.
qiankun官網(wǎng)描述步驟非常詳細(xì):https://qiankun.umijs.org/zh/guide/getting-started
父應(yīng)用(使用的是vue2)
- 安裝qiankun,npm i qiankun -S
- 在vue組件注冊(cè)(不一定是vue組件,main.js中也可以)
PointsStore.vue:
<template>
<div class="PointsStore">
<div id="pointStore"></div><!--子應(yīng)用在父應(yīng)用中的位置-->
</div>
</template>
<script>
import { registerMicroApps, start, initGlobalState } from 'qiankun';
export default {
...
watch: {
language () {
// 數(shù)據(jù)變化時(shí),給子應(yīng)用傳遞數(shù)據(jù)
this.actions && this.actions.setGlobalState(data);
}
},
mounted() {
const initState = {};
// action用來(lái)和子應(yīng)用通信,每次mounted的時(shí)候都要初始化一遍
this.actions = initGlobalState(initState);
if (!window.qiankunStarted) { // 只需注冊(cè)一次子應(yīng)用
window.qiankunStarted = true;
// 注冊(cè)子應(yīng)用
registerMicroApps([
{
name: 'pointStore', // app的名稱
entry: 'http://localhost:8081/', // 加載子應(yīng)用的入口url
container: '#pointStore', // 對(duì)應(yīng)template中的<div id="pointStore"></div>
activeRule: '/point-store/', // 激活子應(yīng)用的路由
props: {
token: 'xx' // 向子應(yīng)用傳遞的數(shù)據(jù)
}
}
]);
start();
}
},
beforeDestroy() {
// 組件銷(xiāo)毀的時(shí)候不要忘了offGlobalStateChange.否則會(huì)出現(xiàn)監(jiān)聽(tīng)不到數(shù)據(jù)的情況
if (this.actions && this.actions.offGlobalStateChange) {
this.actions.offGlobalStateChange();
}
}
};
</script>
- router.js
{
path: 'point-store/*', // 重要,注意增加'/*',路由匹配到point-store/*時(shí),都加載PointsStore.vue
name: 'PointsStore',
component: () => import('../views/PointsStore.vue'),
},
微應(yīng)用(使用的是vue3)
qiankun官網(wǎng)說(shuō)在應(yīng)用入口導(dǎo)出生命周期鉤子,但是quasar的入口不是main.ts,入口文件是quasar自動(dòng)生成的,quasar自動(dòng)生成文件夾:.quasar,其中包含一個(gè)項(xiàng)目入口文件client-entry.js
- 新增入口文件
$ quasar new boot micro-lifeCycle.js
參考:https://next.quasar.dev/quasar-cli/boot-files#usage-of-boot-files
- micro-lifeCycle.js如下:
import { boot } from 'quasar/wrappers';
// 重要:由于需要在mount的時(shí)候需要重新實(shí)例化app(即 new Vue),但是quasar應(yīng)用不是通過(guò)new Vue()實(shí)現(xiàn)的,而是調(diào)用.quasar/client-entry.js內(nèi)的方法,所以根據(jù).quasar/*新建了src/quasar-init/*
// src/quasar-init/*的內(nèi)容和./quasar/*的內(nèi)容幾乎是一致的,區(qū)別:src/quasar-init/client-entry導(dǎo)出了init方法
import { init } from 'src/quasar-init/client-entry';
import * as Types from 'src/store/consts';
// 如果該應(yīng)用是作為子應(yīng)用運(yùn)行
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; // 參考:https://qiankun.umijs.org/zh/faq#a-%E4%BD%BF%E7%94%A8-webpack-%E8%BF%90%E8%A1%8C%E6%97%B6-publicpath-%E9%85%8D%E7%BD%AE
render();
}
class Actions {
// 默認(rèn)值為空 Action
actions = {
onGlobalStateChange: () => { },
setGlobalState: () => { }
};
/**
* 設(shè)置 actions
*/
setActions(actions) {
this.actions = actions;
}
/**
* 映射
*/
onGlobalStateChange(...args) {
return this.actions.onGlobalStateChange(...args);
}
/**
* 映射
*/
setGlobalState(...args) {
return this.actions.setGlobalState(...args);
}
}
const actions = new Actions();
// render第一次和第二次被調(diào)用是: if (window.__POWERED_BY_QIANKUN__) { render(); }, // props為{}
// 后面被調(diào)用是微前端生命周期鉤子的mount: async function mount(props) {render(props);} // props不再事空對(duì)象,有container屬性和值
function render(props = {}) {
const { container } = props; // props來(lái)自父應(yīng)用,container是注冊(cè)子前端時(shí),設(shè)置的container
// container有值,表示子應(yīng)用找到落腳點(diǎn)了,需要重新初始化;
// init方法相當(dāng)于執(zhí)行了一遍./quasar/client-entry.js
if (container) {
init();
// action的作用:和主應(yīng)用通信
actions.setActions(props);
actions.onGlobalStateChange(state => {
const { xxx } = state;
// todo something
}, true);
}
}
async function bootstrap() {
console.log('vue app bootstraped');
}
/**
* 應(yīng)用每次進(jìn)入都會(huì)調(diào)用 mount 方法,通常我們?cè)谶@里觸發(fā)應(yīng)用的渲染方法
*/
async function mount(props) {
console.log('vue app mount');
render(props); // 相當(dāng)于new Vue
}
/**
* 應(yīng)用每次 切出/卸載 會(huì)調(diào)用的方法,通常在這里我們會(huì)卸載微應(yīng)用的應(yīng)用實(shí)例
*/
async function unmount() {
console.log('vue app unmount');
if (window._POINT_STORE_APP_INSTANCE) {
window._POINT_STORE_APP_INSTANCE.unmount(); // 一定要卸載
}
}
/**
* 可選生命周期鉤子,僅使用 loadMicroApp 方式加載微應(yīng)用時(shí)生效
*/
async function update() {
console.log('update props');
}
export default boot(({ app, store }) => {
// 如果有實(shí)例,unmount
if (window._POINT_STORE_APP_INSTANCE) {
window._POINT_STORE_APP_INSTANCE.unmount();
}
window._POINT_STORE_APP_INSTANCE = app;
window._POINT_STORE_STORE = store; // store保存到window下,如果有用到store的話
})
export { bootstrap, mount, unmount, update }
props:

image.png
- src/quasar-init/client-entry.js
和.quasar/client-entry.js差不多,區(qū)別:
// src/quasar-init/client-entry.js
// 封裝createQuasarApp且導(dǎo)出
export function init() {
createQuasarApp(createApp)
.then(app => {
return Promise.all([
import(/* webpackMode: "eager" */ 'boot/i18n'),
import(/* webpackMode: "eager" */ 'boot/axios'),
import(/* webpackMode: "eager" */ 'boot/initApp.ts'),
import(/* webpackMode: "eager" */ 'src/boot/micro-lifeCycle.js')
]).then(bootFiles => {
const boot = bootFiles
.map(entry => entry.default)
.filter(entry => typeof entry === 'function')
start(app, boot)
})
})
}
// .quasar/client-entry.js
createQuasarApp(createApp)
.then(app => {
return Promise.all([
import(/* webpackMode: "eager" */ 'boot/i18n'),
import(/* webpackMode: "eager" */ 'boot/axios'),
import(/* webpackMode: "eager" */ 'boot/initApp.ts'),
import(/* webpackMode: "eager" */ 'boot/./micro-lifeCycle.js')
]).then(bootFiles => {
const boot = bootFiles
.map(entry => entry.default)
.filter(entry => typeof entry === 'function')
start(app, boot)
})
})
- 配置微應(yīng)用的打包工具
qiankun配置微應(yīng)用的打包工具
quasar.conf.js:
const packageName = require('./package.json').name;
module.exports = configure(function (/* ctx */) {
return {
boot: [
'i18n',
'axios',
'initApp.ts',
'./micro-lifeCycle.js'
],
build: {
...
chainWebpack(chain) {
// 設(shè)置入口文件,設(shè)置微前端的生命周期鉤子
chain
.entry('main')
.add('src/boot/micro-lifeCycle.js')
.end()
.output
.library('pointStore')
.libraryTarget('umd')
.jsonpFunction(`webpackJsonp_${packageName}`);
// 重要:參考https://qiankun.umijs.org/zh/faq; 如果不設(shè)置,那么字體庫(kù)找不到
chain.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
},
},
devServer: {
headers: {
'Access-Control-Allow-Origin': '*' // 重要
},
port: 8000,
...
}
...
}
});
-
目錄結(jié)構(gòu)
image.png
注意事項(xiàng)
- 基座應(yīng)用的全局樣式會(huì)影響子應(yīng)用,子應(yīng)用的不會(huì)影響基座應(yīng)用
- 子應(yīng)用的字體庫(kù)需要在build的時(shí)候使用loader字體配置:
chain.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
抖音上看到一個(gè)圖:

image.png
