
一、MicroApp
1.1 MicroApp简介
MicroApp是由京东前端团队推出的一款微前端框架,它从组件化的思维,基于类WebComponent进行微前端的渲染,旨在降低上手难度、提升工作效率。MicroApp无关技术栈,也不和业务绑定,可以用于任何前端框架。
官网链接:
https://micro-zoe.github.io/micro-app/
源码地址:
https://github.com/micro-zoe/micro-app
微前端的概念是由ThoughtWorks在2016年提出的,它借鉴了微服务的架构理念,核心在于将一个庞大的前端应用拆分成多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用融合为一个完整的应用,或者将原本运行已久、没有关联的几个应用融合为一个应用。微前端既可以将多个项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
微前端的出现主要是为了解决了两个问题:
1、随着项目迭代应用越来越庞大,难以维护。
2、跨团队或跨部门协作开发项目导致效率低下的问题。

1.2 关于MicroApp
MicroApp借鉴了WebComponent的思想,通过js沙箱、样式隔离、元素隔离、路由隔离模拟实现了ShadowDom的隔离特性,并结合CustomElement将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染,旨在降低上手难度、提升工作效率。
micro-app和技术栈无关,也不和业务绑定,可以用于任何前端框架。

1.3 MicroApp优点
1、使用简单
我们将所有功能都封装到一个类WebComponent组件中,从而实目前基座应用中嵌入一行代码即可渲染一个微前端应用。
2、功能强劲
micro-app提供了js沙箱、样式隔离、元素隔离、路由隔离、预加载、数据通信等一系列完善的功能。
3、兼容所有框架
为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何前端框架中都可以正常运行。
二、快手上手
在微前端架构中,微前端工程主要由主应用和子应用,下面我们分别列出主应用和子应用需要进行的修改。
2.1 主应用
第一,我们需要在主应用中安装依赖。
npm i @micro-zoe/micro-app --save
然后,初始化micro-app。
// index.js
import microApp from '@micro-zoe/micro-app'
microApp.start()
然后,在主应用中嵌入子应用,对应的React的写法如下。
export function MyPage () {
return (
<div>
<h1>子应用</h1>
// name:应用名称, url:应用地址
<micro-app name='my-app' url='http://localhost:3000/'></micro-app>
</div>
)
}
Vue的写法如下。
<template>
<div>
<h1>子应用</h1>
<!-- name:应用名称, url:应用地址 -->
<micro-app name='my-app' url='http://localhost:3000/'></micro-app>
</div>
</template>
2.2 子应用
对应的,我们需要在子应用的webpack-dev-server的headers中设置跨域支持,如下所示。
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
}
},
完成以上步骤即完成微前端的接入。不过,接入MicroApp需要注意以下几点:
1、name:必传参数,必须以字母开头,且不可以带特殊符号(中划线、下划线除外)
2、url:必传参数,必须指向子应用的index.html,如:http://localhost:3000/ 或
http://localhost:3000/index.html
3、子应用必须支持跨域,跨域配置参考这里
三、功能简介
3.1 JS沙箱
在MicroApp开源后我们收到社区的一些反馈,其中一条关于性能的问题引起我们的重点关注。这不是MicroApp才存在的问题,而是微前端长久以来的难题,在社区有大量关于沙箱性能问题的讨论,却始终没有完美的解决方案。

MicroApp采用的是和qiankun一样的proxy+with的沙箱方案,这也是目前js沙箱的主流方案 。with沙箱的功能超级完善,但是性能损耗却超级明显,在社区中也不乏对with沙箱性能问题的讨论,但一直没有特别完美的解决方案,这是由于with改变了js的作用域链,从而产生大量的重复请求。问题的根源是with,但又不全是with的问题,准确的说是with和proxy两者叠加造成的,with和proxy本质上的性能都不高,需要一种方案避免对这两个方法的频繁读取操作,MicroApp采用的解决方案是变量前置和异步防抖。
• 变量前置:是指使用Object.defineProperty定义全局变量,通过get和set设置响应数据,一是为了避免在proxy的get中进行多余的操作,二是defineProperty的性能比proxy更优秀 。文章来源地址
https://www.yii666.com/blog/719852.html
• 异步防抖:是指在子应用运行时对promise进行标记,确保在上一个promise执行完成之后才会进入下一个,避免并行触发,防止promise被频繁触发会造成性能损失。

在此基础上,MicroApp沙箱还提供了快照、缓存、预加载等功能,在保证功能不变的前提下,彻底解决沙箱的性能问题,升级后的沙箱运行效率媲美原生JS。
不过,使用MicroApp沙箱需要注意以下几点:
1,子应用在沙箱环境中如何获取到真实window
目前有3种方式在子应用中获取外部真实window。
- new Function(“return window”)() 或 Function(“return window”)()
- (0, eval)('window')
- window.rawWindow
2,子应用抛出错误信息:xxx 未定义
xxx未定义错误包括如下一些:
- xxx is not defined
- xxx is not a function
- Cannot read properties of undefined
产生错误的缘由是,在沙箱环境中,顶层变量不会泄漏为全局变量。例如在正常情况下,通过 var name 或 function name () {} 定义的顶层变量会泄漏为全局变量,通过window.name或name就可以全局访问。但是在沙箱环境下这些顶层变量无法泄漏为全局变量,window.name或name的值为undefined,导致出现问题。
解决的方式有两种:
第一种:将 var name 或 function name () {} 修改为 window.name = xx
第二种:通过插件系统修改子应用代码,列如常见的加载webpack打包的dll文件失败的问题,由于dll文件的内容和js地址相对固定,可以直接进行全局查找和修改。
microApp.start({
plugins: {
modules: {
应用名称: [{
loader(code, url) {
if (url === 'xxx.js') {
code = code.replace('var xx_dll=', 'window.xx_dll=')
}
return code
}
}]
}
}
})
3,基座如何对子应用 document 的一些属性进行自定义代理扩展
微前端模式下,一般由基座负责设置站点标题,不希望受到子应用的干扰。但是由于 microApp 对 documet 的代理处理过程,并没有处理 document.title,所以子应用中可能通过 document.title = 'xxx' 意外改变了基座的站点标题。
解决的方法是,通过 customProxyDocumentProps 对 document 的属性进行自定义代理扩展通过给title设置一个空函数,来忽略 document.title 执行。
microApp.start({
customProxyDocumentProperties: new Map([
['title', (value) => {}]
]),
})
3.2 虚拟路由系统
3.2.1 虚拟路由系统背景
微前端是将多个不同的web应用融合在一起渲染,但浏览器只有一个路由系统,这很容易造成应用之间的路由冲突,最常见的就是vue3的路由冲突问题。

上面是vue-router作者对于在微前端环境下的冲突问题的回答,他认为vue-router已经覆盖足够多场景,微前端的问题应该由微前端解决。
在我们刚开源时,并没有对路由进行隔离,用户对于MicroApp的问题几乎一半都和路由相关,由于一个路由系统同时满足多个应用的渲染容易导致冲突,也超级反直觉和难以理解,于是我们推出了虚拟路由系统。

虚拟路由系统与浏览器的路由行为一致,它通过自定义location和history等核心路由API,重写了popState和hashChange事件,拦截路由导航和事件,并提供了一系列自定义API,模拟了在浏览器环境下的Web应用程序的渲染、跳转和返回等路由行为。子应用程序在这个虚拟路由系统中运行,与基座应用程序的路由相互隔离,从而避免相互影响,并增强了子应用程序与基座应用程序之间的交互能力。通过虚拟路由系统,基座应用程序可以方便地获取子应用程序的路由信息并控制子应用程序的跳转,子应用程序的路由信息会作为参数同步到浏览器地址上。此外,虚拟路由系统还提供了许多功能,协助开发人员提高工作效率。
3.2.2 虚拟路由系统实战
路由模式
虚拟路由系统分为四种模式:search、native、native-scope、pure。search是默认模式,一般不需要特意设置,search模式下子应用的路由信息会作为query参数同步到浏览器地址上,如下。

如果是设置单个子应用,如下所示。
<micro-app name='xx' url='xx' router-mode='search'></micro-app>
如果需要全局设置,如下所示。
import microApp from '@micro-zoe/micro-app'
microApp.start({
'router-mode': 'search',
})
native模式下子应用完全基于浏览器路由系统进行渲染,比search模式拥有更加简洁优雅的的浏览器地址,但相应的需要更加复杂的路由配置。
如果是设置单个子应用,如下所示。
<micro-app name='xx' url='xx' router-mode='native'></micro-app>
全局设置参考如下:
import microApp from '@micro-zoe/micro-app'
microApp.start({
'router-mode': 'native',
})
native-scope模式的功能和用法和native模式一样,唯一不同点在于native-scope模式下子应用的域名指向自身而非主应用。
如果是设置单个子应用,参考如下。
<micro-app name='xx' url='xx' router-mode='native-scope'></micro-app>
如果是全局设置,参考如下。
import microApp from '@micro-zoe/micro-app'
microApp.start({
'router-mode': 'native-scope',
})
pure模式下子应用独立于浏览器进行渲染,即不会修改浏览器地址,也不会受其影响,其表现和iframe类似。
如果是设置单个子应用,参考如下。
<micro-app name='xx' url='xx' router-mode='pure'></micro-app>
如果是全局设置,参考如下。
import microApp from '@micro-zoe/micro-app'
microApp.start({
'router-mode': 'pure',
})
配置项
有时候,我们可能会遇到需要关闭虚拟路由的场景。实际上虚拟路由系统是无法关闭的,这里的配置只是为了向下兼容旧版本,它的表现和native路由模式一致。
对于单个子应用来说,只需要使用下面的代码即可。
<micro-app name='xx' url='xx' disable-memory-router></micro-app>
而如果需要全局设置,可以参考下面的代码。
import microApp from '@micro-zoe/micro-app'
// 在start中增加配置
microApp.start({
'disable-memory-router': true, // 关闭虚拟路由
})
默认情况下,子应用卸载后重新渲染,将和首次加载一样渲染子应用的首页。设置keep-router-state可以保留子应用路由状态,在卸载后重新渲染时将恢复卸载前的页面(页面中的状态不保留)。
如果保留某个子应用的路由状态,参考如下。
<micro-app name='xx' url='xx' keep-router-state></micro-app>
如果需要保留所有子应用的路由状态,参考如下。
import microApp from '@micro-zoe/micro-app'
// 在start中增加配置
microApp.start({
'keep-router-state': true, // 保留路由状态
})
注意:
- 如果关闭了虚拟路由系统,keep-router-state也将失效。
- 当设置了default-page时keep-router-state将失效,由于它的优先级小于default-page。
3.3 样式隔离
MicroApp最初是基于style元素的CSSStyleSheet实现的样式隔离:即将CSS字符串插入style元素生成CSSStyleSheet,遍历每个CSS规则,添加前缀实现样式隔离。

这种一种取巧的方式,利用浏览器自身的能力格式化CSS,并在此基础上进行修改,省去许多工作量。但问题也出目前这里,不同浏览器对于一样的CSS生成的CSSStyleSheet可能会不同,这就导致我们在处理CSSStyleSheet会遇到不可以预知的问题,导致CSS表现不一致。
于是MicroApp换了一种方式来实现样式隔离,我们使用正则将CSS字符串切割成最小单元,每个单元包含一段CSS信息,将所有的信息整理生成CSSTree,遍历CSSTree的每个规则,添加前缀实现样式隔离。

3.3.1 样式隔离
MicroApp的样式隔离是默认开启的,开启后会以标签作为样式作用域,利用标签的name属性为每个样式添加前缀,将子应用的样式影响禁锢在当前标签区域。
.test {
color: red;
}
/* 转换为 */
micro-app[name=xxx] .test {
color: red;
}
但主应用的样式依然会对子应用产生影响,如果发生样式污染,推荐通过约定前缀或CSS Modules方式解决。
3.3.2 禁用样式隔离
禁用样式隔离分四个层次:
如果是在所有应用中禁用,可以通过start方法进行全局配置,设置后所有应用的样式隔离都会停止。
import microApp from '@micro-zoe/micro-app'
microApp.start({
disableScopecss: true, // 默认值false
})
如果希望在某个应用中不受全局配置控制,可以设置disableScopecss='false'。
<micro-app name='xx' url='xx' disableScopecss='false'></micro-app>
如果在某一个应用中禁用,当前应用的所有css都不会进行样式隔离。
<micro-app name='xx' url='xx' disableScopecss 或 disable-scopecss></micro-app>
如果想要在某一个文件中禁用,可以在你的css文件中使用以下格式的注释来禁用样式隔离。
/*! scopecss-disable */
.test1 {
color: red;
}
/*! scopecss-enable */
当然,也可以对指定的选择器禁用样式隔离。
/*! scopecss-disable .test1, .test2 */
.test1 {
color: red;
}
.test2 {
color: yellow;
}
.test3 {
color: green;
}
/*! scopecss-enable */
如果想在整个文件范围内禁用样式隔离,将 /*! scopecss-disable */ 注释放在文件顶部。
/*! scopecss-disable */
...
如果需要在某一行中禁用,在文件中使用以下格式的注释在某一特定的行上禁用样式隔离,如下所示。
/*! scopecss-disable-next-line */
.test1 {
color: red;
}
.test2 {
/*! scopecss-disable-next-line */
background: url(/test.png);
}
3.3.3 shadowDOM
shadowDOM具有更好的隔离性,但一些框架(如React)对shadowDOM的兼容性不好,请谨慎使用。开启shadowDOM后,默认的样式隔离将失效。
四、Micro-App-DevTools
Micro-App-DevTools 是基于 MicroApp 推出的一款Chrome浏览器插件,目的是为了在开发和使用 MicroApp 过程中提高效率。通过此插件可以有效的解决调试困难、模拟数据通信、查看视窗范围、设置路由、获取环境变量等诉求,进而更好地协助用户去了解和使用 MicroApp。


Micro-App-DevTools通过模拟子应用开发环境,获取父应用数据,来可视化查看通讯数据,提高开发调试效率。对于路由,将会显示所有应用的路由,包含层层嵌套应用以及一个父应用多个子应用的路由,使不同团队应用也能快速定位自己问题,方便协作。还提供了全局变量和高亮视窗功能,实现快速定位范围,提高排查效率的功能,并集结了图标、右键、控制台的快捷进入方式,使用户快速上手,零成本使用。

参考链接:
MicroApp Github地址:
https://github.com/micro-zoe/micro-app
Micro-App-DevTools Github地址:
https://github.com/micro-zoe/micro-app-chrome-plugin
MicroApp官网地址:
https://micro-zoe.github.io/micro-app

没有技术实力 别用 没有专业的前端高手也别往里面扎
为啥