上图描述了qiankun微前端的运行过程,父子应用之间可以独立运行.没有子应用,不影响父应用的运行.没有父应用,子应用也是可以独立运行.
qiankun官网描述步骤非常详细:https://qiankun.umijs.org/zh/guide/getting-started
父应用(使用的是vue2)
- 安装qiankun,npm i qiankun -S
- 在vue组件注册(不一定是vue组件,main.js中也可以)
PointsStore.vue:
<template>
<div class="PointsStore">
<div id="pointStore"></div><!--子应用在父应用中的位置-->
</div>
</template>
<script>
import { registerMicroApps, start, initGlobalState } from 'qiankun';
export default {
...
watch: {
language () {
// 数据变化时,给子应用传递数据
this.actions && this.actions.setGlobalState(data);
}
},
mounted() {
const initState = {};
// action用来和子应用通信,每次mounted的时候都要初始化一遍
this.actions = initGlobalState(initState);
if (!window.qiankunStarted) { // 只需注册一次子应用
window.qiankunStarted = true;
// 注册子应用
registerMicroApps([
{
name: 'pointStore', // app的名称
entry: 'http://localhost:8081/', // 加载子应用的入口url
container: '#pointStore', // 对应template中的<div id="pointStore"></div>
activeRule: '/point-store/', // 激活子应用的路由
props: {
token: 'xx' // 向子应用传递的数据
}
}
]);
start();
}
},
beforeDestroy() {
// 组件销毁的时候不要忘了offGlobalStateChange.否则会出现监听不到数据的情况
if (this.actions && this.actions.offGlobalStateChange) {
this.actions.offGlobalStateChange();
}
}
};
</script>
- router.js
{
path: 'point-store/*', // 重要,注意增加'/*',路由匹配到point-store/*时,都加载PointsStore.vue
name: 'PointsStore',
component: () => import('../views/PointsStore.vue'),
},
微应用(使用的是vue3)
qiankun官网说在应用入口导出生命周期钩子,但是quasar的入口不是main.ts,入口文件是quasar自动生成的,quasar自动生成文件夹:.quasar,其中包含一个项目入口文件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的时候需要重新实例化app(即 new Vue),但是quasar应用不是通过new Vue()实现的,而是调用.quasar/client-entry.js内的方法,所以根据.quasar/*新建了src/quasar-init/*
// src/quasar-init/*的内容和./quasar/*的内容几乎是一致的,区别:src/quasar-init/client-entry导出了init方法
import { init } from 'src/quasar-init/client-entry';
import * as Types from 'src/store/consts';
// 如果该应用是作为子应用运行
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 {
// 默认值为空 Action
actions = {
onGlobalStateChange: () => { },
setGlobalState: () => { }
};
/**
* 设置 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第一次和第二次被调用是: if (window.__POWERED_BY_QIANKUN__) { render(); }, // props为{}
// 后面被调用是微前端生命周期钩子的mount: async function mount(props) {render(props);} // props不再事空对象,有container属性和值
function render(props = {}) {
const { container } = props; // props来自父应用,container是注册子前端时,设置的container
// container有值,表示子应用找到落脚点了,需要重新初始化;
// init方法相当于执行了一遍./quasar/client-entry.js
if (container) {
init();
// action的作用:和主应用通信
actions.setActions(props);
actions.onGlobalStateChange(state => {
const { xxx } = state;
// todo something
}, true);
}
}
async function bootstrap() {
console.log('vue app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
async function mount(props) {
console.log('vue app mount');
render(props); // 相当于new Vue
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
async function unmount() {
console.log('vue app unmount');
if (window._POINT_STORE_APP_INSTANCE) {
window._POINT_STORE_APP_INSTANCE.unmount(); // 一定要卸载
}
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
async function update() {
console.log('update props');
}
export default boot(({ app, store }) => {
// 如果有实例,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:
- src/quasar-init/client-entry.js
和.quasar/client-entry.js差不多,区别:
// src/quasar-init/client-entry.js
// 封装createQuasarApp且导出
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)
})
})
- 配置微应用的打包工具
qiankun配置微应用的打包工具
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) {
// 设置入口文件,设置微前端的生命周期钩子
chain
.entry('main')
.add('src/boot/micro-lifeCycle.js')
.end()
.output
.library('pointStore')
.libraryTarget('umd')
.jsonpFunction(`webpackJsonp_${packageName}`);
// 重要:参考https://qiankun.umijs.org/zh/faq; 如果不设置,那么字体库找不到
chain.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
},
},
devServer: {
headers: {
'Access-Control-Allow-Origin': '*' // 重要
},
port: 8000,
...
}
...
}
});
-
目录结构
注意事项
- 基座应用的全局样式会影响子应用,子应用的不会影响基座应用
- 子应用的字体库需要在build的时候使用loader字体配置:
chain.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end();
抖音上看到一个图: