微前端方案

介绍

微前端是一种类似于微服务的架构,是一种由多个独立交付的前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是一个完整的应用;或者将一些原本独立运行的应用,没有关联的应用糅合成一个整体。

它主要解决如下问题:

  • 将大型项目拆分 便于维护
  • 便于跨团队,跨部门合作
  • 技术栈无关&业务无关 React,Vue 不同团队可以选择适合自己的技术栈
  • 子应用只需要关系业务细节 诸如鉴权,登陆之类的交给主应用来做

现有的几种方案

Iframe

  • 由于iframe自身的特性,完全隔离了css和js,不会造成不同子应用css或js的污染,同时子应用接入的成本较低,不需要做太大的改造。
  • 缺点也很明显
    • 由于完全隔离,父子应用通信需要通过postMessage进行(仅能传递字符串)
    • 对于需要登陆的场景,需要思考如何将主应用的cookie透传到根域名不同的各个子应用中
    • 浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用
    • 如何收集子应用异常
    • 对于子应用(不居中)的一个弹框(居中)在主应用达不到预期效果

single-spa & Garfish & qiankun

介绍

single-spa是通过监听 url change 事件,在路由变化时匹配到渲染的子应用并进行渲染,同时single-spa要求子应用修改渲染逻辑并暴露出三个方法: bootstrapmountunmount ,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。

因为qiankun是基于single-spa进行封装,所以这些特点也被qiankun继承下来,并且需要对webpack配置进行一些修改,并扩展了沙箱隔离,预加载,缓存,路由,插件等能力。

Garfish在qiankun的基础上做了一下优化,如沙箱隔离使用new Function替换Eval。

但目前来看spa方案qiankun使用的人数较多,社区活跃度较高,且方案更为成熟。

主应用

主应用中注册子应用

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
42
43
44
45
46
import React, { useEffect } from 'react';
import Garfish from 'garfish';

//无需手动控制加载
Garfish.run({
basename: '/',
domGetter: '#container',
//子应用列表信息
apps: [
{
name: 'react',
activeWhen: '/react',
entry: 'http://localhost:3000', // html入口
},
{
name: 'vue',
activeWhen: '/vue',
entry: 'http://localhost:8080/index.js', // js入口
},
],
});

const App = () => {
useEffect(async () => {
//手动控制子应用加载
const app = await Garfish.loadApp('app1', {
entry: 'http://localhost:3001',//子应用资源入口
basename: '/',//子应用基础路径
domGetter: '#container',//默认挂载点
//子应用provider导出函数 生命周期方法中将接收到此数据
props: {
msg: 'hello world',
},
});
await app.mount();
});

return (
<div className='main'>
<h1>Main App</h1>
<div id="container"></div>
</div>
);
};

export default App;

子应用

1
2
3
4
5
6
7
8
9
// vite.config.js
export default defineConfig({
base: 'http://localhost:3000/',
server: {
port: 3000,
cors: true,
origin: 'http://localhost:3000',
},
});

机制

  • 生命周期
    • Mount
      • 创建app容器添加到dom上
      • 编译子应用的代码
      • 拿到子应用的provider
      • 调用app.options.beforeMount钩子
      • 调用provider.render
      • 将app set到Garfish.activeApps中
      • 调用app.options.afterMount钩子
      • 如果渲染失败app.mount会返回false
    • Unmount
      • 调用app.options.beforeUnmount钩子
      • 调用provider.destroy
      • 移除app的dom
      • app.display,app.mounted = false
      • 将app从Garfish.activeApps中delete
      • 调用 app.options.afterUnmount 钩子
      • 根据app.unmount判断是否卸载成功
    • Show
      • app的容器添加到dom上
      • 调用provider.render
      • app.dispaly = true
      • app.show判断是否渲染
    • Hide
      • 调用provider.destory
      • 将app从dom移除
      • app.dispaly = false
      • app.hide判断是否隐藏成功
  • 沙箱机制
  • 不使用iframe隔离的情况下保证样式,全局变量不冲突
    • Eval
    • New Function eval需要每次都执行 new Function是重复执行一个函数 因此选用New Function
      隔离方案
    • 快照沙箱
      在某个阶段给当前运行环境打上快照,需要的时候恢复快照。(运行时保存window所有的变量 卸载后把window缺失的变量存起来 等下一次调用恢复)
    • vm沙箱
    • 防止逃逸
  • 路由机制

micro-app

介绍

借鉴了WebComponent的思想,通过CustomElement结合自定义的 ShadowDom ,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,micro-app不需要像single-spaqiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置, 相对接入微前端成本较低

主应用

1
2
3
4
5
6
7
8
9
10
11
12
import ReactDOM from 'react-dom';
import './index.css';
import Router from './router';
import microApp from '@micro-zoe/micro-app'

//入口引入
microApp.start()

ReactDOM.render(
<Router />,
document.getElementById('root')
);

为子应用添加路由

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
import { Suspense } from 'react'
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import Home from './pages/home/home'
import config from './config'
function App() {
return (
// 设置主应用基础路由为main-react17(用于后续部署),则子应用基础路由(baseroute)为/main-react17/xxx
<BrowserRouter basename='/main-react17'>
<div id='router-container'>
<Switch>
<Route path="/" exact>
<Home />
</Route>
<Route path="/app-vue3_hxh">
<Suspense fallback={<div>Loading...</div>}>
<micro-app
name='appname-vue3'
url={`${config.vue3}/child/vue3/`}
baseroute='/main-react17/app-vue3_hxh'
></micro-app>
</Suspense>
</Route>
<Redirect to='/' />
</Switch>
</div>
</BrowserRouter>
)
}

export default App

子应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function mount () {
// __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由
history = createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL)
router = createRouter({
history,
routes,
}) as Router

// @ts-ignore
app = createApp(App)
app.use(router)
app.mount('#vue3-app')

console.log('微应用child-vue3渲染了')
}

webpack配置

1
2
3
4
5
6
// 开发环境
devServer: {
headers: {'Access-Control-Allow-Origin': '*'}
}
// 线上环境
Nginx代理

生命周期等其他概念与qiankun or Garfish类似。

Module Federation

WIP……


微前端方案
https://jing-jiu.github.io/jing-jiu/2022/08/05/探索/微前端方案/
作者
Jing-Jiu
发布于
2022年8月5日
许可协议