微前端常见落地方案对比总结
# 导语
在大型前端项目开发中,团队常常面临巨石应用的困境。随着业务迭代,项目代码量急剧膨胀,模块间耦合度高,开发效率低下,维护成本攀升。微前端作为一种将微服务理念拓展到前端开发的架构风格,能够将庞大的单体应用拆分为多个可独立开发、测试、部署的小型应用,从根本上解决团队协作和系统维护的难题。
本文将深入对比目前业界主流的五大微前端落地方案,包括基于 iframe 的传统方案、基于路由分发的 qiankun 与 single-spa、基于构建工具的 Module Federation、组件驱动的 Bit 方案,以及基于浏览器原生标准的 Web Components 方案。通过对各方案的技术原理、核心特性、优缺点和适用场景进行全方位剖析,并重点讲解 JS 沙箱机制 和 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 年提出的微服务(Microservices)架构思想,两者在解决的问题上高度相似——随着应用规模扩大,耦合度升高,导致缺乏灵活性,难以维护。
需要特别澄清的是,微前端并非一门具体的技术,而是一套整合了技术、策略和方法的完整架构体系。它可能以脚手架、配套工具和规范约束等多种形式呈现,不同方案各有利弊,适合业务场景的就是好方案。此外,微前端本身没有技术栈约束,技术栈无关不是微前端的固有要求,各团队可以根据实际情况选择 React、Vue 或其他框架。微前端也不要求各应用必须能独立运行,拆分的粒度可以是应用级、页面级,甚至组件级。
# 微前端的核心价值与适用场景
微前端的兴起主要源于三类业务需求。遗留系统迁移是最常见的原因——许多企业存在使用 Backbone.js、Angular.js 或 Vue.js 1 等老旧技术栈构建的应用,这些应用在线上稳定运行但缺乏新功能投入,在不重写原有系统的前提下接入新业务是极其诱人的选择。后端解耦、前端聚合是第二个重要需求,特别是 To B 应用场景,用户希望像使用单一产品一样使用企业提供的多个系统,聚合成为技术趋势。第三类需求是系统需要支持动态插拔机制,具备清晰的服务边界,支持不同团队独立演进。
然而,微前端并非万能解决方案。当团队具备系统内所有架构组件的话语权、有足够动力去治理和改造整个系统、或者系统本身已是不可分离的架构量子时,引入微前端可能带来不必要的复杂性。技术团队需要在引入微前端之前,充分评估收益与成本的平衡。
# 微前端五大主流方案对比
目前业界实现的微前端主流方案多达二十余种,按照技术实现方式可以划分为五大流派。接下来我们将逐一分析各流派的代表方案、技术原理和适用场景。
# 方案一:iframe 派
iframe 是最传统也是最简单的微前端实现方式。HTML 内联框架元素 <iframe> 表示嵌套的浏览上下文,能有效地将另一个 HTML 页面嵌入到当前页面中。这种方案的核心优势体现在三个方面:即来即用,开发者可以直接使用,无需引入额外依赖;隔离完美,iframe 可以创建全新独立的宿主环境,子应用之间互不干扰;组合灵活,支持在一个页面放置多个应用。
然而,iframe 方案存在多个致命缺陷。视窗大小不同步是典型问题,例如在 iframe 内的弹窗想要居中展示就非常困难。子应用间通信受限,只能通过 postMessage 传递序列化的消息,增加了开发复杂度。性能开销显著,加载速度慢、构建 iframe 环境会导致白屏时间过长。路由状态丢失也是常见痛点,刷新页面后 iframe 的 URL 状态会丢失,用户体验不佳。
无界(Wujie) 是腾讯在 2021 年推出的创新方案,定位为「基于 iframe 的全新微前端方案」。它继承 iframe 的优点同时补足其缺点,核心思路是:利用 iframe 的隔离性把 JS 代码放到 iframe 里执行(通过 Proxy),利用 Shadow DOM 的隔离性把子应用的 DOM 写到 ShadowRoot 中实现样式隔离。无界的主要优点包括单页面中多应用同时激活、隔离机制优雅、组件式使用方便;缺点是 Shadow DOM 和 Proxy 导致兼容性一般。
# 方案二:路由分发 + 资源处理派
这一流派是业界最主流的微前端实现方式,突出特点是 production-ready,提供完整的基座应用与子应用的主从关系、路由分发机制、子应用资源处理、JS 沙箱隔离、样式隔离支持以及完善的配套体系。代表方案包括基于 single-spa 的 qiankun、字节的 Garfish、阿里巴巴的 icestark 等。
# 2.1 single-spa 原理与实战
single-spa 是这一流派的开山鼻祖,核心定位是「为实现前端微服务化的 js 路由」。它实现了主应用通过路由匹配实现对子应用生命周期的管理。
核心原理:single-spa 通过监听浏览器 URL 的变化,在路由切换时判断是否需要加载或卸载某个子应用。核心思想是将整个应用视为一个 SPA,但在内部根据路由规则加载不同的子应用。每个子应用需要导出标准的生命周期方法供主应用调用。
single-spa 的核心 API 简洁明了:
// 主工程注册子应用
singleSpa.registerApplication({
name: 'app1', // 子应用名称
app: () => System.import('app1'), // 如何加载子应用(用户自定义)
activeWhen: '/app1', // URL 匹配规则,表示何时激活这个子应用
});
// 启动主应用
singleSpa.start();
子应用需要导出三个核心生命周期方法:
// 子应用导出生命周期
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);
};
使用 single-spa-vue 封装 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;
single-spa 的优势与局限:
- 优势:轻量级(小于 5kb gzip)、框架无关、路由劫持机制完善
- 局限:不支持 JS 沙箱隔离、不支持 CSS 隔离,这也是众多框架基于它做二次封装的原因
# 2.2 qiankun 原理与实战
qiankun 是蚂蚁金服基于 single-spa 孵化的微前端实现库,定位为「快速、简单、完整的微前端解决方案」,口号是「可能是你见过最完善的微前端解决方案」。qiankun 是目前国内影响力最大的面向生产的微前端方案,也是唯一得到 single-spa 官方推荐的方案。
核心原理:qiankun 在 single-spa 基础上引入了 import-html-entry 库来加载和处理子应用的 HTML、JS、CSS 资源,并实现了完整的 JS 沙箱和 CSS 隔离机制。
主应用配置示例:
import { registerMicroApps, start } from 'qiankun';
// 注册子应用
const apps = [
{
name: 'vueApp', // 子应用名称
// 默认会加载这个 HTML,解析里面的 js 动态执行(子应用必须支持跨域)
entry: '//localhost:10000',
container: '#vue', // 容器
activeRule: '/vue', // 激活的路径
props: { // 传递属性给子应用
a: 1,
}
},
{
name: 'reactApp',
entry: '//localhost:20000',
container: '#react',
activeRule: '/react'
},
];
// 注册应用
registerMicroApps(apps);
// 启动
start({
prefetch: false, // 取消预加载
sandbox: true // 开启沙箱
});
子 Vue 应用配置:
// src/main.js
let instance = null;
function render(props) {
instance = new Vue({
router,
render: h => h(App)
}).$mount('#app');
}
// 独立运行微应用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 如果被 qiankun 使用,动态注入路径
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// 子应用的协议 - 导出供父应用调用
export async function bootstrap() {}
export async function mount(props) {
render();
}
export async function unmount(props) {
instance.$destroy();
}
子 React 应用配置:
# 2.3 Garfish 原理
# 方案三:Module Federation 派
配置示例: