技术细节
本文中的技术细节主要参考乾坤为主,各框架内部实现各有差异,但总体方向上不会有太大的区别。
路由劫持
目前单页应用使用路由的方式分为两种:hash 模式,history 模式。这两种方式中都无法完全依赖单个原生事件来覆盖所有场景需要做一些兼容工作。
我们除了需要监听popstate、hashchange这两个事件外。在调用 pushState 以及 replaceState 也会造成路由变化,但不会触发事件,因此我们还需要去重写这两个函数。
渲染子应用
渲染子应用的第一步首先要加载子应用资源,我们可以通过应用注册时的 entry 字段拿到资源入口。这里入口资源加载分为两种方案:
- JS Enrty:需要将整个微应用打包成一个 JS 文件,对子应用改造较大,同时丧失了按需加载、首屏资源加载优化、css 独立打包等优化措施。
- HTML Entry:由 import-html-entry 库实现的,通过 http 请求加载指定地址的首屏内容即 html 页面,然后解析这个 html 模版得到 template, scripts , entry, styles。
{
template: 经过处理的dom结构,link、script 标签都被注释掉了,
scripts: [脚本的http地址 或者 { async: true, src: xx } 或者 代码块],
styles: [样式的http地址],
entry: 入口脚本的地址,要不是标有 entry 的 script 的 src,要不就是最后一个 script 标签的 src
}
import-html-entry 的加载过程:
a. fetch 请求获取 HTML 内容。
b. 解析 html 为相对资源路径拼接上正确的域名并找出 link、script 标签下对应的 script、css 资源。
c. 返回替换后的 template,及加载后的远程和 inline 的 js、css。
沙箱
- 记录用快照的方式在加载子应用前记录全局状态并在卸载时还原记录的状态。
- 通过 proxy(或 defineproperty)劫持对 window 的操作并创建一个 fakeWindow 对象,赋值操作都发生在 fakeWindow 对象下,取值时按照 fakeWindow -> window 的顺序依次查找。
CSS隔离
- Dynamic Stylesheet:在应用切出/卸载后,同时卸载掉其样式表即可,原理是浏览器会对所有的样式表的插入、移除做整个 CSSOM 的重构,从而达到 插入、卸载 样式的目的。这样即能保证,单应用场景下在一个时间点里,只有一个应用的样式表是生效的。
- 编译改造:提供一个 postcss 插件,在应用构建的时候给所有的样式都加上应用前缀包括应用公共库的 CSS。
- 域隔离:为每个 css 规则添加特定的前缀来起到隔离的作用。
参考资料
- [1] https://webpack.docschina.org/concepts/module-federation
- [2] https://wujie-micro.github.io/doc/guide/
- [3] https://github.com/facebook/react/issues/10422
- [4] https://juejin.cn/post/6885212507837825038
- [5] https://github.com/umijs/qiankun/blob/master/src/sandbox/patchers/css.ts
- [6] https://www.yuque.com/kuitos/gky7yw/gesexv?from=from_parent_mindnote
- [7] https://juejin.cn/post/7004661323124441102#heading-1
- [8] https://martinfowler.com/articles/micro-frontends.html
- [9] https://zeroing.jd.com/micro-app/docs.html#/
- [10] https://qiankun.umijs.org/zh/guide