微前端之qiankun

主应用

基座(主应用)负责导航的渲染和登录态的下发,为子应用提供一个挂载的容器div,基座应该保持简洁(qiankun官方demo甚至直接使用原生html搭建),不应该做涉及业务的操作

主应用注册微应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);

start();

微应用导出周期函数

微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}

/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
);
}

/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}

配置微应用打包工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const { name } = require('./package');
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};

webpack打包设置 webpack_public_path

1
2
3
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

vue3+vite 问题微前端解决方案

https://github.com/tengmaoqing/vite-plugin-qiankun/tree/master

微应用之间传值

  • props
1
2
3
4
5
6
7
8
9
10
{
name:'subapp',
entry:'//localhost:10200',
container:'#microApp',
activeRule:'/subapp',
props: {
test:'测试主应用传值',
parentActions:actions
}
}

手动加载或者注册子应用的时候把需要传的值带过去

  • actions 发布-订阅的设计模式

类似于watch监听 qiankun框架提供一套中间状态池 不同的子应用可以改变池子中的状态 监听状态的应用都会收到状态变化实现应用之间的传值

主应用构建状态池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {
initGlobalState
} from 'qiankun';

var state = {
num: 1
};
// 初始化 state
const actions = initGlobalState(state);
actions.onGlobalStateChange((stat, prev) => {
console.log('主应用检测到state变更:', stat, prev);
state.num = stat.num
});
// 你还可以定义一个获取state的方法下发到子应用
actions.getGlobalState = function () {
return state
}
export default actions;

props 下发到子应用

1
2
3
4
5
6
7
8
9
10
{
name:'subapp',
entry:'//localhost:10200',
container:'#microApp',
activeRule:'/subapp',
props: {
test:'测试主应用传值',
parentActions:actions
}
}

子应用中全局app(vue)中引用状态池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function render(props = {}) {
const { container } = props;

console.log('props :', props);
instance = new Vue({
router,
store,
// 挂载在根节点上
data(){
return {
test: props.test,
parentActions: props.parentActions,
}
},
render: (h) => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}

子应用需要更新的时候更新状态

1
2
3
4
5
6
changeMainData(){
console.log('=====> :',this.$root.parentActions);
let a = this.$root.parentActions.getGlobalState().num
a = a + 1
this.$root.parentActions.setGlobalState({num: a})
}

其他应用设置监听的话 就能得到状态变化

1
2
3
mounted() {
this.state = actions.getGlobalState().num
},

vite应用静态资源404

vit.config 文件添加下面配置

1
2
3
server: {
origin: 'http://localhost:5173', //项目baseUrl,解决主应用中出现静态地址404问题
},

封装全局数据

把全局下发的数据封装到通用的数据存储中 里面封装的store 来保存全局数据 这样不管数据是主应用下发的还是单独启动子应用获取 对于使用者来说 不需要关心 数据来自哪里 只要从封装的store上取数据就好了

子应用独立仓库

随着项目发展,子应用可能会越来越多,如果子应用和基座都集合在同一个git仓库,就会越来越臃肿。

若项目有CI/CD,只修改了某个子应用的代码,但代码提交会同时触发所有子应用构建,牵一发动全身,是不合理的。

同时,如果某些业务的子应用的开发是跨部门跨团队的,代码仓库如何分权限管理又是一个问题。

子项目之间的公共插件如何共享

巨无霸应用的公共依赖和公共函数被太多的页面使用,导致升级和改动困难,使用微前端可以让各个子项目独立拥有自己的依赖,互不干扰。而我们想要复用公共依赖,这与微前端的理念是相悖的。

所以我的想法是:父项目提供公共依赖,子项目可以自由选择用或者不用。