概念
微前端是指存在于浏览器中的微服务。它是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。
每个微前端都拥有独立的git仓库、package.json和构建工具配置。因此,每个微前端都拥有独立的构建进程和独立的部署/CI。这意味着,每个仓库能快速构建。
本文选取市面上主流微前端框架,对比分析选择一款合适的微前端框架,最后以 qiankun 为例,构建基础的微前端框架
技术方案
- 1、接入成本
- 2、隔离 (元素隔离、样式隔离、JS隔离)
- 3、多实例
- 4、性能(预加载、公共依赖服用)
- 5、子应用保活
- 6、子应用嵌套
- 7、通信(父子、子子通信)
- 8、技术栈无关
- 9、社区活跃程度
- 10、支持 esm
- 11、微组件
| 框架 | 实现原理 | 维护者 | 劣势 |
|---|---|---|---|
| iframe | 原生沙箱 | 浏览器原生 | 1、浏览器前进后退状态 2、通信麻烦 3、全屏弹窗 4、性能问题 5、无法做 seo |
| single-spa | 路由监听 | 国外 | 1、无通信 2、不支持 js 沙箱 3、样式冲突 4、性能问题 |
| qiankun | 继承 single-spa + 沙箱 | 阿里 | 1、样式隔离 2、微组件能力缺失 |
| 无界 | Iframe + WebComponent | 腾讯 | 1、不稳定 |
| emp | module-federation | 百度 | 1、不能支持多框架 2、社区不活跃 |
| micro-app | WebComponent + 沙箱 | 京东 | 1、社区不活跃 |
| garfishjs | 路由监听 + 沙箱 | 字节 | 1、社区不活跃 |
实践
主应用
import { registerMicroApps, start, initGlobalState } from 'qiankun';
const apps = [
{
"name": "star-demo",
"entry": "//localhost:9528",
"container": "#subapp-viewport",
"activeRule": ["/star"]
},
{
"name": "ghss-demo",
"entry": "//localhost:8081",
"container": "#subapp-viewport",
"activeRule": ["/ghss"]
}
];
const state = {
vue: "vuepath",
utils: "utilspath"
};
// 注册子应用
registerMicroApps(
apps,
{
beforeLoad: app => console.log('before load', app.name),
beforeMount: [
app => console.log('before mount', app.name),
],
},
);
const actions = initGlobalState(state)
// 监听state钩子
actions.onGlobalStateChange((state, prev) => {
console.log(state, prev);
});
// 重新设置state
actions.setGlobalState(state);
// 卸载监听state钩子
actions.offGlobalStateChange();
// 启动微前端
start({
prefetch: "all",
sandbox: {
strictStyleIsolation: true
}
})
子应用
// 以vue为例
// vue.config.js
{
devServer: {
headers: {
'Access-Control-Allow-Origin': '*'
},
},
configureWebpack: {
output: {
// 以package.json的name作为应用名称,需要唯一,如有问题,需要改成和主应用注册名字一致
library: `${name}-[name]`,
// 把子应用打包成 umd 库格式
libraryTarget: "umd",
jsonpFunction: `webpackJsonp_${name}`
}
},
}
// main.js 入口文件
const getBaseRoute = pathname => {
let baseRoute = pathname;
if (Array.isArray(pathname)) {
pathname.forEach(item => {
if (location.pathname.indexOf(item) > -1) {
baseRoute = item;
}
});
}
return baseRoute;
};
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
// 渲染页面
const render = props => {
const { pathname, container } = props || {};
instance = new Vue({
router, // router base设置
store,
render: h => h(App)
}).$mount(container ? container.querySelector("#app") : "#app");
}
// 生命周期钩子
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子
export async function bootstrap() {
console.log("app bootstraped");
}
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
isUnMount = false;
props.onGlobalStateChange((data) => {
console.log(data);
}, true);
render(props);
}
// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
export async function unmount() {
console.log("app unmount");
isUnMount = true;
if (instance && isUnMount) {
instance.$destroy();
instance = null;
}
}
// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
问题
样式隔离
- 严格沙箱隔离
- 命名空间隔离
- 自行隔离
svg 问题
在 vue 项目中,svg 注入目标为 body,需要更换插件
font-face 问题
开启 shadow dom模式不支持 font-face,所以需要在父应用加载
动态脚本加载问题
子应用资源 url 没有被正确解析,需要手动拼接 publicPath
全局弹窗问题
开启shadow dom模式,全局弹窗不能显示,需要添加劫持脚本,代理到子应用根节点
原理
JS 沙箱
- SnapshotSandbox 快照沙箱,只支持单实例
- LegacySandbox 遗留沙箱,只支持单实例
- ProxySandbox 代理沙箱,默认使用代理沙箱,支持多实例代理
样式隔离
- css module
- css namespace
- css scoped
- shadow dom
通信
- EventBus(window 挂载对象)
- url 传参
- props 传参
- 浏览器原生通信方式
登录
- 单点登录
总结
本次探讨了现有的微前端方案,通过比较各方优缺点,以 qiankun 为例进行实践,并对一些常见的问题分析解决,并了解微前端的核心原理沙箱、通信部分,并对微前端单点登录方案进行了探讨。