目录
- 前言
- 技术方案
- 沙箱
- with
- iframe
- 环境变量
- 主应用生命周期
- 子应用生命周期
- 初始化
- 更新
- 卸载
- 缓存
- JS 沙箱
- 样式隔离
- 元素隔离
- 路由系统⭐
- 数据通信⭐
- 资源系统
- 预加载
- umd 模式
- 其他功能
- 调试工具
前言
https://micro-zoe.github.io/micro-app/
micro-app
是由京东前端团队推出的一款微前端框架,它借鉴了 WebComponent 的思想,通过 js沙箱
、样式隔离
、元素隔离
、路由隔离
模拟实现了 ShadowDom 的隔离特性,并结合 CustomElement 将微前端封装成一个类 WebComponent 组件,从而实现微前端的组件化渲染,旨在降低上手难度、提升工作效率。
micro-app
与技术栈无关,也不和业务绑定,可以用于任何前端框架。
技术方案
https://juejin.cn/post/7236021829000691771
https://juejin.cn/post/7309477710523269174
- 主应用引入框架 Micro App,通过链接加载子应用,双方状态隔离,互不影响
沙箱
MicroApp 有两种沙箱方案:with沙箱
和 iframe沙箱
。
默认开启 with沙箱
,如果 with沙箱
无法正常运行,可以尝试切换到 iframe沙箱
,比如 vite。
with
with 沙箱通常是指使用 JavaScript 的 with 语句来创建一个新的作用域。这种方法在现代开发中不推荐使用,因为它可能导致代码难以阅读和维护、作用域不明确、调试困难、全局变量污染,而且 with 语句在严格模式下是不被允许的。
const microApp = {data: {message: 'Hello from MicroApp'},logMessage() {with(this.data) {console.log(message);}}
};microApp.logMessage(); // 输出: Hello from MicroApp
iframe
iframe
沙箱是一种更为常见的隔离技术,通过在 HTML 中使用 <iframe>
标签来嵌入微应用。每个 <iframe>
都有自己独立的全局作用域,这意味着 JavaScript 变量和 DOM 不会泄露到宿主应用中。
子应用的执行 js 脚本会被 micro 拿出来放到 iframe 进行执行,一般只会加载 样式文件。
环境变量
子应用可以通过一些 window 注入的全局变量来判断当前加载的状态如何:
__MICRO_APP_ENVIRONMENT__
(是否在微前端环境中)__MICRO_APP_NAME__
(获取应用在 micro-app 挂载的值)__MICRO_APP_BASE_ROUTE__
(子应用的基础路由路径)__MICRO_APP_BASE_APPLICATION__
(判断当前是否是主应用,执行 microApp.start 之后生效)rawWindow
(子应用获取主应用的执行 window 上下文)- 主应用的 window 内部包含子应用的 window,默认是 window[0] window[1] window[2]。
- 但是这个包含的 window 被注入了一些方法和变量,所以和原本的不一致了。
rawDocument
(子应用获取主应用的执行 document 结构)
主应用生命周期
- created: 标签初始化后,加载资源前触发。
- beforemount:加载资源完成后,开始渲染之前触发。
- mounted:子应用渲染结束后触发。
- unmount:子应用卸载时触发。
- error:子应用加载出错时触发,只有会导致渲染终止的错误才会触发此生命周期。
- 开启缓存前,先触发123,切换触发4,返回接着触发123
- 开启keep-alive后,应用卸载时会进入缓存,而不是销毁它们,以便保留应用的状态和提升重复渲染的性能。先触发123,切换不触发,返回触发1
子应用生命周期
初始化
更新
卸载
缓存
- 开启缓存后,子应用无需重复上述的渲染过程
JS 沙箱
https://micro-zoe.github.io/micro-app/docs.html#/zh-cn/sandbox
- 通过自定义的window、document拦截子应用的JS操作,放在 iframe 执行,实现一个相对独立的运行空间,避免全局变量污染,让子应用进行独立运行
- 后续出一篇文章,分析 iframe webcomponent 内部执行原理
样式隔离
micro-app[name=my-app1] .main .title
- 一般情况下,子应用不开启 scoped 进行隔离,主应用会加上 micro-app[name=my-app1] 进行样式标识
- 如果主应用的样式在全局下面,不开启 micro 的话,很可能会影响到子应用的原始样式
- 默认下样式隔离是开启的,也可以在应用、文件、行进行禁用操作
元素隔离
【小程序 - 大智慧】Expareser 组件渲染框架_exparser框架-CSDN博客
- 元素的隔离来自 Shadow Dom,元素不会逃离
<micro-app>
元素边界,子应用只能对自身的元素进行增、删、改、查的操作。 - 微前端下主应用拥有统筹全局的作用,可以获取子应用的元素。
- 自定义了
micro-app-head
、micro-app-body
等 Web Component 组件进行隔离。
路由系统⭐
拦截浏览器路由事件以及自定义的 location
、history
,实现了一套虚拟路由系统,子应用运行在这套虚拟路由系统中,和主应用的路由进行隔离,避免相互影响
search
是默认模式,子应用的路由信息会作为 query 参数同步到浏览器地址上。native
子应用和主应用共同基于浏览器路由进行渲染,但是配置更加复杂。native-scope
相比于native
,子应用的域名指向自身而非主应用。pure
模式是指子应用独立于浏览器路由系统进行渲染,即不修改浏览器地址,也不增加路由堆栈。state
模式是指基于浏览器history.state
进行渲染的路由模式,在不修改浏览器地址的情况下模拟路由行为,可以增加路由堆栈。- 路由系统和
vue-router
大差不差,具体包含了应用跳转、拦截、配置、路径解析等。
数据通信⭐
MicroApp
主应用和子应用之间的通信是绑定的,主应用只能向指定的子应用发送数据,子应用只能向主应用发送数据,这种方式可以有效的避免数据污染,防止多个子应用之间相互影响。
- 全局通信
- 主 <=> 子通信
- 无论是通信还是接收数据,发送数据是异步执行的,多个请求会在下一帧合并为一次执行。默认数据都是走缓存的,如果
key
和value
值都一样就不会发送,后续的数据请求会和之前的进行合并,然后一起发送过去。最后,当子应用卸载时,要注意通信的数据会被缓存,可能会导致一些困扰,此时可以主动清空缓存数据来解决。
资源系统
// 方式一:excludeAssetFilter
import microApp from '@micro-zoe/micro-app'microApp.start({excludeAssetFilter (assetUrl) {if (assetUrl === 'xxx') {return true // 返回true则micro-app不会劫持处理当前文件}return false}
})// 方式二:配置 exclude 属性
<link rel="stylesheet" href="xx.css" exclude>
<script src="xx.js" exclude></script>
<style exclude></style>
https://micro-zoe.github.io/micro-app/docs.html#/zh-cn/static-source
- 自动补全:对子应用相对地址的资源路径进行补全。
- 资源共享:当子应用加载相同地址的
js
或css
资源时,会直接从缓存中提取数据,从而提升渲染速度。 - 资源过滤:对于共享的资源使用
exclude
不加载,excludeAssetFilter
指定部分特殊的动态加载的微应用资源(css
/js
)不被 `
预加载
预加载是指在子应用尚未渲染时提前加载静态资源,从而提升子应用的首次渲染速度。为了不影响主应用的性能,预加载会在浏览器空闲时间执行。
microApp.preFetch([{ name: 'my-app1', url: 'xxx' }, // 加载资源并解析{ name: 'my-app2', url: 'xxx', level: 1 }, // 只加载资源{ name: 'my-app3', url: 'xxx', level: 3 }, // 加载资源、解析并渲染{ name: 'my-app4', url: 'xxx', level: 3, 'default-page': '/page2' }, // 加载资源、解析并渲染子应用的page2页面
])
umd 模式
// main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import routes from './router'
import App from './App.vue'let app = null
let router = null
let history = null
// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行
window.mount = () => {history = createWebHistory()router = createRouter({history,routes,})app = createApp(App)app.use(router)app.mount('#app')
}// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数
window.unmount = () => {app.unmount()history.destroy()app = nullrouter = nullhistory = null
}// 如果不在微前端环境,则直接执行mount渲染
if (!window.__MICRO_APP_ENVIRONMENT__) {window.mount()
}
- 默认模式:初次渲染和后续渲染时会顺序执行所有
js
,以保证多次渲染的一致性。 - umd 模式:子应用定义
mount
、unmount
方法,此时只在初次渲染时执行所有js
,后续渲染只会执行这两个方法,在多次渲染时具有更好的性能和内存表现。
其他功能
- 支持多层嵌套,A 嵌套 B,B 嵌套 C,需要按照规定进行配置。
- 插件系统可以对全局和单独应用的
js
处理规则进行修改,因为默认在沙箱中,顶层的变量是无法泄漏为全局变量的(如var xx =
,function xxx
定义变量,无法通过window.xx
访问),导致js
报错。 - 通过自定义
fetch
替换框架自带的fetch
,可以修改fetch
配置(添加 cookie 或 header 信息等等),或拦截 HTML、JS、CSS 等静态资源。
调试工具
MicroApp