微前端--qiankun框架
qiankun 特性
- 基于 single-spa 封装,提供了更加开箱即用的 API。
- 技术栈无关,任意技术栈的应用均可使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
- HTML Entry 接入方式,让我们接入微应用像使用 iframe 一样简单。
- 样式隔离,确保微应用之间样式互相不干扰。
- JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
- 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。
qiankun是基于基座模式的,所以它必然有一个基座应用(主应用),来管理各个子应用的加载和卸载。
# 微前端需要解决的问题
应用的加载与切换( single-spa[路由问题、应用入口]、应用加载);
应用的隔离与通信( js隔离、css样式隔离、应用间通信)
- 路由问题
- single-spa是监听hashChange和popState来检测路由变化的,劫持原生的pushState和replaceState事件
- 应用入口
- 应用入口必须暴露出bootstrap、mount、unmount三个生命周期钩子函数且返回Promise,以保证single-spa可以注册回调函数。
- bootstrap--挂载前的准备, 在子应用的
bootstrap
钩子内手动创建这样一个节点并插入到基座应用
采用快照策略实现js隔离
在加载应用前,将window上的所有属性保存起来(即拍摄快照);等应用被卸载时,再恢复window上的所有属性。IE下的快照策略无法支持多实例模式。
qiankun通过import-html-entry,可以对html入口进行解析,并获得执行脚本的方法execScripts。qiankun引入该接口后,首先为该应用生成一个window的代理对象,然后将代理对象作为参数传入接口,以保证应用内的js不会对全局window造成影响。
css样式隔离
两种样式隔离方案,一种是基于shadowDom隔离,即为子应用的根节点创建一个shadow root。最终整个应用的所有DOM将形成一棵shadow tree。它内部所有节点的样式对树外面的节点无效。
缺陷:弹出框直接挂载到document.body下,脱离了shadow tree,它的样式仍会对全局造成污染。
sandbox: { strictStyleIsolation: true // experimentalStyleIsolation: true // 实验性方案,scoped方式 },
1
2
3
4- 另一种则是实验性的,思路类似于Vue中的scoped属性,给每个子应用的根节点添加一个特殊属性,用作对所有css选择器的约束。不支持@ keyframes,@ font-face,@ import,@ page(即不会被重写)
应用间通信
基于一个全局的globalState对象。这个对象由基座应用负责创建,内部包含一组用于通信的变量,以及两个分别用于修改变量值和监听变量变化的方法:setGlobalState和onGlobalStateChange。
// 在基座应用中初始化 import { initGlobalState, MicroAppStateActions } from 'qiankun'; const initialState = {}; const actions: MicroAppStateActions = initGlobalState(initialState); export default actions; // 加载子应用时通过props将actions传递到子应用内, actions.onGlobalStateChange(globalState, oldGlobalState){} // 监听全局状态变化 actions.setGlobalState(...); // 修改全局状态
1
2
3
4
5
6
7
8
9
# qiankun实践
安装:$ yarn add qiankun # 或者 npm i qiankun -S
# 在主应用中注册微应用
// 微应用入口配置 micro-app.js
const microApps: Array<any> = [{
name: 'module-app1',
entry: 'https://app1.example.com',
activeRule: '/app1',
container: '#root-view',
sandbox: { strictStyleIsolation: true } // 开启样式隔离
}, {
name: 'module-shop',
entry: 'https://app2.example.com',
activeRule: '/app2',
container: '#root-view',
sandbox: { strictStyleIsolation: true } // 开启样式隔离
}, ...];
export default microApps;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
如果微应用不是直接跟路由关联的时候,可手动加载微应用的方式:
import { loadMicroApp } from 'qiankun';
loadMicroApp({name:'app', entry:'//localhost:7100', container:'#yourContainer'});
2
现在希望在加载左侧菜单栏的时候去注册和启动qiankun
// menu.vue
<script>
import {registerMicroApps, start} from 'qiankun'; // 引入注册子应用和启动的接口函数
import microApps from './modules/micro-apps'; // 引入微应用入口配置
export default class PrimeMenu extends Vue {
async created () { // 注册一些全局生命周期钩子,如进行日志打印,不需要可不传
registerMicroApps(microApps, { beforeMount () {} });
start({ prefetch: true }); // 启动qiankun,并开启预加载
}
}
</script>
2
3
4
5
6
7
8
9
10
11
路由变化触发hashChange事件,而qiankun借助single-spa所绑定的监听事件,会侦测到地址变化而执行reroute函数;
随后qiankun通过import-html-entry提供的能力,去下载app1对应的html文件,并从中解析出所依赖的脚本和样式文件。
qiankun会将其封装在一个沙箱中,执行这些脚本并添加样式表,从而渲染出子应用。
window.history.pushState({}, '', '/app1/overview');
window.location.href = '/app1/overview';
2
# 微应用(以vue框架为例)
子应用内暴露出三个生命周期钩子函数:bootstrap、mount、unmount
let router = null; let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/',
mode: 'history',
routes,
});
instance = new Vue({router,store,render: (h) => h(App),})
.$mount(container ? container.querySelector('#app') : '#app');
}
if (window.__POWERED_BY_QIANKUN__) { // 在qiankun环境下,修正加载路径
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} else { render(); } // 独立运行时
export async function bootstrap() { console.log('[vue]vueapp bootstraped');}
export async function mount(props) {
console.log('[vue]props from main', props);
render(props);
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
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
# 打包配置修改(vue.config.js)
const { name } = require('./package');
module.exports = {
devServer: { headers: { 'Access-Control-Allow-Origin': '*' } },
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把微应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
2
3
4
5
6
7
8
9
10
11
umd格式的应用会向外暴露指定的生命周期钩子函数,便于single-spa解析。
jsonpFunction配置是webpack打包之后保存在window中的key,只需保证各个子应用不一致即可。