# 微前端
# 微前端解决的问题
- 不同团队(技术栈不同),同时开发一个应用
- 每个团队开发的模块都可以独立开发,独立部署
- 实现增量迁移
# 微前端解决方案
可以将一个应用划分成若干子应用,将子应用打包成一个个的模块。当路径切换时加载不同的子应用,这样每个子应用都是独立的,从而解决了前端协同开发的问题。 子应用需要暴露固定的钩子: bootstrap、mount、unmount。
- iframe、webComponent
- 2018年 Single-SPA 诞生了,single-spa 是一个用于前端微服务化的 Javascript 前端解决方案(本身没有处理样式隔离,js 执行隔离),实现了路由劫持和应用加载
- 2019年 qiankun 基于 Single-SPA 提供了开箱即用的 API (single-spa + sandbox + import-html-entry),接入简单
- 2020年 EMP 基于 webpack5 module federation,接入成本低,解决第三方依赖包问题
# iframe 的问题
- url 不同步,浏览器刷新 iframe url 状态丢失,后退前进按钮无法使用
- UI 不同步,DOM 结构不共享
- 全局上下文完全隔离,内存变量不共享
- 慢,每次子应用进入都是一次浏览器上下文重建,资源重新加载的过程
# single-spa 的缺陷
- 不能动态加载js文件
- 样式不隔离
- 没有js沙箱的机制
# CSS 隔离方案
子应用之间样式隔离:
- Dynamic StyleSStylesheet:动态样式表,当应用切换时移除老应用样式,添加新应用样式
主应用和子应用之间样式隔离:
- BEM(Block Element Modifier):约定项目前缀
- CSS-Module:打包时生成不冲突的选择器名
- Shadow DOM:真正意义上的隔离
- css-in-js
# js 沙箱
快照沙箱:
class SnapshotSandbox {
constructor() {
this.proxy = window;
this.modifyPropMap = {}; // 记录在window上的修改
this.active();
}
active() {
this.windowSnapshot = {};
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
this.windowSnapshot[prop] = window[prop];
}
}
Object.keys(this.modifyProp).forEach(p => {
window[p] = this.modifyMap[p];
})
}
inactive() {
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
if (window[prop] !== this.windowSnapshot[prop]) {
this.modifyProp[prop] = window[prop];
window[prop] = this.windowSnapshot[prop];
}
}
}
}
}
const sandbox = new SnapshotSandbox();
((window) => {
window.a = 1;
window.b = 2;
console.log(window.a, window.b);
sandbox.inactive();
console.log(window.a, window.b);
sandbox.active();
console.log(window.a, window.b);
})(sandbox.proxy)
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
代理沙箱:
class ProxySandbox {
constructor() {
const rawWindow = window;
const fakeWindow = {};
const proxy = new Proxy(fakeWindow, {
set(target, p, value) {
target[p] = value;
return true;
},
get(target, p) {
return target[p] || rawWindow[p];
}
});
this.proxy = proxy;
}
}
// test
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.a = 1;
((window) => {
window.a = 'hello';
console.log(window.a);
})(sandbox1.proxy);
((window) => {
window.a = 'world';
console.log(window.a);
})(sandbox2.proxy);
1
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
28
29
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
28
29
← Javascript 小程序 →