微前端是什么
一种由独立交付的多个前端应用组成整体的架构风格。具体的,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品。
微前端解决了什么问题
- 业务模块间日益加剧的耦合,可以按业务功能将一整块前端应用分解成一系列更小、更内聚的微前端应用
- 开发拆分、解耦,达到并行开发
- 新框架、新方案适应现有的工程环境(构建工具等),低成本引入新的技术实践,还允许低风险地替换产品局部功能
例如有这样一个应用,其中顶部的导航头、身份认证和左边的侧边栏在内的所有部分加起来就是我们的主应用,或者说是框架应用。而中间那个框也就是应用主体,应用 body 部分,它就是我们接入的一系列子应用,它可能是应用 a、应用 b 、应用 c 等等一系列的应用。我们把这一系列的应用一起集成到了一个大的控制台应用里面,这就是微前端技术的一个典型应用场景。
微前端和组件化的区别
组件化:应用拆分的粒度是组件
微前端:微前端则是将前端应用分解成能够独立开发、测试、部署的子应用,而在用户看来仍然是内聚的单个产品,粒度是应用。与技术栈无关
- 能优化发布流程
组件化:
采用微前端
2. 与技术栈无关,主应用可以接入任何技术栈的子应用
qiankun是什么?
qiankun就是一款由蚂蚁金服推出的比较成熟的微前端框架,基于 single-spa进行二次开发,用于将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。它有三个特点
- 简单,任何技术栈,js框架都可接入(做到随着时间的推移,当我们技术栈陈旧的时候,还是能够正常的接入我们的框架应用)
- 完备,基于 single-spa 封装了非常多的能力,例如样式隔离、沙箱、预加载
- 生产可用,在目前市场上已经有大量的实践经验,有稳定的生产体验。
如何接入qiankun
主流程
主应用配置
由于目前项目采用的是antd-pro内置了umi,这里介绍基于@umijs/plugin-qiankun插件的接入方式,qiankun原生接入也是类似的,但是要麻烦一些,例如本地跨域设置,子应用切换时卸载等等需要手动实现,新项目接入推荐使用umi
注册子应用
-
插件构建期配置子应用(左边)
-
运行时动态配置子应用(src/app.ts 里开启)(右边)
方式一适合在应用打包构建时就能确定入口url的情况,方式二则相反,例如路由需要动态获取。由于目前前端发布采用的是同一镜像可同时发布测试和生产环境,这样导致我们需要在运行时根据当前访问链接是测试或者生产来配置不同的子应用入口(测试或生产),所以我们采用方式二。经测试两种方案在性能上并无差别。
装载子应用(同样支持构建时和运行时两种方式)
使用路由绑定的方式
使用 <MicroApp /> 组件的方式
export function MyPage() {
return (
<div>
<div>
+ <MicroApp name="app1" />
</div>
</div>
)
}
子应用配置
插件注册
qiankun: {
slave: {},
},
};
插件会自动为你创建好 qiankun 子应用需要的生命周期钩子, bootstrap(应用加载之前) 、mount(应用render之前) 和 unmount(应用卸载之后) ,有这三个函数导出,我们的框架应用就可以知道如何加载这个子应用。
自定义逻辑
你想在生命周期期间加一些自定义逻辑,可以在子应用的 入口文件里导出 qiankun 对象,并实现每一个生命周期钩子
父子应用通讯
以mg的投放分析菜单为例我们需要给子应用传递例一些特定的mg的功能参数。
主应用使用,由于装载子应用采用的是路由绑定式,这里采用导出一个 useQiankunStateForSlave函数的方式,如果采用 microApp组件模式可以直接用props传递。
const [masterState, setMasterState] = useState({});
return {
masterState,
setMasterState,
};
}
子应用使用(中会自动生成一个全局 model,可以在任意组件中获取主应用透传的 props 的值)
function MyPage() {
const masterProps = useModel('@@qiankunStateFromMaster');
return <div>{JSON.stringify(masterProps)}</div>;
}
方式和props传递方式具体可查看文档
qiankun核心
JS 沙箱、CSS 样式隔离、应用 HTML 入口接入、应用通信、应用路由
应用html入口接入
解析子应用的逻辑是在 import-html-entry这个包中实现的。
html解析
当我们配置子应用的 entry 后,qiankun 会去通过 fetch 获取到子应用的 html 字符串
css处理
子应用的依赖的各种资源关系后,会去通过 fetch 获取 css,并将 css 全部以内联形式嵌入 html 模板中
js处理
1.fetch 获取外联的 js 字符串
2.创建一个匿名自执行函数包裹住获取到的 js 字符串,最后通过 eval 去创建一个执行上下文执行 js 代码
3.qiankun 是创建自己的执行上下文执行子应用的 js,因此在加载后的子应用中是看不到 js 资源引用的,仅有一个资源被执行替换的标识。
js沙箱隔离
JS 沙箱简单点说就是,主应用有一套全局环境window,子应用有一套私有的全局环境fakeWindow,子应用所有操作都只在新的全局上下文中生效,这样的子应用好比被一个个箱子装起来与主应用隔离,因此主应用加载子应用便不会造成JS 变量的相互污染、JS 副作用, 每个子应用的全局上下文都是独立的。
快照沙箱 - snapshotSandbox
快照沙箱就是在应用沙箱挂载和卸载的时候记录快照,在应用切换的时候依据快照恢复环境。
优点:兼容几乎所有浏览器
缺点:无法同时有多个运行时快照沙箱,否则在 window 上修改的记录会混乱,一个页面只能运行一个单实例微应用
代理沙箱 - proxySandbox
当有多个实例的时候,比如有A、B两个应用,A 应用就活在 A 应用的沙箱里面,B 应用就活在 B 应用的沙箱里面,A 和 B 无法互相干扰,这样的沙箱就是代理沙箱,这个沙箱的实现思路其实也是通过 ES6 的 proxy,通过代理特性实现的。
优点: 可同时运行多个沙箱,不会污染 window 环境
缺点:不兼容 ie,在全局作用域上通过 var或 function声明的变量和函数无法被代理沙箱劫持
主要踩坑经历
样式隔离
master:{
apps:[],
sandbox:{ experimentalStyleIsolation:true},
prefetch:'all'
}
}
{strictStyleIsolation?:boolean,experimentalStyleIsolation?: boolean }默认true。
当配置为 { strictStyleIsolation: true }时表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom节点,从而确保微应用的样式不会对全局造成影响。
坑点:子应用document. getElementById 等API因为dom节点在shadowDOM里面。所以没有办法获取正确数据
解决办法:
- 取到主应用挂载子应用的节点(规则是
window.qiankunShadowWrapperName = __qiankun_microapp_wrapper_for_${props.name}__,name 为qiankun 配置的apps 里子应用的name - 取到shadowRoot,赋值给window.shadowBoxDocument,后续子应用用到dom节点的地方用window.shadowBoxDocument代替。
但是这种办法无法解决子应用内部引用的外部资源包内部使用了document的问题。
最后采用的是sandbox:{ experimentalStyleIsolation:true}开启运行时的 scoped css 功能
但是这种形式不能避免被优先级比它高的样式覆盖的情况。例如important等
子应用加载的资源404
原因是 webpack 加载资源时未使用正确的 publicPath。 @umijs/plugin-qiankun将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量 __webpack_public_path =window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
window.INJECTED_PUBLIC_PATH_BY_QIANKUN 这个值是配置子应用时的entry的静态资源路径,如果我们子应用的js,css等静态资源和站点html分开就会存在找不到资源的问题。
解决办法
在子应用入口文件头部重新给 webpack_public_path 赋值为我们子应用正确的路径。
`你的静态资源路径` :
window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
qiankun 导致的跨域问题
由于 qiankun 是通过 fetch 去获取微应用的引入的静态资源的,所以必须要求这些静态资源支持。
当给子应用静态资源域名设置了跨域规则后,发现总是会偶发性的发生跨域错误。
产生问题的原因:缓存引起的问题。
解决办法:返回头配置Vary:Origin,不同的域名不会取同一份缓存
主应用和子应用同时开启mfsu报错
1.场景:需要主应用菜单控制子应用菜单路由,例如mg中 投放分析和看板都属于ezbi这个子应用,当主应用路由例如/analysis-explore/ck_common 在子应用菜单中存在并且layout不为false时会报如下错误
解决办法:
在子应用中为微前端调用方式添加特定路由,不要与菜单中的路由重合,设置layout:false
2.在使用mfsu时子应用不能配置publicpath 否则import-html-entry会报错
可以看到请求的是8001主应用的端口
这就是因为publicpath引起的,看看umi对于publicpath 配置项解释
当我们用默认 '/' 其实是相当于用了绝对url host:端口/,如果我们给public 赋值 /ezbi/ 是一个相对ulr,所以用mfsu时,主应用去fetch子应用资源的时候,会采用主应用的host:端口,如果我们要本地开发配置publicpath的话,可以配置成完整的url host:端口/path,但其实在本地开发没什么必要这样做。