本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
本文作者为 360 奇舞团前端开发工程师
微前端架构是为了在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用 (Frontend Monolith) 后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
微前端框架内的各个应用都支持独立开发部署、不限技术框架、支持独立运行、应用状态隔离但也可共享等特征。
本文会从框架的应用隔离实现方案、实战、优缺点三个方面探一探各个框架。帮助大家了解各个框架是如何使用,如何运行,从而能选出适合自己项目的微前端方案。
iframe
在没有各大微前端解决方案之前,iframe是解决这类问题的不二之选,因为iframe提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。
但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题:
url 不同步,浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
UI 不同步,DOM 结构不共享,弹窗只能在 iframe 内部展示,无法覆盖全局
全局上下文完全隔离,内存变量不共享,iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
慢,每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
single-spa
目前 (2024 年 4 月)github star 13k
Single-spa(https://github.com/single-spa/single-spa) 是最早的微前端框架,兼容多种前端技术栈;是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架;
简单来说就是一个聚合,使用这个库可以让你的应用可以 使用多个不同的技术栈(vue、react、angular 等等) 进行同步开发,最后使用一个公用的路由去实现完美的切换;
实现方案
Single-spa 实现了一套生命周期,开发者需要在相应的时机自己去加载对应的子应用。
它做的事情就是注册子应用、监听 URL 变化,然后加载对应的子应用 js,执行对应子应用的生命周期流程。
提供
registerApplication方法,用来注册子应用列表。提供了
activeWhen,由开发者指定路由满足条件时,激活(挂载)子应用的 js、css。js隔离由single-spa-leaked-globals实现,本质上就是在 mount A 子应用时,正常添加全局变量,比如 jQuery 的 $, lodash 的 _。在 unmount A 子应用时,用一个对象记录之前给 window 添加的全局变量,并把 A 应用里添加 window 的变量都删掉。下一次再 mount A 应用时,把记录的全局变量重新加回来就好了。css隔离:子应用和子应用之间通过single-spa-css插件提供的 css 生命周期函数,做到子应用 mount 时加载 css,子应用 unmount 时将 css 也 unmount 掉;而主应用与子应用之间可以通过PostCSSPrefix Selector 给样式自动加前缀的方式,或者Shadow DOM的形式去解决。
single-spa 实战
1. 主应用入口文件:
主要通过single-spa提供的registerApplication方法注册子应用,子应用需要指定加载子应用的方法、和路由条件。
import Vue from 'vue'import App from './App.vue'import router from './router'import { registerApplication, start } from 'single-spa'Vue.config.productionTip = false// 远程加载子应用function createScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script') script.src = url script.onload = resolve script.onerror = reject const firstScript = document.getElementsByTagName('script')[0] firstScript.parentNode.insertBefore(script, firstScript) })}// 记载函数,返回一个 promisefunction loadApp(url, globalVar) { // 支持远程加载子应用 return async () => { await createScript(url + '/js/chunk-vendors.js') await createScript(url + '/js/app.js') // 这里的return很重要,需要从这个全局对象中拿到子应用暴露出来的生命周期函数 return window[globalVar] }}// 子应用列表const apps = [ { // 子应用名称 name: 'app1', // 子应用加载函数,是一个promise app: loadApp('http://localhost:8081', 'app1'), // 当路由满足条件时(返回true),激活(挂载)子应用 activeWhen: location => location.pathname.startsWith('/app1'), // 传递给子应用的对象 customProps: {} }, { name: 'app2', app: loadApp('http://localhost:8082', 'app2'), activeWhen: location => location.pathname.startsWith('/app2'), customProps: {} }, { // 子应用名称 name: 'app3', // 子应用加载函数,是一个promise app: loadApp('http://localhost:3000', 'app3'), // 当路由满足条件时(返回true),激活(挂载)子应用 activeWhen: location => location.pathname.startsWith('/app3'), // 传递给子应用的对象,这个很重要,该配置告诉react子应用自己的容器元素是什么,这块儿和vue子应用的集成不一样,官网并没有说这部分,或者我没找到,是通过看single-spa-react源码知道的 customProps: { domElement: document.getElementById('microApp'), // 添加 name 属性是为了兼容自己写的lyn-single-spa,原生的不需要,当然加了也不影响 name: 'app3' } }]// 注册子应用for (let i = apps.length - 1; i >= 0; i--) { registerApplication(apps[i])}new Vue({ router, mounted() { // 启动 start() }, render: h => h(App)}).$mount('#app')2. 子应用导出文件
子应用需要安装
single-spa-react或者single-spa-vue,将子应用传递给
single-spa-react,得到子应用运行的生命周期,子应用将生命周期导出到全局,
在主应用可以获取子应用的生命周期函数
import React from 'react';import ReactDOM from 'react-dom';import './index.css'import { BrowserRouter, Link, Route } from 'react-router-dom'import singleSpaReact from 'single-spa-react'// 子应用独立运行if (!window.singleSpaNavigate) { ReactDOM.render(rootComponent(), document.getElementById('root'))}// 生命周期aconst reactLifecycles = singleSpaReact({ React, ReactDOM, rootComponent, errorBoundary(err, info, props) { return <div> This renders when a catastrophic error occurs </div> }})// 这里和vue不一样,props必须向下传递export const bootstrap = async props => { console.log('app3 bootstrap'); return reactLifecycles.bootstrap(props)}export const mount = async props => { console.log('app3 mount'); return reactLifecycles.mount(props);}export const unmount = async props => { console.log('app3 unmount'); return reactLifecycles.unmount(props)}// 根组件function rootComponent() { return <React.StrictMode> <BrowserRouter> <div> <Link to="/app3">Home</Link> | <Link to="/app3/about"> About</Link> <Route exact path="/app3" component={Home} /> <Route exact path="/app3/about" component={About} /> </div> </BrowserRouter> </React.StrictMode>}// home 组件function Home() { return <div> <h1>app3 home page</h1> </div>}// about 组件function About() { return <div> <h1>app3 about page</h1> </div>}3. 打包配置
将子应用导出模式设置为 umd
const package = require('./package.json')module.exports = { // 告诉子应用在这个地址加载静态资源,否则会去基座应用的域名下加载 publicPath: '//localhost:8082', // 开发服务器 devServer: { port: 8082 }, configureWebpack: { // 导出umd格式的包,在全局对象上挂载属性package.name,基座应用需要通过这个全局对象获取一些信息,比如子应用导出的生命周期函数 output: { // library的值在所有子应用中需要唯一 library: package.name, libraryTarget: 'umd' } }}4. 预览
可以看到它是动态加载的子应用的 js,并执行 js,将内容渲染到了主应用的盒子内。
框架优缺点
优点:
敏捷性 - 独立开发、独立部署,微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
技术栈无关,主框架不限制接入应用的技术栈,微应用具备完全自主权;
增量升级,在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
缺点
需要自己去加载子应用
不支持 Javascript 沙箱隔离,需要自己去使用
single-spa-leaked-globals之类的库去隔离不支持 css 隔离,需要自己使用
single-spa-css库或者postcss等去解决样式冲突问题无法预加载
qiankun
目前 (2024 年 4 月) github star 15.4k
阿里的qiankun 是一个基于 single-spa 的微前端实现库,孵化自蚂蚁金融,帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。
实现方案
single-spa 是基于
js-entry方案,而qiankun是基于html-entry及沙箱设计,使得微应用的接入 像使用 iframe 一样简单。主应用监听路由,加载对应子应用的 html,挂载到主应用的元素内,然后解析子应用的 html,从中分析出 css、js 再去沙盒化后加载执行,最终将子应用的内容渲染出来。
qiankun 实现样式隔离有两种模式可供开发者选择:
strictStyleIsolation: 这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。experimentalStyleIsolation: 当experimentalStyleIsolation被设置为 true 时,qiankun 会改写子应用所添加的样式,会为所有样式规则增加一个特殊的选择器规则,来限定其影响范围qiankun 实现 js 隔离,采用了两种沙箱,分别为基于 Proxy 实现的沙箱和快照沙箱,当浏览器不支持 Proxy 会降级为快照沙箱
Proxy 沙箱机制:
// 伪代码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 }}let sandbox1 = new ProxySandbox();let sandbox2 = new ProxySandbox();window.a = 1;// 伪代码((window) => { window.a = 'hello'; console.log(window.a) // hello})(sandbox1.proxy);((window) => { window.a = 'world'; console.log(window.a) // world})(sandbox2.proxy);快照沙箱
// 伪代码class SnapshotSandbox { constructor() { this.proxy = window; this.modifyPropsMap = {}; // 修改了那些属性 this.active(); // 调用active保存主应用window快照 } /**1. 初始化时,在子应用即将mount前,先调用active,保存当前主应用的window快照*/ active() { this.windowSnapshot = {}; // window对象的快照 for (const prop in window) { if (window.hasOwnProperty(prop)) { // 将window上的属性进行拍照 this.windowSnapshot[prop] = window[prop]; } } Object.keys(this.modifyPropsMap).forEach(p => { window[p] = this.modifyPropsMap[p]; }); } /** * 子应用卸载时,遍历当前子应用的window属性,和主应用的window快照做对比 * 如果不一致,做两步操作 * 1. 保存 不一致的window属性, * 2. 还原window */ inactive() { for (const prop in window) { // diff 差异 if (window.hasOwnProperty(prop)) { // 将上次拍照的结果和本次window属性做对比 if (window[prop] !== this.windowSnapshot[prop]) { // 保存修改后的结果 this.modifyPropsMap[prop] = window[prop]; // 还原window window[prop] = this.windowSnapshot[prop]; } } } }}qiankun 实战
1. 主应用入口文件
初始化主应用,并注册子应用
主应用入口文件初始化应用,注册子应用,注册子应用时支持传入子应用列表, 注册子应用时需要指明以下几个主要参数:
name: 微应用的名称,微应用之间必须确保唯一
entry: 子应用的访问链接。主应用会加载整个页面,例如 https://qiankun.umijs.org/guide/
container:需要挂载子应用的 DOM 元素
loader: 子应用未加载时的界面,一般为 loading
activeRule: 路由匹配规则
开启子应用 start(options)
options.prefetch此时可以选择是否预加载子应用。options.sandbox默认情况下的沙箱可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离。qiankun 提供了另外两种方式的隔离,供开发者选择:
strictStyleIsolation: 当配置为
{ strictStyleIsolation: true }时表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。experimentalStyleIsolation:当
{experimentalStyleIsolation: true}被设置,qiankun 会改写子应用所添加的样式为所有样式规则增加一个特殊的选择器规则来限定其影响范围。
import { registerMicroApps, start, initGlobalState } from 'qiankun';registerMicroApps([ { name: 'react app', // app name registered entry: '//localhost:7100', container: '#yourContainer', activeRule: '/yourActiveRule', }, { name: 'vue app', entry: { scripts: ['//localhost:7100/main.js'] }, container: '#yourContainer2', activeRule: '/yourActiveRule2', },]);// 通讯const { onGlobalStateChange, setGlobalState } = initGlobalState({ user: 'qiankun',});onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev));setGlobalState({ ignore: 'master', user: { name: 'master', },});/** * 设置默认进入的子应用 */setDefaultMountApp('/react16');/** * 启动应用 */start({ prefetch: true, // 预加载子应用 sandbox:{ strictStyleIsolation: true, // shadow dom的方式实现样式隔离 // experimentalStyleIsolation: true, //添加特殊的选择器的方式实现样式隔离 }});runAfterFirstMounted(() => { console.log('[MainApp] first app mounted');});2. 子应用导出生命周期钩子
子应用需要在自己的入口 js 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
import React from 'react';import ReactDOM from 'react-dom';import App from './App';import * as serviceWorker from './serviceWorker';function render(props) { const { container } = props; ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));}/*** 和主应用通讯*/function storeTest(props) { props.onGlobalStateChange((value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true); props.setGlobalState({ ignore: props.name, user: { name: props.name, }, });}if (!window.__POWERED_BY_QIANKUN__) { render({});}/** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */export async function bootstrap() { console.log('[react16] react app bootstraped');}/** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */export async function mount(props) { console.log('[react16] props from main framework', props); storeTest(props); render(props);}/** * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */export async function unmount(props) { const { container } = props; ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));}3. 配置打包工具:
为了让主应用能正确识别微应用暴露出来的一些全局信息和开发环境下的跨域兼容,在子应用(以 create-react-app 出来的 react 项目为例)安装 @rescripts/cli,并在子应用目录下新建. rescriptsrc.js,内容如下:
const { name } = require('./package');module.exports = { webpack: (config) => { config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; // 为了能通过window['app-name1']拿到子应用声明的生命周期 // webpack 5 需要把 jsonpFunction 替换成 chunkLoadingGlobal config.output.jsonpFunction = `webpackJsonp_${name}`; config.output.globalObject = 'window'; return config; }, devServer: (_) => { const config = _; config.headers = { 'Access-Control-Allow-Origin': '*', }; config.historyApiFallback = true; config.hot = false; config.watchContentBase = false; config.liveReload = false; return config; },};4. 预览
使用strictStyleIsolation:true方式进行样式隔离,会生成一个shadow dom,进行样式的完全隔离:
使用experimentalStyleIsolation:true的方式进行样式隔离,会在 css 选择器前添加特殊标识:
可以看到,qiankun会将子应用的 html 渲染到自定义的 container 中。 主应用加载的是子应用的 html,在解析子应用的 html 的过程中遇到 js 和 css 会载框架内进行沙盒处理,完成 css 和 js 的隔离,之后下载并执行,完成整个子应用的渲染过程。
框架优缺点
优点
html entry 的接入方式,不需要自己写 load 方法,而是直接写子应用的访问链接就可以。
提供 js 沙箱
提供样式隔离,两种方式可选
资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
社区活跃
umi 插件,提供了
@umijs/plugin-qiankun供umi应用一键切换成微前端架构系统 除了最后一点拓展以外,微前端想要达到的效果都已经达到。应用间通信简单,全局注入
路由保持,浏览器刷新、前进、后退,都可以作用到子应用
缺点
改造成本较大,从 webpack、代码、路由等等都要做一系列的适配
对 eval 的争议,eval 函数的安全和性能是有一些争议的:MDN 的 eval 介绍;
无法同时激活多个子应用,也不支持子应用保活
无法支持
vite等 ESM 脚本运行
wujie
目前(2024 年 4 月)github star 3.7k
wujie 是腾讯出品。基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等
实现方案
无界利用
iframe和webcomponent来搭建天然的 js 隔离沙箱和 css 隔离沙箱,利用 iframe 的 history 和主应用的 history 在同一个 top-level browsing context 来搭建天然的路由同步机制
支持以
fiber的形式执行 js,由于子应用的执行会阻塞主应用的渲染线程,当fiber设置为 true,那么 js 执行时采取类似react fiber的模式方式间断执行,每个 js 文件的执行都包裹在requestidlecallback中,每执行一个 js 可以返回响应外部的输入,但是这个颗粒度是 js 文件,如果子应用单个 js 文件过大,可以通过拆包的方式降低达到 fiber 模式效益最大化
wujie 是如何渲染子应用的?
wujie 跟 qiankun 一样,都是基于 html entry 加载的,但他们解析 html 的过程是不一样的。 qiankun 是直接解析并执行 js、css、html 的,而 wujie 则是先解析 html,提取出 script 脚本放入空的 iframe 中,提取出 css、html 放入到 web components 中,具体来说:
解析入口 HTML ,分别得到 script、css、模版 html
创建一个纯净的 iframe,为了实现应用间(iframe 间)通讯,无界子应用 iframe 的 url 会设置为主应用的域名(同域),因此 iframe 的 location.href 并不是子应用的 url。创建好后停止加载 iframe。
iframe 内插入 js,将抽离出来的 script 脚本,插到 iframe 中去,在 iframe 中执行子应用的 js
创建 web component,id 为子应用 id,将抽离出来的 html 插入。
由于
iframe内的 js 有可能操作 dom,但是iframe内没有 dom,随意wujie框架内对iframe拦截 document 对象,统一将 dom 指向shadowRoot,此时比如新建元素、弹窗或者冒泡组件就可以正常约束在shadowRoot内部。
wujie 实战
wujie 接入很简单,主应用可以让开发者以组件的方式加载子应用。子应用只需要做支持跨域请求改造,这个是所有微前端框架运行的前提,除此之外子应用可以不做任何改造就可以在无界框架中运行,不过此时运行的方式是重建模式。 子应用也可以配置保活、生命周期适配进入保活模式或单例模式。
1. 主应用入口文件
与其他框架一样,先配置子应用,
// main-react/index.jsimport "react-app-polyfill/stable";import "react-app-polyfill/ie11";import React from "react";import ReactDOM from "react-dom";import WujieReact from "wujie-react";import "./index.css";import App from "./App";import hostMap from "./hostMap";import credentialsFetch from "./fetch";import lifecycles from "./lifecycle";import plugins from "./plugin";const { setupApp, preloadApp, bus } = WujieReact;const isProduction = process.env.NODE_ENV === "production";bus.$on("click", (msg) => window.alert(msg));const degrade = window.localStorage.getItem("degrade") === "true" || !window.Proxy || !window.CustomElementRegistry;/** * 大部分业务无需设置 attrs * 此处修正 iframe 的 src,是防止github pages csp报错 * 因为默认是只有 host+port,没有携带路径 */const attrs = isProduction ? { src: hostMap("//localhost:7700/") } : {};/** * 配置应用,主要是设置默认配置 * preloadApp、startApp的配置会基于这个配置做覆盖 */setupApp({ name: "react16", url: hostMap("//localhost:7600/"), attrs, // 子应用iframe的src exec: true, // 预执行 fetch: credentialsFetch, // 自定义的fetch方法 plugins, /** 子应用短路径替换,路由同步时生效 */ prefix: { "prefix-dialog": "/dialog", "prefix-location": "/location" }, /** 子应用采用降级iframe方案 */ degrade, ...lifecycles,});setupApp({ name: "vue3", url: hostMap("//localhost:7300/"), attrs, exec: true, alive: true, // 子应用保活,state不会丢失 plugins: [{ cssExcludes: ["https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"] }], // 引入了的第三方样式不需要添加credentials fetch: (url, options) => url.includes(hostMap("//localhost:7300/")) ? credentialsFetch(url, options) : window.fetch(url, options), degrade, ...lifecycles,});if (window.localStorage.getItem("preload") !== "false") { preloadApp({ name: "react16", }); if (window.Proxy) { preloadApp({ name: "vue3", }); }}ReactDOM.render(<App />, document.getElementById("root"));引入子应用的地方直接以组件式的方式引入:
import React from "react";import hostMap from "../hostMap";import WujieReact from "wujie-react";import { useNavigate, useLocation } from "react-router-dom";export default function React16() { const navigation = useNavigate(); const location = useLocation(); const path = location.pathname.replace("/react16-sub", "").replace("/react16", "").replace("/",""); //// const react16Url = hostMap("//localhost:7600/") + path; const props = { jump: (name) => { navigation(`/${name}`); }, }; return ( // 单例模式,name相同则复用一个无界实例,改变url则子应用重新渲染实例到对应路由 <WujieReact width="100%" height="100%" url={react16Url} sync={!path} props={props} ></WujieReact> );}2. 预览
框架优缺点
优点
接入简单,可以以组件的方式引入子应用
纯净无污染
无界利用
iframe和webcomponent来搭建天然的 js 隔离沙箱和 css 隔离沙箱利用 iframe 的 history 和主应用的 history 在同一个 top-level browsing context 来搭建天然的路由同步机制
副作用局限在沙箱内部,子应用切换无需任何清理工作,没有额外的切换成本
支持
viteesmoudle加载,由于 js 是独立在iframe中加载的,所以支持esmodule加载支持预加载
支持应用保活,子应用状态保留,由于是独立在 iframe 中的,而切换应用时不会移除 iframe,所以子应用的状态会被保留在原来的
iframe中,当主应用再次渲染子应用 dom 时,会显示之前的状态。多应用同时激活在线
缺点
- iframe 沙箱的 src 设置了主应用的 host,初始化 iframe 的时候需要等待 iframe 的 location.orign 从'about:blank'初始化为主应用的 host,这个采用的计时器去等待的不是很优雅。
Micro App
截至目前(2024 年 4 月)github star 5.2kmirco-app 是京东 2021 年开源的一款微前端框架。它借助了浏览器对 webComponent 的支持,实现了一套微前端方案体系。并且由于 Shadow Dom 对 react 这类库的兼容性较差,便自己实现了类 Shadow Dom 的效果。与 qiankun 相比,接入更加简单。最新的版本也支持iframe实现 js 隔离,类似wujie。
实现方案
首先 micro-app 实现了一个基于 WebComponent 的组件,并实现了类Shadow Dom 的效果,开发者只需要用<micro-app >来加载子应用,整个对子应用的加载、js 隔离、css 隔离的逻辑都封装在了 web component 组件<micro-app>中,具体来说:
当调用
microApp.start()后,会注册一个名为micro-app的自定义 webComponent 标签。我们可以从<micro-app name='app1' url='xx' baseroute='/my-page'></micro-app>中拿到子应用的线上入口地址。<micro-app>组件内部,当匹配到路由后,跟 qiankun 一样加载 html,得到 html 字符串模版分析 html 字符串,提取
<head>头和<body>,并替换为框架自定义标签<micro-app-head>和<micro-app-body>在
<micro-app-head>内,会对 script 标签和 link 标签的内容进行加载并执行将
<micro-app-head>和<micro-app-body>插入到<micro-app>标签内<micro-app>内提供了 js 沙箱方法(v1.0 以前跟 qiankun 沙箱一样),<micro-app-head>挂载到<micro-app>后,内部会逐一对<micro-app-head>内的script标签的 js 绑定作用域,实现 js 隔离。
css 隔离方案
默认使用正则将 CSS 字符串切割成最小单元,每个单元包含一段 CSS 信息,将所有的信息整理生成 CSSTree,遍历 CSSTree 的每个规则,添加前缀实现样式隔离。
js 隔离方案
micro-app 有两种方式实现 js 隔离,默认是跟 qiankun 一样采用 proxy 沙箱的方式隔离, 在 v1.0 发布后支持了基于原生 iframe 的隔离方式。
Micro App 实战
1. 主应用入口文件
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import Router from './router';import microApp from '@micro-zoe/micro-app'microApp.start()ReactDOM.render( <React.StrictMode> <Router /> </React.StrictMode>, document.getElementById('root'));调用子应用
export function MyPage () { return ( <div> <h1>子应用👇</h1> // name:应用名称, url:应用地址 <micro-app name='my-app' url='http://localhost:3000/'></micro-app> </div> )}2. 预览
框架优缺点
优点
接入简单,组件式引入子应用
团队持续更新维护
js 隔离、css 隔离、路由同步
支持子应用保活, 需要开启 keep-alive 模式
支持 fiber 模式,提升主应用的渲染性能。
缺点
1.0 之前不支持 vite,1.0 之后支持了
默认 css 隔离方式,主应用的样式还是会污染到子应用。
子应用和主应用必须相同的路由模式,要么同时 hash 模式,要么同时 history 模式
依赖于 CustomElements 和 Proxy 两个较新的 API。Proxy 暂时没有做兼容,所以对于不支持 Proxy 的浏览器无法运行 micro-app。
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。