qiankun 微前端的一些总结
date
Feb 7, 2023
slug
qiankun
status
Published
tags
微前端
summary
微前端的一些总结
type
Post
中 M微前端是基于 JS Entry和 qiankun这种区别比较大,今天有空看看 qiankun具体是怎么做。
一、路由特点
基于 Single-Spa,路由机制核心围绕「路由劫持 / 监听」实现子应用的加载、切换、卸载。主应用仅做路由监听和调度,子应用拥有独立的路由系统,本质是多应用的路由协同。
分 history和 hash两种模式,history中监听 popstate实现各应用间跳转,一般 会pushState/replaceState方法,达到监听所有history方法。
二、HTML Entry vs JS Entry
维度 | HTML Entry(HTML 入口) | JS Entry(JS 入口) |
核心形式 | 子应用暴露完整的 HTML 地址(如 http://localhost:3001/index.html) | 子应用暴露编译后的 JS 包地址(如 http://localhost:3001/main.js) |
加载逻辑 | 主应用加载子应用的 HTML,解析其中的 CSS/JS 资源并执行 | 主应用直接加载子应用的 JS 包,手动挂载 / 卸载子应用 |
隔离性 | 天然支持样式 / 脚本隔离(框架自动处理) | 需手动处理样式 / 脚本隔离,成本高 |
接入成本 | 极低(子应用几乎无需改造) | 较高(子应用需适配微前端规范) |
样式隔离
- scoped css,为每个 子应用添加样式前缀
- 如需更彻底的隔离,可手动开启 Shadow DOM 模式,qiankun 会自动将子应用挂载到 Shadow Root 中。
- 若子应用有依赖全局 DOM 的逻辑(如
document.body.appendChild),需适配 Shadow DOM(通过shadowRoot替代document);
- IE 浏览器不支持 Shadow DOM,需做好降级。
样式存在的一些问题
- 子应用全局样式仍污染主应用:
- 原因:qiankun 的自动前缀对
body/html等根选择器的处理有限; - 解决:子应用尽量避免写全局样式,或手动给全局样式添加前缀,如:
css
/* 子应用内推荐写法 */
.app1-wrapper body { background: #fff; }- Shadow DOM 下第三方库样式失效:
- 原因:第三方库样式挂载到
document.head,不在 Shadow DOM 内; - 解决:qiankun 提供了
appendCssLinkToShadowDom配置,自动将样式注入 Shadow Root:
javascript
运行
sandbox: {
strictStyleIsolation: true,
appendCssLinkToShadowDom: true, // 自动将子应用 CSS 注入 Shadow DOM
}- 主应用样式覆盖子应用:
- 原因:主应用样式优先级高于子应用自动前缀样式;
- 解决:子应用样式添加
!important(谨慎使用),或主应用给全局样式添加作用域限制。
三、js隔离
qiankun框架为了实现js隔离,提供了三种不同场景使用的沙箱,分别是快照沙箱、proxy代理沙箱
快照沙箱
快照沙箱是基于
diff来实现的,主要用于不支持window.Proxy的低版本浏览器,而且也只适应单个的子应用。激活沙箱时,将
window的快照信息存到windowSnapshot中, 如果modifyPropsMap有值,还需要还原上次的状态;激活期间,可能修改了window的数据;退出沙箱时,将修改过的信息存到modifyPropsMap里面,并且把window还原成初始进入的状态。代理沙箱
qiankun基于es6的Proxy实现了两种应用场景不同的沙箱,一种是legacySandbox(单例),一种是proxySandbox(多例)。因为都是基于Proxy实现的,所以都称为代理沙箱。proxySandbox不会污染全局window,支持多个子应用同时加载。四、应用间通信
一般的通信方案是通过 URL 及 CustomEvent 来处理,但在一些简单场景下,基于 props 的方案会更直接便捷:
主应用创建共享状态:
import { initGloabalState } from 'qiankun'; initGloabalState({ user: 'kuitos' });
微应用通过 props 获取共享状态并监听:
export function mount(props) { props.onGlobalStateChange((state, prevState) => { console.log(state, prevState); }); };