前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片NEW
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 业务场景题真实业务问题与追问
  • 查漏补缺常见问题解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
    • AI 定制路线NEW按你的简历现排
    • AI 知识地图NEW串起全站知识点
  • 动态
    • AI 热点NEWAI 每日动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
AI 助手NEW
旧版

微前端落地方案对比,qiankun 无界 Module Federation 怎么选

首页2024-02-25 16:40:12Front-End
微前端qiankunModule FederationWeb Components前端架构技术选型

一个跑了三四年的中后台,前端仓库里堆着十几个业务模块,两个组改同一个 package.json,谁升级一次 element-ui 全公司都得跟着回归。这时候有人会说,上微前端吧。可微前端不是一个包,是二十多种互相打架的实现路子,选错了就是把巨石应用换成了一堆互相依赖的碎石头。

这篇把业界主流的五个流派挨个拆开讲,原理、代码、坑点、适用规模都写清楚,最后给一条能直接照着走的选型路径,帮你在动手前先判断「这个方案到底配不配我的项目」。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 微前端到底在解决什么问题,什么情况下不该上
  • 一张图看懂主流微前端方案的运行链路
  • iframe 派、路由分发派、Module Federation 派、组件驱动派、Web Components 派的原理与取舍
  • 快照沙箱与 Proxy 沙箱的完整实现代码,以及各自的边界
  • CSS 隔离的四条路子,每条在什么场景会失效
  • 多维度对比表 + 一条可执行的选型决策路径

# 一、先想清楚要不要上微前端

微前端(Micro Frontends)这个词是 Thoughtworks 在 2016 年 11 月的 Technology Radar 里提出来的,定义是「一种由独立交付的多个前端应用组成整体的架构风格,把前端应用拆成更小、更简单、能独立开发测试部署的应用,而在用户看来仍然是内聚的单个产品」。Martin Fowler 的英文版更短:An architectural style where independently deliverable frontend applications are composed into a greater whole.

它借的是 2014 年微服务那套思路,解决的问题也高度重合,规模变大、耦合变高、改不动了。

先说结论,微前端不是一门技术,是一整套技术加策略加规范的组合。它可能表现为一个脚手架、一套构建插件、一份团队约定,不同方案各有各的取舍,适合业务场景的就是好方案。有两个常见误解需要先掰正。第一,技术栈无关不是微前端的强制要求,你完全可以做一个全 React 的微前端体系。第二,子应用也不一定要能独立运行,拆分粒度可以是应用级、页面级,甚至组件级。

那什么情况下值得上?我自己的感受是,只有三类需求撑得起这个复杂度。

遗留系统迁移是最硬的理由。手上有 Backbone.js、Angular.js 或者 Vue 1 写的老系统,线上跑得好好的,业务方不给重写预算,但新功能要用 React 18 写,这时候「不重写老系统直接接入新业务」的诱惑几乎无法拒绝。第二类是后端解耦、前端聚合,尤其 To B 场景,客户买了你五个系统,他要的是一个入口一次登录,而不是五个域名五套导航。第三类是系统需要动态插拔,服务边界清晰,不同团队要能各自演进各自发版。

反过来,如果你对系统里所有架构组件都有话语权,有人力也有动力去做统一治理,或者这个系统本身就是不可分割的一整块,那引入微前端只会白白多出一层运行时复杂度。技术团队要在动手之前先算一遍收益和成本,别被热度推着走。

# 二、主流方案的运行链路长什么样

除了 iframe 和 Module Federation,主流的运行时微前端方案链路都长得差不多,理解了这张图,后面各家的差异就只是细节:

                    ┌─────────────────────────────┐
   URL 变化  ─────► │  基座应用 (Container)        │
                    │  路由监听 / 注册表 / 公共依赖 │
                    └──────────────┬──────────────┘
                                   │ 命中 activeRule
                                   ▼
                    ┌─────────────────────────────┐
                    │  资源加载 (import-html-entry)│
                    │  取 entry HTML → 抽 JS / CSS │
                    └──────────────┬──────────────┘
                                   │
                 ┌─────────────────┴─────────────────┐
                 ▼                                   ▼
        ┌──────────────────┐               ┌──────────────────┐
        │  JS 沙箱          │               │  CSS 隔离         │
        │  Proxy / 快照     │               │  动态样式表       │
        │  劫持 window      │               │  Shadow DOM      │
        └────────┬─────────┘               └────────┬─────────┘
                 └─────────────────┬─────────────────┘
                                   ▼
                    ┌─────────────────────────────┐
                    │  子应用 mount(props)         │
                    │  Vue2 / React18 / 老 jQuery  │
                    └─────────────────────────────┘
@前端进阶之旅: 代码已经复制到剪贴板

链路上有四个必须解决的问题,各家方案的差异全在这四点上:路由怎么分发、子应用资源怎么加载、JS 全局环境怎么隔离、样式怎么不互相污染。你在评估任何一个新框架时,直接拿这四个问题去问它就行。

# 三、iframe 派,隔离最好但体验最差

<iframe> 是嵌套的浏览上下文,能把另一个 HTML 页面完整塞进当前页面。用它做微前端有三个别的方案给不了的好处。

即来即用,不引任何依赖,今天下午就能上线。隔离完美,iframe 创建的是全新的宿主环境,JS 全局变量、CSS、路由全都天然隔开,你不用写一行沙箱代码。组合灵活,一个页面里放三个 iframe 挂三个子应用,互不干扰。

问题也很直接。视窗大小不同步,iframe 里弹一个 Modal 想相对整个浏览器窗口居中,你得靠 postMessage 通知父页面来画遮罩,这个我踩过,弹层多起来之后代码会非常难维护。子应用通信受限,只能传可序列化的消息,传函数、传 Proxy 对象一律不行。性能开销显著,每个 iframe 都要重新建一整套浏览器环境,白屏时间躲不掉。路由状态丢失,用户在 iframe 里点了三层,一刷新回到首页。

无界(Wujie) 是腾讯开源的一个思路很聪明的方案,定位是「基于 iframe 的全新微前端方案」。它把 iframe 的隔离性和 Shadow DOM 的隔离性拆开用,JS 放进 iframe 里执行并通过 Proxy 接管全局对象,DOM 写进主文档的 ShadowRoot 里做样式隔离。这样既拿到了原生 JS 隔离,又绕开了 iframe 视窗和渲染层面的那些老问题。

无界的优点是单页面多应用可以同时激活、隔离机制干净、组件式接入很省事。代价是它同时依赖 Shadow DOM 和 Proxy,兼容性一般,要支持老浏览器的项目得先掂量一下。

# 四、路由分发 + 资源处理派,生产环境的主力

这一派是目前落地量最大的,突出特点就是 production-ready,基座和子应用的主从关系、路由分发、资源加载、JS 沙箱、样式隔离、配套调试工具,一整套都给你备齐了。代表是基于 single-spa 的 qiankun、字节的 Garfish、阿里的 icestark。

# 4.1 single-spa,这一派的地基

single-spa 把自己定义成「为实现前端微服务化的 js 路由」,它做的事情非常克制:监听浏览器 URL 变化,在路由切换时判断该加载还是卸载哪个子应用,然后调子应用导出的生命周期函数。

主应用侧只有两个 API,注册和启动:

// 主工程注册子应用
singleSpa.registerApplication({
    name: 'app1', // 子应用名称
    app: () => System.import('app1'), // 如何加载子应用(用户自定义)
    activeWhen: '/app1', // URL 匹配规则,表示何时激活这个子应用
});

// 启动主应用
singleSpa.start();
@前端进阶之旅: 代码已经复制到剪贴板

注意 app 这个字段,single-spa 自己不管你怎么把子应用的代码弄下来,它只要求你返回一个 Promise。这也是它和 qiankun 最大的分工差异,后面会讲到。

子应用这边要导出三个生命周期,主应用按需调用:

// 子应用导出生命周期
import SubApp from './index.tsx';

export const bootstrap = () => {
    // 初始化逻辑,只在应用首次加载时执行一次
    console.log('子应用 bootstrap');
};

export const mount = (props) => {
    // 挂载逻辑,每次应用激活时执行
    ReactDOM.render(<SubApp />, props.container);
};

export const unmount = (props) => {
    // 卸载逻辑,每次应用切换时执行
    ReactDOM.unmountComponentAtNode(props.container);
};
@前端进阶之旅: 代码已经复制到剪贴板

bootstrap 只跑一次,mount 和 unmount 每次切换都跑。写子应用时最容易出事的就是 unmount,定时器、全局事件监听、第三方 SDK 实例,这些在 unmount 里没清干净,切几次应用内存就上去了。

Vue 子应用可以用官方适配包 single-spa-vue 省掉样板代码:

// main.js
import singleSpaVue from 'single-spa-vue';

const appOptions = {
   el: '#vue',
   router,
   render: h => h(App)
}

// 在非子应用中正常挂载应用
if (!window.singleSpaNavigate) {
    delete appOptions.el;
    new Vue(appOptions).$mount('#app');
}

const vueLifeCycle = singleSpaVue({
   Vue,
   appOptions
});

// 子应用必须导出以下生命周期
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;
export default vueLifeCycle;
@前端进阶之旅: 代码已经复制到剪贴板

window.singleSpaNavigate 这个判断是关键,它让同一份代码既能被基座加载,又能自己 npm run dev 独立跑起来,日常开发不用每次都起基座。

single-spa 的账很好算。优势是轻量(gzip 后不到 5kb)、框架无关、路由劫持做得完善。局限是它不做 JS 沙箱,也不做 CSS 隔离。这就是为什么国内几乎没人直接用裸的 single-spa 上生产,大家用的都是基于它二次封装的框架。

# 4.2 qiankun,国内落地量最大的一个

qiankun 是蚂蚁基于 single-spa 孵化的实现,口号是「可能是你见过最完善的微前端解决方案」,也是 single-spa 官方推荐名单上的方案。它在 single-spa 上补了两块最缺的东西:用 import-html-entry 负责子应用 HTML/JS/CSS 资源的加载与执行,再加上一套完整的 JS 沙箱和样式隔离。

主应用配置长这样:

fe
  • 一、先想清楚要不要上微前端
  • 二、主流方案的运行链路长什么样
  • 三、iframe 派,隔离最好但体验最差
  • 四、路由分发 + 资源处理派,生产环境的主力
    • 4.1 single-spa,这一派的地基
    • 4.2 qiankun,国内落地量最大的一个
    • 4.3 Garfish,字节完全自研的那个
  • 五、Module Federation 派,构建时就把依赖接上
  • 六、Component-driven 派,把粒度做到组件级
  • 七、Web Components 派,赌浏览器原生标准
  • 八、JS 沙箱到底怎么实现的
    • 8.1 快照沙箱
    • 8.2 Proxy 代理沙箱
  • 九、CSS 隔离的四条路子
  • 十、主流方案横向对比
  • 十一、一条可执行的选型路径
  • 总结
  • 参考

← ESLint 9新特性、重大变化与从8升级的完整攻略React 18 新特性与升级指南,createRoot 自动批处理与 Suspense SSR →