前言:本文着眼于梳理业界微前端技术(介绍下微前端几场恶仗的主打人)的发展脉络、在主流产品中的应用情况,以及目前不同技术方案之间的架构和核心原理对比,只关注精华部分,不纠结于技术细节,帮大家快速建立起对微前端整体上的认识。
注:相信本文对各个微前端方案的总结是相对全面的,但缺点是对每个方案深入了解的程度有限,难免有错误和遗漏之处,欢迎大家评论勘误和补充!也欢迎大家通过我提供的索引,选择自己感兴趣的方向继续深入学习。
最后更新时间:2024.01.19
-
微前端简介
-
什么是微前端?
相信大家对此已经耳熟能详,我就不多废话了,直接摘一段词条解释。
微前端是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。
此外,Martin Fowler 还给过一个更简洁的英文解释:
An architectural style where independently deliverable frontend applications are composed into a greater whole.
厘清一些关于微前端概念的误区:
-
微前端不是一门具体的技术,而是整合了技术、策略和方法,可能会以脚手架、配套工具和规范约束等等成体系的形式综合呈现,是一种宏观上的架构。这种架构目前有多种方案,各有利弊,但只要适用业务场景的就是好方案。
-
微前端本身并没有技术栈的约束(技术栈无关不是微前端的固然要求)。每一套微前端方案的设计,都是基于实际需求出发。如果是多团队统一使用了 React 技术栈,可能对微前端方案的跨技术栈使用并没有要求;如果是多团队同时使用了 React 和 Vue 技术栈,可能就对微前端的跨技术栈要求比较高。
-
微前端要求各个应用能独立开发、测试、部署,但并不要求各个应用能独立运行。也就是说,微前端的粒度不一定是应用级的,也有可能是页面级,甚至组件级。
-
微前端的发展
“微前端(Micro Frontends)”这个概念由 thoughtworks 于 2016 年 11 月在 TECHNOLOGY RADAR 文章中率先提出。经过 5 年的发展,现在我随便一列,实现微前端的主流方案就已经多达 20 多种,实现的方式也是五花八门,至于应用微前端技术的产品之多,那就更数不清了。
就这个成果来看,当时的想法可以说是非常具有前瞻性🐮🐮。
如果再往前追溯,“微前端”其实是受到了 2014 年正式提出的“微服务(Microservices)”概念的启发。
微服务是指一种软件架构风格,以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic)的 API 集相互通信。
-
在技术上:
- “微前端”和“微服务”需要解决的问题是共通的(你看,连定义都是这么相像…),简单说起来就是:应用随着项目迭代越来越庞大,耦合度升高,以致缺乏灵活性,难以维护。
-
在团队协作上:
- 康威定律指出,设计系统的架构受制于产生这些设计的组织的沟通结构。它指出了组织架构越庞大,其系统间沟通成本越高的问题。
- 解决这一问题的有效手段就是,将大的系统拆分成一个个微小的,可以独立自治的子系统。一旦系统的依赖限制在了内部,功能上更加内聚,对外部的依赖变少,那么就能显著的减少跨系统之间的沟通成本了。
- 简单来说,康威定律的指导思想就是:既然沟通是大问题,那么就不要沟通就好了。所以,微前端(微服务架构)也关注如何解决组织和团队间协作带来的工程问题,而不是单纯的某个技术问题。
-
而在业务上:
-
选择前端微服务化的原因却刚好与“解耦”、“拆分”相反——人们更想要的结果是聚合,尤其是那些 To B 的应用。比如大家熟悉的各类云服务网站、以及大部分的中台应用。
-
在“聚合”这一目标上,面临的另一个重大困难来自遗留系统。在既不重写原有系统的基础之下,又可以抽出人力来开发新的业务,对于业务和技术人员来说, 是一个相当吸引力的特性。这也是微前端大受欢迎的主要原因之一。
-
-
微前端历史演进图
单体应用 -> 前后端分离 -> 微服务化 -> 微前端化。
暂时无法在飞书文档外展示此内容
-
微前端背后的核心思维
-
技术不可知主义
- 每个团队应该选择自己的技术栈以及技术进化路线,而不是与其他团队步调一致。
-
隔离团队之间的代码
-
即便所有团队都使用同样的框架,也不要共享同一个运行时环境。构建自包含的Apps。不要依赖共享的状态或者全局变量。
-
-
微前端的实现要求(评价标准)
是不是一个微前端实现方案,和是不是一个好的微前端实现方案——这是两个不同的问题。
判断一个微前端是否优秀(符合需求)和全面,可以从以下维度进行评价分析:
-
微前端的呈现方式
典型的微前端看起来大致可以分为两种:
-
按布局呈现
举例,主应用(上图 Menu&Header 部分)负责路由、账号管理等公共功能模块,子应用(Content&Footer 部分)承载业务逻辑。这也是我们在生产中常见的微前端的呈现。
-
按领域呈现
- 中台应用的开发者可能对上面按布局划分的微前端更加熟悉,但真实世界是更复杂的。比如商城应用,不同团队可能负责页面的不同模块,比如产品、推荐、结算模块等,在页头、侧边栏部分淡化了主从关系。
总结而言,在微前端的设计中,拆分应该更多地根据业务需求来做,而不是技术决策。领域驱动设计(Domain-Driven Design,简称DDD),也应该是微前端分解的准则。
-
微前端的集成方式
-
微前端的体系
前文提到微前端不只是一门具体的技术,而往往是一套体系。在目前主流的 production-ready 的微前端方案中(主要集中在下文定义的庄重稳健老干部派),这套体系大致可分为如下几个部分:
-
治理体系
「治理体系」简单看可以视为一个上线管理平台 + 上线发布流程。业界的管理平台大体实现这两个功能:
- 应用管理 – 能上线各种主应用、子应用不同版本,列出上线应用不同版本的入口地址
- 依赖管理 – 明确管理父子应用依赖关系,将子应用入口地址注入父应用
-
开发配套
这一部分比较容易理解:给开发者提供完整的文档指引是必备项。
-
微物料
微物料将微前端的粒度级别从应用级别延伸到了组件、函数级别。
- App: 一整个微前端应用,内部也能有很多模块、多个页面 (Page)。
- Page: 一个稍大一点有路由的微前端组件可以称为页面,如一个数据查询页面。
- Widget : 没有路由的小组件(挂件),如一个样式很独特的按钮。
- Function: 被远程加载执行的一个功能函数,如试想一下用 UMD 加载 lodash 一个 func (接口格式定义在应用外)。
-
运行时容器
这部分是狭义上的「微前端框架」所做的事,具体原理将在下文分析。
-
微前端的批评意见
微前端的主要批评意见集中在其热度很高的情况下,开发者将微前端视作完美的解决方案,“热闹驱动开发”,而脱离了实际业务需求和对微前端收益、成本、缺点的综合评估。
满足以下几点,你可能就不需要 微前端 ❌:
-
你/你的团队具备系统内所有架构组件的话语权:简单来说就是,系统里的所有组件都是由一个小的团队开发的。
-
你/你的团队有足够动力去治理、改造这个系统中的所有组件:直接改造存量系统的收益大于新老系统混杂带来的问题。
-
系统及组织架构上,各部件之间本身就是强耦合、自洽、不可分离的。系统本身就是一个最小单元的「架构量子」,拆分的成本高于治理的成本。
满足以下几点,你才确实可能需要 微前端 ✅:
-
系统本身是需要集成和被集成的,一般有两种情况:
- 旧的系统不能下,新的需求还在来。
- 系统需要有一套支持动态插拔的机制。
-
系统中的部件具备足够清晰的服务边界。
微前端的其他批评意见来自其自身的固有缺点,比如:
- 系统复杂度
- 性能(体积大小)
- 额外问题的不可预见性
总结:一切都是权衡。
-
主流技术方案对比(五大门派间不得不说的故事)
这五大门派只是大致的划分,它们之间并非水火不容, 而是对彼此的理念、核心技术均有一定程度的参考和应用
-
iframe:Old school 死磕派
HTML 内联框架元素
<iframe>
表示嵌套的浏览上下文(Browsing context),能有效地将另一个 HTML 页面嵌入到当前页面中。
通过 iframe 嵌入来实现微前端,(可能)是从微前端概念产生以来最为经典、old-school、能跑就行(又不是不能用)的解决方案。
- iframe 使用简单,即来即用(拿来吧你)。
- iframe 可以创建一个全新、独立的宿主环境,子应用独立运行,隔离完美(干净又卫生啊兄弟们)。
- iframe 支持在一个页面放上多个应用,组合灵活。
但是,iframe 也有很多送命问题:
-
视窗大小不同步:例如我们在 iframe 内的弹窗想要居中展示。
-
子应用间通信问题:只能通过
postMessage
传递序列化的消息。 -
额外的性能开销:加载速度、构建 iframe 环境导致白屏时间太长。
-
路由状态丢失:刷新一下,iframe 的 url 状态就丢失了。
一款极致的微前端框架,基于 WebComponent 容器(ShadowRoot) + iframe 沙箱
但是,在冬光灿烂的 2021 年年末,迎面走来了红光满面的来自腾讯的头铁工程师方阵,“让 iframe 焕发新生”是他们坚定的誓言,“让微前端不再纷争”谱写了青春的画卷…😝😝
无界的口号是:继承 iframe 的优点,补足 iframe 的缺点。
具体是怎么继承和补足的呢?我用最简单的话来总结:
- 利用 iframe 的隔离性,把 JS 代码放到 iframe 里执行(通过 Proxy)。
- 利用 Shadow ****DOM 的隔离性,把子应用的 DOM 写到 ShadowRoot 里,实现样式隔离。
下图就描述了这样一个过程:主应用 A 通过构造一个 shadowRoot 和 iframe,然后将子应用 B 的 HTML 写入 shadowRoot 中,同时将子应用 B 的 JS 放到 iframe 中运行。
一个样式隔离,一个 JS 沙箱,跑起来那起码得是夫妻双双把家还的场面了。
在继续分析之前,我们不妨再来回顾一下 Shadow DOM,这个概念将在下文几度露脸。
📖📖 主要技术点之 Shadow DOM
Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。
// open 表示可以通过页面内的 JavaScript 方法来获取 Shadow DOM
let shadow = elementRef.attachShadow({mode: 'open'});
// 不可以从外部获取 Shadow DOM
let shadow = elementRef.attachShadow({mode: 'closed'});
Shadow DOM 在各种微前端方案中有广泛运用,比如无界、Garfish 等,主要的作用是:把每个子应用包裹到一个 Shadow DOM 中,从而保证其运行时的样式的绝对隔离。
- 上面两点是无界方案精髓中的精髓,其它的要点都是些技术细节了。我拣 2 条最重要的说说:
-
为什么要改写嵌入的 iframe 的域名(从 hostB 转换为 hostA)?
- 答:为了利用同源 iframe 可以便捷通信的特性。
-
在 iframe 中操作路由,会导致主、子应用路由不统一的问题怎么解决?
-
答:监听 iframe 的路由变化并同步到主应用,浏览器的 url 也会同步到 iframe。
-
说完了技术,再说用法:
// 和 iframe 非常相像
import WujieReact from "wujie-react";
<WujieReactwidth="100%"
height="100%"
name="xxx"
url="{xxx}"
sync="{true}"
fetch="{fetch}"
props="{props}"
beforeLoad="{beforeLoad}"
beforeMount="{beforeMount}"
afterMount="{afterMount}"
beforeUnmount="{beforeUnmount}"
afterUnmount="{afterUnmount}"
></WujieReact>
最后总结一下无界的特点:
无界的优点 | 无界的缺点 |
---|---|
单页面中,多应用同时激活 | 因为 Shadow DOM 和 Proxy,导致兼容性一般 |
隔离机制优雅 | |
组件式的使用,方便(注册❌, 路由匹配❌) |
-
路由分发+资源处理:庄重稳健老干部派
庄重稳健派不跟你多谈些有的没的,突出的就是一个目标:production-ready。
基座应用和子应用的主从关系、路由分发+对子应用资源的提前处理、设计精巧的 JS 沙箱隔离机制、对样式隔离的支持、甚至是完善的配套体系,是这一门派的突出特点,也让这一门派成为了业界真正的顶流。
代表性方案:基于 single-spa 的 qiankun、 Garfish、icestark 等。
single-spa 是一个小于 5kb(gzip)的 npm 包,用于协调微前端的挂载和卸载。它知道何时挂载应用程序,并且可以框架无关的方式挂载应用程序。
single-spa 是这一派绝对的开山鼻祖,跟粽子界的屈原一个档次。
single-spa 实现了什么?我用最简单的话来说:主应用通过路由匹配实现对子应用生命周期的管理。具体见下图:
暂时无法在飞书文档外展示此内容
再看看 API,你就能大概明白 single-spa 的运行机制了:
// 主工程把子应用给注册了
singleSpa.registerApplication({
name: 'A', // 子应用名 app: () => System.import('A'), // 如何加载你的子应用(用户自定义) activeWhen: '/a', // url 匹配规则,表示啥时候开始走这个子应用的生命周期
})
singleSpa.start() // 启动主应用
// ————————————————————————————————————————————————————————————————
// 子应用把生命周期导出
import SubApp from './index.tsx'
export const bootstrap = () => {}
export const mount = () => {
// 使用 React 来渲染子应用的根组件 ReactDOM.render(<SubApp />, document.getElementById('root'));
}
export const unmount = () => {}
以上就是 single-spa 核心中的核心,下面我再补充几个细节:
- 主应用如何导入子应用? 这个需要用户自行定义,官方推荐的是 SystemJS + import maps。
<script type="systemjs-importmap">
{
"imports": {
"@react-mf/root-config": "//localhost:9000/react-mf-root-config.js"
}
}
</script>
<script>
singleSpa.registerApplication({
name: 'app', // 子应用名 app: () => System.import('@react-mf/root-config'), // 如何加载你的子应用 activeWhen: '/appName', // url 匹配规则,表示啥时候开始走这个子应用的生命周期 customProps: { // 自定义 props,从子应用的 bootstrap, mount, unmount 回调可以拿到 authToken: 'xc67f6as87f7s9d'
}
})
</script>
- 不支持 JS 沙箱隔离
- 不支持 CSS 隔离
这俩属于是硬伤级别了,这也是为什么那么多框架基于 single-spa 做二次封装的原因。
qiankun 是一个基于 single-spa 的微前端实现库,孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,经过了蚂蚁内部 200+ 平台的使用充分检验和打磨。主打特点:接入简单、功能完备、生产可用。
qiankun 的口号是:可能是你见过最完善的微前端解决方案。
qiankun 是国内目前影响力最大的面向生产的微前端方案,同时也是唯一得到它的师承 single-spa 官方推荐的方案。
在我调研期间,国内基本每一个微前端方案在介绍特性时都会拿自己和 qiankun 进行对比,足以证明它的地位。
qiankun 这个名字是怎么得来的呢?据作者解释:小世界有大乾坤。我们希望在微前端这个小世界里面,通过 qiankun 这个框架鼓捣一个大乾坤。(提问:那为啥不叫捣乾呢?)
-
qiankun 本身
主要特点:
-
样式隔离
-
JS 副作用隔离
-
一个页面同时嵌入多个微前端子应用
-
框架不限
-
支持资源预加载
-
支持主/子应用数据通信
API:
// 主应用
registerMicroApps([
{
name: 'app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/app',
},
]);
start();
// ————————————————————————————————————————————————————————————————————
// 子应用导出生命周期
export async function bootstrap() {
console.log('react app bootstraped');
}
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(
props.container ? props.container.querySelector('#root') : document.getElementById('root'),
);
}
export async function update(props) {
console.log('update props', props);
}
核心技术点:
-
single-spa。 我就不再多介绍了,只提一嘴,这就是 qiankun 和 single-spa 的 API 为啥那么相似的原因。
-
import-html-entry。简单地说,就是一个加载并处理 HTML、JS、CSS 的第三方库——qiankun 通过这个库来加载子应用的资源从而做进一步处理。那都要处理啥呢?请看第三条。
-
隔离/通信等机制。这一块也就是我们需要重点关注的 qiankun 的精髓了。
📖📖 核心技术点之 JS 沙箱
现在业界流行的沙箱分成 2 种,一种是快照沙箱,一种是 Proxy 沙箱。
快照沙箱其实特别好理解。A 应用启动前我给 Window 照个写真,应用卸载掉的时候我再照个大头贴。等会新应用 B 进来我就把干净卫生的写真给 B,应用 A 回来我就把 A 本人脏脏的大头贴给 A。
class Sandbox {
private snapshotOriginal
private snapshotMutated
activate: () => void;
deactivate: () => void;
execScript: (code: string) => void;
}
const sandbox = new Sandbox(); // 创建一个沙箱
sandbox.activate(); // 写真
sandbox.execScript(code); // 然后传入需要执行的代码,放到沙箱里执行
sandbox.deactivate(); // 大头贴
-
在
activate
的时候遍历 Window上的变量,存为 snapshotOriginal。 -
在
deactivate
的时候再次遍历 Window 上的变量,分别和 snapshotOriginal 对比,将不同的存到 snapshotMutated 里,将 Window 恢复回到 snapshotOriginal。 -
当应用再次切换的时候,就可以把 snapshotMutated 的变量恢复回 Window 上,实现一次沙箱的切换。
-
Proxy 沙箱也简单。前面说到我要把代码放在沙箱里执行,那么咋执行呢?
咱把 Window 一代理就完事儿了。
想在 Window 上挂东西?挂在咱们的 fakeWindow 上吧你。
想从 Window 上取东西?先看看咱们的 fakeWindow 上有没有,没有再从 Window 上拿给你。
const varBox = {};
const fakeWindow = new Proxy(window, {
get(target, key) {
return varBox[key] || window[key]
},
set(target, key, value) {
varBox[key] = value;
return true;
}
})
// 'with' 语句将某个对象添加到作用域链的顶部,如果在函数体 statement 中有某个未使用
// 命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值
const fn = new Function('window', `
with(window) {
window.localstorage.setItem('1',123);
}`
);
fn(fakeWindow);
看完对比,自然地就会联想(强行自然一下)到:Proxy 沙箱相比快照沙箱而言,天然允许多实例的存在(也就是一个页面能同时挂几个子应用),对于副作用的隔离控制权更高,能够实现一些特殊的自定义功能。
📖📖 核心技术点之样式隔离
样式隔离这一块 qiankun 就没有 Garfish 搞的那么精致。
-
支持 Shadow DOM 严格的样式隔离,但可能会有问题。
-
在切换子应用时,通过删除子应用各自的 css 样式表来隔离。
-
官方建议自行定义选择器前缀。
-
qiankun 的配套体系
前文我提到,好用的微前端不是微前端框架本身就能解决的——而是需要治理体系、开发配套、甚至是微物料的紧密配合。根据 qiankun 作者的分享,配套体系主要包括以下内容:
-
接入管控
- 为了解决以下这几个问题:“一个低质量的子应用能随随便便接入 toB 的巨无霸应用吗?”、“接入了之后出问题了怎么整?”、“错误把对内的子应用接到对外的主应用了怎么办?”、“把一个子应用一口气接到了3个主应用,完事儿之后,忘了一个!”……
- 所以需要一个配置中心:看到主、子应用的对应关系;接入前审核;应用分类(如果你是热门应用,我就预加载你)等等。
-
子应用部署、版本管理、灰度管理、降级容灾
-
监控
-
注重数据的价值,流量、性能、错误监控、用户使用习惯等等,都会在日常运营和后续产品迭代出显示出价值。
-
Garfish 是字节完全自研的微前端框架。🈚️ single-spa,🈚️ import-html-entry。咱还能说啥呢,麻烦大家把 🐮 打在公屏上。
主要特点:
-
Garfish 微前端子应用支持任意多种框架、技术体系接入。
-
强大的预加载能力,自动记录用户应用加载习惯增加加载权重,应用切换时间极大缩短。
-
支持依赖共享,极大程度的降低整体的包体积,减少依赖的重复加载。(但有问题,会导致子应用无法独立运行)。
-
支持多实例能力,可在页面中同时运行多个子应用提升了业务的拆分力。
-
提供业务插件满足业务方的各种定制需求。
主要技术点:
-
沙箱的原理和 qiankun 相似。
-
样式隔离的处理整的很细致,我用最简单的话说:劫持各种事件和资源,把插入的各种样式全部收集起来统一隔离管理,同时也支持 Shadow DOM。具体欢迎大家查看 Garfish 的文档了解。
-
提供 HTML 和 JS 双入口。
Piral 是一家德国公司推出的微前端框架,遵循主、子应用的模式,主应用叫“piral instance”,子应用叫“pilets”。
Piral 比较有意思的是 2 点:
-
子应用角色反转:
- 以往咱们配置子应用都是在主应用里注册,piral 是真·只把主应用当成一层壳。渲染逻辑、路径、甚至菜单都可以在子应用中导出。
// 子应用的配置
import { PiletApi } from 'my-app';
import { MyPage } from './MyPage';
import { MyPageMenu } from './MyPageMenu';
export function setup(app: PiletApi) {
app.registerMenu(MyPageMenu);
app.registerPage('/my-page', MyPage);
}
-
提供上云的服务:
- 分发子应用的后端服务有吗?没有?那你别动了,我帮你得了。
-
Module Federation:去中心化心机派
心机:又有聪明、智慧的意思,形容人很实在。
Module Federation(以下简称 MF) 是 Webpack5 的新特性,相信大家对此已经有了一定的了解,官方的介绍如下:
多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。这通常被称作微前端,但并不仅限于此。
借助于 MF, 我们可以在多个独立构建的应用之间,动态地调用彼此的模块。这种运行机制可以轻松地拆分应用,真正做到跨应用的模块共享。
举例:
- App1 可以在 ModuleFederationPlugin 中定义自己应用的名称(name),暴露出的模块的入口文件名(filename)、暴露出的模块的路径(exposes)、公共依赖(shared)等信息。
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = (env = {}) => ({
// 其它 webpack 配置...
plugins: [
new ModuleFederationPlugin({
name: "app1", // 当前应用的名称,需要全局一
filename: "remoteEntry.js", // 共享模块的入口文件
library: { type: 'var', name: 'app1' }, // 共享模块的全局引用
exposes: { // 导出的模块,只有在此申明的模块才可以作为远程依赖被使用
"./Button": "./src/components/Button",
},
shared: ['vue', 'element-ui'] // 远程加载的模块对应的依赖使用本地项目的依赖
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./public/index.html"),
})
],
devServer: {
port: 3001,
}
});
- App2 可以在 ModuleFederationPlugin 中定义其它应用模块的引用(remotes),从而在项目中使用
import Button from "app1/Button"
导入远程模块。
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = (env = {}) => ({
// 其它 webpack 配置...
plugins: [
new ModuleFederationPlugin({
name: 'app2',
filename: "remoteEntry.js",
remotes: { // 引入远程应用的导出的模块, name@host address/filename.
app1: 'app1@http://localhost:3001/remoteEntry.js'
},
shared: ['vue', 'element-ui'] // 抽离的依赖与其它应用保持一致
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./public/index.html"),
})
],
});
在实现微前端上,MF 的特点是:
-
模块共享式的架构方式——每个应用既可以是暴露模块供其它应用调用的 remote,也可以是使用其它应用模块的 host。换句话说,具有去中心化的特点。
-
在
shared
中定义公共依赖库,可以避免公共依赖重复加载。缺点是:- 第一:按需加载第三方依赖比较难实现——如果各个子应用都引用了 echarts 的拓扑图组件,则会重复加载多次,因为模块联邦中的各个应用是各自打包的,没有办法综合所有应用来做 Tree shaking(仅能各自应用各自 Tree-shaking;但如果把 echarts 直接写到
shared
里,则只能全量加载,这样引用包的体积也会很大。 - 第二:在利用
shared
能力时,如果要升级主应用,所有子应用需要一起升级才能避免重复加载。 - 解决方案:利用 Module Federation 的
remotes
能力,再提供一个应用专门提供库被消费。这个应用专门用来做需要 Tree-shaking 依赖的打包,所有关于这个依赖的引用都要指向这个项目(略麻烦)。
- 第一:按需加载第三方依赖比较难实现——如果各个子应用都引用了 echarts 的拓扑图组件,则会重复加载多次,因为模块联邦中的各个应用是各自打包的,没有办法综合所有应用来做 Tree shaking(仅能各自应用各自 Tree-shaking;但如果把 echarts 直接写到
-
版本控制需要额外设计。可以考虑在运行时动态注入各个 remoteEntry 的确切地址来解决。
-
没有沙箱机制,无法做到隔离。这也是单纯使用 MF 实现微前端比较核心的缺陷。
综上,Module Federation 微前端的架构图如下所示:
暂时无法在飞书文档外展示此内容
EMP 是由欢聚时代业务中台前端团队推出的,基于 Module Federation 实现的微前端解决方案。Star 还挺高,想法比较新颖,有点儿意思。
EMP 具有以下核心特点:
-
通过 MF 实现第三方依赖共享,减少不必要的代码引入。
-
每个微应用独立部署运行,并通过 CDN 的方式引入主程序中,因此只需要部署一次,便可以提供给任何基于 Module Federation 的应用使用。并且此部分代码是远程引入,无需参与应用的打包。
-
动态更新微应用:EMP通过 CDN 加载微应用,因此每个微应用中的代码有变动时,无需重新打包发布新的整合应用便能加载到最新的微应用。
-
去中心化,每个微应用间都可以引入其他的微应用,弱化中心应用的概念。
-
跨技术栈组件式调用,提供了在主应用框架中可以调用其他框架组件的能力。
-
按需加载,开发者可以选择只加载微应用中需要的部分,而不是强制只能将整个应用全部加载。
-
应用间通信,每一个应用都可以进行状态共享,就像在使用 npm 模块进行开发一样便捷。
-
生成对应技术栈模板,它能像 create-react-app 一样,通过指令一键搭建好开发环境,减少开发者的负担。
-
远程拉取 ts 声明文件,emp-cli 中内置了拉取远程应用中代码声明文件的能力,让使用 ts 开发的开发者不再为代码报错而烦恼。
-
Component-driven:小家闺秀细腻派
前文提到,微前端的粒度并不局限于应用级。比如小家闺秀的细腻派,就集结了业界一些专注于组件级的微前端方案,待我娓娓道来一下。
Dan 神曾经在 Twitter 表示:微前端要解决的事情,在技术上用好的组件模型就能解决
Bit 在 GitHub 有相当高的热度,star 数已超过 17k,同时在业界已经得到了广泛的运用,比如 eBay,Dell,Tesla 等等大公司均使用了这一方案。
Bit 将自身定义为“为组件驱动开发而服务”,倡导开发者将开发分布到“组件”的粒度从而摆脱巨石应用;在 Bit 托管组件的复用率接近 100%,这显示出了 Component-driven 理念的生命力。
Bit 官方示意图,页面由不同的组件构成,这些组件由不同的团队在不同的 CodeBase 中开发、测试、构建,并最终集成在一起,从而实现巨石应用解耦、模块化开发
Bit 的核心产品是:
-
一个丰富的组件市场,可以在其中快速找到需要的组件,查看 UI、版本、依赖、传参等。这和 antd、element-ui 等“官方直营” UI 组件库是不同的,而更接近 Arco “民间加盟” 的 Material market。
-
便捷的 CI/CD pipeline、包含组件代码自动打包、快速发布、组件更新后同步等特性。
在微前端层面,Bit 是一个构建时集成的框架,正如前文总结,这其实是一个相当大的劣势。
Bit 微前端工作流
那怎么改进呢?有老哥发话了:
Module Federation 的作者 Zack Jackson 对 Bit 表示盛赞
“Module Federation 运行时集成的灵活优势,结合上 Bit 绝对能打的组件协作系统,那可真是被窝里放屁——能闻(文)能捂(武)了属于是。”
当然前面说的这些也很玄乎,其实 Bit 在本质上就是一个更易用、更强大、更贴合微前端场景的 npm——他哥俩都是一类“包管理工具”,而且托管在 bit.dev 网站上的组件甚至可以直接通过 npm 或者 yarn 的方式下载使用。
那么为啥要用 Bit,不直接用 npm 呢?
-
Bit 可以基于代码分析自动化代码打包
-
Bit 提供了在不离开项目上下文的情况下使用单个命令访问包代码
-
Bit 管理包之间的依赖关系和基于依赖关系的自动版本控制
-
Bit 可以让你在任何消费项目中访问包(即组件)的源代码,并直接从那里更改它
-
Bit 添加了一层开发工具,以帮助创建、隔离、测试和演示打包的组件
Open Components(以下简称 OC)是一个开源框架,可以帮助开发团队简单、快捷地部署前端组件。
OC 和 Bit 的对比如下:
Bit | OC | |
---|---|---|
工作流(抽象) | 创建组件 – > 发布组件 -> 消费组件 | 和 Bit 相同 |
操作 | Bit CLI | OC CLI |
组件市场 | 可视化的组件市场、细节丰富、文档详细、查找方便 | 无可视化市场 |
托管服务器 | Bit官方网站或自定义 | Heroku |
SSR | – | 支持 |
运行时集成 | 不支持 | 通过“运行时代理”模块,动态更新组件的版本,实现运行时集成 |
架构:
暂时无法在飞书文档外展示此内容
-
Web Components:面向未来简约派
Web Components 和基于 Web Components 的微前端方案的核心是组件化思维,其实也可以被归入“细腻派”,但由于这一派都亲自表示其有明确的师承,再给归到别的派就不合适了。
-
原生 Web Components
Web Components 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们。
Web Components 包含三项主要技术:
-
Custom elements(自定义元素) :一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
-
Shadow DOM (影子 DOM) :一组 JavaScript API,用于将封装的“影子” DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
-
HTML templates(HTML 模板) :
<template>
和<slot>
元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
Web Components 是 2011 年推出的一套 Web API,可以解决在“组件化”开发时,对框架的依赖问题。也就是浏览器的原生组件,使用时可以彻底摆脱对 React、Vue 等第三方库的依赖,简单直接,代码量小。Google 由于掌握了 Chrome 浏览器,一直在推动 Web Components 的发展。
现在相对于 React 等框架的广泛运用, Web Components 仍然算不上火,但是也已经有不少大公司使用了这项技术,比如 Twitter 的嵌入式推文、YouTube、IBM、Wikipedia 等。
通过 Web Components 实现微前端的主要特点:
-
技术栈无关:Web Components 是浏览器原生组件,在任何框架中都可以使用。
-
独立开发:使用 Web Components 开发的应用无需与其他应用间产生任何关联。
-
应用间隔离:Shadow DOM 的特性,使得各个引入的微应用间可以达到相互隔离的效果。
-
但要真正使用纯 Web Components 技术来实现微前端,困难在于:
- 重写现有的前端应用,工程量太大。
- 上下游生态系统不完善。
- 系统架构复杂,组件间通信困难。
- 替代性的选择可以是,在现有框架中引入使用 Web Components 创建的组件,这样可以渐进式地向 Web Components 方向前进。
综上所述,Web Components 是有能力以组件加载的方式将微应用整合在一起作为微前端的一种手段,但投入实践仍存在巨大困难,而且因为是浏览器的新特性,所以它的兼容性不是很好,如果有兼容性要求的项目还是无法使用,具体可点击查看。
MicroApp 是由京东推出的一款年轻的、基于类 WebComponent 进行渲染的微前端框架。据介绍,活儿相当全:
提供了JS沙箱、样式隔离、元素隔离、预加载、资源地址补全、插件系统、数据通信等一系列完善的功能。
“稳健派”会的活儿 MicroApp 会,“稳健派”不会的活儿 MicroApp 还会,总结来说,基本就是秦始皇摸电线——嬴麻了。
上面这些特性能看也能学,但都不是最重要的。老话说,擒贼先擒贼,打蛇得打蛇,了解 MicroApp,就必须要直击它的核心特点:
- 从组件化的思维实现微前端。
- 接入成本低,用
<micro-app>
标签将应用包裹:<micro-app name="app1" url="..." baseurl="/app1"></micro-app>
,即可把该应用转换为一个带生命周期、具备沙箱机制和通信功能等等特性的微前端子应用。
这两个其实是一回事,看看核心代码:
// 自定义元素
class MyElement extends HTMLElement {
// 声明需要监听的属性名,这些属性变化时会触发 attributeChangedCallback
static get observedAttributes () {
return ['name', 'url']
}
constructor() {
super();
}
connectedCallback() {
// 用 Web Components 自带的生命周期回调函数替代“老干部”们的生命周期 // 元素被插入到DOM时执行,此时去加载子应用的静态资源并渲染
console.log('micro-app is connected')
}
disconnectedCallback () {
// 生命周期
// 元素从DOM中删除时执行,此时进行一些卸载操作
console.log('micro-app has disconnected')
}
attributeChangedCallback (attr, oldVal, newVal) {
// 元素属性发生变化时执行,可以获取name、url等属性的值
console.log(`attribute ${attrName}: ${newVal}`)
}}
/**
* 注册元素
* 注册后,就可以像普通元素一样使用micro-app
* 当micro-app元素被插入或删除DOM时即可触发相应的生命周期函数
*/
window.customElements.define('micro-app', MyElement)
不得不说,还是有点东西的。
Magic Microservices 是字节开源的一款基于 Web Components 的轻量级的微前端工厂函数,口号是:
让前端的一切皆可“微”(一个函数解决微前端)。别的先不说,这个口号就是比较新颖的。
Magic Microservices 要做的事情我用最简单的话来说就是:把你用 React 、 Vue 等框架写的代码转换为基于 Web Components 的浏览器通行标签,从而在任何框架的项目中复用。
Magic 存在的价值真正意义上只是一个抹平了框架对接层的轻薄 Bridge,不论是组件的开发方,还是使用组件的项目业务方,都不需要感知具体的技术选型,只需要遵循相应的接口规范,就能实现抹平框架的复用能力。
Magic Services 对未来前端开发的设想 (DDD:领域驱动设计)
Magic Services 的使用:
import magic from '@magic-microservices/magic'
// 这个 MyModule 可以来自本地,也可以来自远程(此时可跨技术栈,是 Magic 的最大威力)
import MyModule from '/path/to/your/module'
magic('custom-component', MyModule)
// 完事儿了之后,如果本地是 React,直接这么用,岂不美哉
ReactDOM.render(
<custom-component id="123"
test
count={count}
callback={useProps(() => 'test')}
></custom-component>,
MOUNT_NODE,
);
看上面这段代码你就能发现,其实和 MicroApp 的部分思路有异曲同工之妙——都把 Web Components 用作提供给微应用的容器。具体我不细说了。
Magic Services 的注意事项(梦回“老干部派”):
// 被包裹的 module 需要导出生命周期函数
// 告诉 Web Components 在什么时候应该执行什么样的渲染
export async function bootstrap() {
...
}
export async function mount(container, props) {
ReactDOM.render(..., container);
}
export async function updated(attrName, value, container, props) {
ReactDOM.render(..., container);
}
export async function unmount() {
...
}
为啥要这么写呢?
其实是因为要和现有框架的生命周期相结合,所以 Magic Services 的生命周期系统才比 MicroApp 设计的更加严格。
但核心原理还是依赖我在 MicroApp 核心代码里写到的 Web Components 自带的生命周期回调函数——无非是把这些上面这些导出的函数放到自带的回调函数里再执行一次。
我看下来觉得 Magic Services 其实是个很有意思的方案。
-
微前端方案/产品大全
目前我见过的最全面的微前端方案列表总结,欢迎大家收藏。
-
主流&非主流产品对比
产品/方案 | 官方简介 | 官方文档 | 作者/团队 | Star | 门派(大致划分) | 应用产品 | 框架支持 | 备注 | 其它参考资料 | |
---|---|---|---|---|---|---|---|---|---|---|
1 | Bit | 一种用于构建可组合软件的构建系统 | 官网 | bit | 17.3k 🥇 | Component-driven | DELL、eBay、Tesla 等 | 不限 | 构建时集成;可以简单理解为微前端架构与 Arco Material market 的结合体 | Building Micro Frontends with Components |
2 | qiankun | 快速、简单、完整的微前端解决方案 | 官网 | 蚂蚁金服 | 15.1k 🥈 | 路由分发+资源处理 | 已经在蚂蚁内部和业界广泛应用 | 不限 | 可能是国内最有影响力且 production-ready 的微前端框架 | 可能是你见过最完善的微前端解决方案 如何设计实现微前端框架-qiankun |
3 | single-spa | 为实现前端微服务化的 js 路由 | 官网 | Joel Denning | 12.8k 🥉 | 路由分发+资源处理 | namecheap,beamery 等 | 不限 | 出现最早、影响力最大的“稳健派”微前端框架,被诸多微前端框架作为路由系统、生命周期等模块的基石 | 微前端框架 之 single-spa 从入门到精通【微前端】single-spa 到底是个什么鬼 |
4 | micro-app | 一款简约、高效、功能强大的微前端框架 | 官网 | 京东零售 | 5k | Web Components | 京东 | 不限 | 贯彻新思路的后起之秀 | 极致简洁的微前端框架-京东MicroApp开源了 |
5 | 无界 | 基于 iframe 的全新微前端方案 | 文档 | 腾讯 | 3.5k | iframe | 腾讯 | 不限 | 贯彻新思路的后起之秀 | — |
6 | Garfish | 一个强大的微前端框架 | 官网 | 字节跳动 | 2.3k | 路由分发+资源处理 | 飞书、巨量引擎等 | 不限 | production-ready 的稳健方案 | Garfish 微前端架构设计 |
7 | EMP | 下一代微前端构建方案 | 官网, Wiki | YY | 2.2k | Module Federation | 欢聚时代业务中台 | React, Vue2 | 别出心裁 | EMP-面向未来微前端方案正式开源了! |
8 | icestark | 面向大型应用的微前端解决方案 | 官网 | 阿里巴巴 | 2k | 路由分发+资源处理 | 淘宝、飞猪、饿了么等 | 不限 | production-ready 的稳健方案 | — |
9 | vite-plugin-deferation | 针对 vite & rollup 的 Module Federation | — | originjs | 1.8k | Module Federation | — | 不限 | 一款在 vite&rollup 构建工具上实现模块联邦的插件 | — |
10 | piral | 使用微前端的下一代 Web 开发框架 | 官网 | smapiot | 1.6k | 路由分发+资源处理 | smapiot、蔡司等 | 不限 | 甚至贴心的提供了上云服务,serverless 微前端 | features and alternatives |
11 | Open Components | 前端世界中的 serverless,为无痛的微前端交付而生 | 官网 | OpenTable | 1.4k | Component-driven | OpenTable,Chegg 等 | 不限 | 外国框架,中文资料几乎没有 | — |
12 | Mooa | 为 Angular 服务的微前端框架 | 文档 | 黄峰达 | 0.8k | 路由分发+资源处理 | 暂无信息 | Angular | 仅支持 Angular,代码较老,不建议花太多时间学习 | — |
13 | alfajs | 企业级的微前端解决方案 | 官网 | 阿里巴巴 | 0.8k | 路由分发+资源处理 | 阿里云控制台体系 | 不限 | — | — |
14 | frint | 为开发灵活和响应式的 Web 应用的 JS 框架 | 官网 | frintjs | 0.7k | Component-driven | 暂无信息 | Vue、React 等 | 外国框架,中文资料几乎没有 | — |
15 | luigi | 企业级的微前端框架 | 官网 | SAP | 0.7k | 路由分发+资源处理 | SAP 等 | 不限 | 用 Svelte 编写;外国框架,中文资料少 | — |
16 | ilc | 企业级框架,支持将微前端组合成支持 SSR 和 i18n 的SPA 应用 | 官网 | namecheap | 0.6k | 路由分发+资源处理 | 暂无信息 | 不限 | 少见的服务端集成的微前端框架,SEO 友好 | — |
17 | puzzle-js | 实现可扩展、高速度网站的微前端框架 | 文档 | PuzzleJs | 0.6k | 路由分发+资源处理 | 暂无信息 | 不限 | 支持服务端集成&运行时集成 | — |
18 | berial | 简单的微前端框架 | Demo | berialjs | 0.5k | Web components | 暂无信息 | 不限 | — | — |
19 | Magic Microservices | 一款基于 Web Components 的轻量级的微前端工厂函数 | 文档 | 字节跳动 | 0.5k | Web Components | 暂无信息 | 不限 | 比较新颖的方案 | 面向未来与浏览器规范的前端DDD架构设计 |
20 | Nut | 为微前端而生的框架 | 官网 | 网易考拉 | 0.1k | — | 暂无信息 | 不限 | 并非“纯正”的微前端框架 | NUT – born for microfrontends |
21 | wmadMicro未开源 | 基于 React 技术栈的中心路由基座式微前端 | 文档 | 美团 | — | 路由分发+资源处理 | 美团外卖 | React | 未开源、文档较少 | — |
22 | Bifrost 未开源 | 面向 Vue 技术栈的通用微前端框架 | 文档 | 美团 | — | 路由分发+资源处理 | 美团闪购 | Vue | 未开源、文档较老 | — |
23 | Wolf 未开源 | 企业级微前端解决方案 | 文档 | 网易严选 | — | 路由分发+资源处理 | 网易严选 | 不限 | — | — |
24 | MicroX 未开源 | — | PPT | 阿里巴巴 | — | 路由分发+资源处理 | 阿里云智能 | — | 内部产品、公开披露资料少 | — |
-
关键词
-
发展趋势 (部分)
Bit、qiankun、single-spa 在 GitHub 有着遥遥领先的关注度
-
“门派”分布
“路由分发+资源处理”是最主流的技术门派
-
面向未来的社区提案
在分析现有的微前端方案之后,让我们再看看社区对微前端的“畅想”是啥样的。
业界对于微前端的设想在两份提案中有集中体现,分别是 ECMAScript 的 ShadowRealm API(直译:影子领域)提案和由 Google 提出的的 Web API Portals 提案。
这两份提案仍在完善中,但被普遍认为是微前端的理想形态,尽管距落地还有距离(尤其是 ShadowRealm),但我们可以从中发现趋势,顺应趋势。
此外,等到提案最终实现,可能也并不是各种第三方微前端框架的终结,因为届时的框架也会在此基础上有更大的发展。
因为篇幅限制,以下我对各个方案的介绍秉承“骑自行车去酒吧,该省省该花花”的原则,多写核心亮点,少写细枝末节。
-
Overview
ShadowRealm API 提案的出发点是解决不同来源的程序在执行时产生的冲突问题。
Shadow Realms 提案提供了一种新机制,通过 API 控制 Realm 内程序的执行,并可以在新的全局对象和 JavaScript built-ins 的上下文中执行 JavaScript 代码。
在实际生产中,代码来自不同的团队、供应商、npm 包等等非常常见,尤其是在微前端的环境下。这些程序必须争夺全局共享资源,特别是共享全局对象,并且执行这些程序的副作用往往难以观察,导致不同程序之间发生冲突,并可能影响应用程序本身的完整性。
比如,某个应用在 Window
上挂载了某个变量,覆盖了另一个应用挂载的同名变量,这就会导致错误。以及,某个应用注册了 setTimeout
,或者添加了一个全局监听事件,也可能对其他应用造成影响。应用间缺乏隔离、副作用不可控,对大型微前端项目而言是不可接受的。
在了解 Shadow Realms 时,很容易联想到 iframe 和 Web Workers 的隔离机制。与 iframe 相比,Shadow Realms 的隔离机制更为严格(也因此有开发者认为其过于严格,对其适用于微前端的场景表示怀疑),具体对比如下:
iframe | Shadow Realms | |
---|---|---|
隔离 | 同源 iframe 和父窗口可以共享 contentWindow,也就是可以拿到对方的 DOM | 不支持 |
全局对象 | 同源 iframe 会创建一个新的可同步访问的全局对象 | 也会创建新的全局对象,但省去了 Web API |
性能 | 性能较差(内存& CPU ) | 性能更好(内存& CPU ) |
目前部分主流微前端框架提供的 JS 隔离能力(JS 沙箱)和 Shadow Realms 在本质上高度一致,只在实现方式和其它细节上具有差异。
-
API 示例
const red = new ShadowRealm();
globalThis.someValue = 1;
// evaluate 方法可近似理解为 eval 方法 // 但是在 ShadowRealm 环境中执行,只影响 ShadowRealm 的全局变量
red.evaluate('globalThis.someValue = 2');
console.assert(globalThis.someValue === 1); // yields true
// importValue 方法可以异步导入模块,从而在 Realm 自身的环境中执行
const setUniqueValue =
await red.importValue('./inside-code.js', 'setUniqueValue');
/* setUniqueValue = (cb) => (cb(globalThis.someValue) * 2); */ // 执行 setUniqueValue 方法时,globalThis.somevalue 的值为 2
result = setUniqueValue((x) => x ** 3);
console.assert(result === 16); // yields true
-
微前端实践
ShadowRealm 目前在微前端技术方案中的实践较少。figma 在 2019 年曾尝试使用 Realm shim(垫片)创建沙箱系统。但 TC39 提案后来将 Realm 的发展转换到了新的方向(ShadowRealm),因此这个 npm 包目前已经被废弃。
-
Overview
Portals API 的目的是让用户在浏览多页面应用程序(MPA)时,享受在不同页面间跳转的流畅体验。因为相比 SPA 页面的丝滑跳转,MPA 跳转往往会白屏一段时间。查看效果。
<portal>
和 <iframe>
类似,它们都允许在一个页面中嵌入另一个浏览上下文,并有良好的隔离机制,具体对比如下:
Portals | iframe | |
---|---|---|
创建 | <portal src="..."></portal> |
<iframe src=""></iframe> |
跳转 | 调用 activate 方法,可以跳转到 src 中的页面 |
不可跳转 |
路由 | 在 <portal> 标签里的页面操作路由,路由变化可反映到地址栏 |
路由变化无法在父、子应用中同步 |
浏览上下文 | 总是在 top-level;<portal> 标签的浏览上下文属于一个单独的浏览上下文组,所以有单独的事件循环,即使是同源 <portal> |
嵌套在树中;同源 <iframe> 与父级浏览上下文共享一个事件循环,从而才能跨 同步通信
|
通讯 | 通过 postMessage API | 通过 postMessage API 跨域通信; 在同源 iframe 中共享 contentWindow,通过 window.parent 引用父窗口对象… |
-
API 示例
/* portal 的通信机制 */
<portal src="..."></portal>
<script>
// 向 portal 元素发送消息
const portal = document.querySelector('portal');
portal.postMessage({someKey: someValue}, ORIGIN);
// 通过 window.portalHost 接收消息
window.portalHost.addEventListener('message', evt => {
const data = evt.data.someKey;
// 处理事件
});
</script>
-
微前端实践
最近的提及来自字节的开源微前端方案 Magic Microservices,其用 Magic Web Components 方案对 Portals 进行了替代式实践。
在 Puzzle 中,原生的 <portal src="..."></portal>
写法被改造为 <magic-portal manifest={useProps(manifestJson)}></magic-portal>
,对应的通信机制也仿照 Portals 进行了设计。
-
总结
qiankun、EMP、Garfish、MicroApp、Bit、无界……看完这篇文档,你可能已经被这些形形色色的方案和流派绕晕了;但我猜有一点是你确信的——微前端正迎来它最好的时代。
怎样看待这些解决方案?在我调研的过程中,发现大家的意见褒贬不一,到底是“群魔乱舞”,还是“群雄争霸”?我想这完全取决于你的视角。
如果你只着眼于每一个流派中的诸多解决方案都存在很多的相似性,那你就会觉得他们都在“重复造轮子”、“简直是浪费时间”、“KPI 驱动开发”等等。
但如果你能跳出这些相似性,则会发现每个方案或多或少都会有自己独特的亮点。指望每个创新都是颠覆性的并不现实,不积跬步,何以致千里?
况且,大家纷纷入场提出新的方案,本身就是微前端方案的不成熟导致的必然结果——换句话说,如果今天场上的玩家并不多,我们对未来微前端发展的期待又从何而来呢?
所以,我建议大家有更开放的心态!因为文章篇幅有限,欢迎大家通过我列出的 References 进一步深入学习。
-
参考文章 / Codebase
References
本文参考了大量资料,向这些作者表示感谢!部分主要文章清单如下:
Overview
-
Microfrontends.info
提案
-
How to build a plugin system on the web and also sleep well at night
-
Module Federation
原文链接:https://juejin.cn/post/7338230967390224435 作者:字节架构前端