导航
导航
文章目录󰁋
  1. 导语
  2. 什么是微前端
  3. 微前端的核心价值与适用场景
  4. 微前端五大主流方案对比
    1. 方案一:iframe 派
    2. 方案二:路由分发 + 资源处理派
      1. 2.1 single-spa 原理与实战
      2. 2.2 qiankun 原理与实战
      3. 2.3 Garfish 原理
    3. 方案三:Module Federation 派
    4. 方案四:Component-driven 派
    5. 方案五:Web Components 派
  5. JS 沙箱机制详解
    1. 快照沙箱
    2. Proxy 代理沙箱
    3. qiankun 的沙箱实现
  6. CSS 隔离方案详解
    1. 子应用之间样式隔离
    2. 主应用与子应用之间的样式隔离
      1. 方案一:BEM 命名规范
      2. 方案二:CSS Modules
      3. 方案三:Shadow DOM
      4. 方案四:CSS-in-JS
      5. qiankun 的样式隔离
  7. 主流方案综合对比
  8. 选型建议与最佳实践
  9. 总结
NEW
🚀

前端系统进阶指南

系统化学习前端知识

关注公众号

公众号:前端进价之旅

微前端常见落地方案对比总结

导语

在大型前端项目开发中,团队常常面临巨石应用的困境。随着业务迭代,项目代码量急剧膨胀,模块间耦合度高,开发效率低下,维护成本攀升。微前端作为一种将微服务理念拓展到前端开发的架构风格,能够将庞大的单体应用拆分为多个可独立开发、测试、部署的小型应用,从根本上解决团队协作和系统维护的难题。

本文将深入对比目前业界主流的五大微前端落地方案,包括基于 iframe 的传统方案、基于路由分发的 qiankunsingle-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)架构思想,两者在解决的问题上高度相似——随着应用规模扩大,耦合度升高,导致缺乏灵活性,难以维护。

需要特别澄清的是,微前端并非一门具体的技术,而是一套整合了技术、策略和方法的完整架构体系。它可能以脚手架、配套工具和规范约束等多种形式呈现,不同方案各有利弊,适合业务场景的就是好方案。此外,微前端本身没有技术栈约束,技术栈无关不是微前端的固有要求,各团队可以根据实际情况选择 ReactVue 或其他框架。微前端也不要求各应用必须能独立运行,拆分的粒度可以是应用级、页面级,甚至组件级。

微前端的核心价值与适用场景

微前端的兴起主要源于三类业务需求。遗留系统迁移是最常见的原因——许多企业存在使用 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-spaqiankun、字节的 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();
}
// vue.config.js
module.exports = {
devServer: {
port: 10000,
headers: {
'Access-Control-Allow-Origin': '*' // 允许跨域
}
},
configureWebpack: {
output: {
library: 'vueApp',
libraryTarget: 'umd' // 打包成 UMD 格式
}
}
};

子 React 应用配置

// index.js
function render() {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
}

// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render();
}

// 子应用协议
export async function bootstrap() {}
export async function mount() {
render();
}
export async function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
// config-overrides.js
module.exports = {
webpack: (config) => {
config.output.library = 'reactApp';
config.output.libraryTarget = "umd";
config.output.publicPath = 'http://localhost:20000/';
return config;
},
devServer: function (configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.headers = {
"Access-Control-Allow-Origin": "*",
};
return config;
};
},
};

2.3 Garfish 原理

Garfish 是字节跳动完全自研的微前端框架,与 qiankun 的不同在于它没有基于 single-spaimport-html-entry,完全自研。其主要特点包括:

  • 支持任意框架技术体系接入
  • 强大的预加载能力(自动记录用户应用加载习惯增加加载权重)
  • 支持依赖共享,降低整体包体积
  • 支持多实例能力(页面中同时运行多个子应用)
  • 提供业务插件满足定制需求

在样式隔离方面,Garfish 处理得更为细致,劫持各种事件和资源,把插入的样式全部收集起来统一隔离管理,同时也支持 Shadow DOM。

方案三:Module Federation 派

Module Federation(以下简称 MF)是 Webpack 5 引入的新特性,官方定义是「多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署。这通常被称作微前端,但并不仅限于此。」这一流派的核心特点是去中心化——每个应用既可以是暴露模块供其它应用调用的 remote,也可以是使用其它应用模块的 host。

配置示例

// App1 - 提供方
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
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: "./public/index.html",
})
],
devServer: {
port: 3001
}
};
// App2 - 消费方
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app2',
filename: "remoteEntry.js",
remotes: {
// 引用远程模块
app1: 'app1@http://localhost:3001/remoteEntry.js'
},
shared: ['vue', 'element-ui']
}),
],
};

// 使用远程模块
import Button from 'app1/Button';

EMP(Enterprise Micro Frontends) 是欢聚时代业务中台前端团队基于 Module Federation 实现的微前端解决方案,核心特点包括:通过 MF 实现第三方依赖共享、每个微应用独立部署运行并通过 CDN 引入主程序、动态更新微应用、去中心化弱化中心应用概念、跨技术栈组件式调用。

方案四:Component-driven 派

这一流派将微前端的粒度从应用级延伸到了组件级,代表方案是 Bit。Bit 在 GitHub 上有超过 17k 的 star,已被 eBay、Dell、Tesla 等大公司采用。

方案五:Web Components 派

MicroApp 是京东推出的基于类 WebComponent 进行渲染的微前端框架,接入成本极低:

// 只需用标签包裹即可
<micro-app name="app1" url="http://localhost:3000" baseurl="/app1"></micro-app>

Magic Microservices 是字节跳动开源的基于 Web Components 的轻量级微前端工厂函数。

JS 沙箱机制详解

JS 沙箱机制是微前端方案的核心技术之一,用于隔离不同子应用之间的 JavaScript 执行环境,防止子应用对全局环境造成污染。

快照沙箱

快照沙箱的原理是在应用激活时遍历 Window 上的变量并存储为一个”快照”,应用卸载时再次遍历对比,将不同的变量存储并将 Window 恢复。其核心实现如下:

class SnapshotSandbox {
constructor() {
this.proxy = window;
this.modifyPropsMap = {}; // 记录修改的属性
this.active(); // 初始化时激活
}

// 激活:记录当前 window 快照
active() {
this.windowSnapshot = {};
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
// 将 window 上的属性进行拍照
this.windowSnapshot[prop] = window[prop];
}
}
// 恢复之前修改的属性
Object.keys(this.modifyPropsMap).forEach(p => {
window[p] = this.modifyPropsMap[p];
});
}

// 失活:记录变更并还原 window
inactive() {
for (const prop in window) {
if (window.hasOwnProperty(prop)) {
// 将上次拍照的结果和本次 window 属性做对比
if (window[prop] !== this.windowSnapshot[prop]) {
// 保存修改后的结果
this.modifyPropsMap[prop] = window[prop];
// 还原 window
window[prop] = windowSnapshot[prop];
}
}
}
}
}

使用示例

let sandbox = new SnapshotSandbox();

((window) => {
window.a = 1;
window.b = 2;
window.c = 3;
console.log(window.a, window.b, window.c); // 1, 2, 3
sandbox.inactive(); // 卸载,window 恢复原状
console.log(window.a, window.b, window.c); // undefined, undefined, undefined
})(sandbox.proxy);

局限性:快照沙箱只能针对单实例应用场景,如果是多个实例同时挂载的情况则无法解决,这时只能通过 Proxy 代理沙箱来实现。

Proxy 代理沙箱

Proxy 代理沙箱通过创建独立的代理对象来隔离每个子应用的执行环境,每个应用都在自己的代理对象上操作,不会影响全局 window。

class ProxySandbox {
constructor() {
const rawWindow = window;
const fakeWindow = {};
const proxy = new Proxy(fakeWindow, {
set(target, p, value) {
target[p] = value;
return true;
},
get(target, p) {
// 优先从 fakeWindow 取,否则从真实 window 取
return target[p] || rawWindow[p];
}
});
this.proxy = proxy;
}
}

// 使用示例
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();

window.a = 1; // 全局变量

((window) => {
window.a = 'hello';
console.log(window.a); // hello(只在 sandbox1 中)
})(sandbox1.proxy);

((window) => {
window.a = 'world';
console.log(window.a); // world(只在 sandbox2 中)
})(sandbox2.proxy);

console.log(window.a); // 1(全局 window 未受影响)

优势:每个应用都创建独立的 Proxy 代理,好处是每个应用都是相对独立的,不需要直接更改全局的 window 属性,可以支持多实例同时运行。

qiankun 的沙箱实现

qiankun 同时支持快照沙箱和 Proxy 沙箱两种模式,通过配置选择:

start({
sandbox: {
// true 开启代理沙箱(默认),'legacy' 开启快照沙箱
strictStyleIsolation: true, // 开启严格的样式隔离
experimentalStyleIsolation: true // 实验性的样式隔离
}
});

Proxy 沙箱的优势在于天然支持多实例运行(一个页面同时挂多个子应用),对副作用的隔离控制权更高。

CSS 隔离方案详解

CSS 隔离是微前端的另一个核心技术难题,主要包括子应用之间的样式隔离和主应用与子应用之间的样式隔离。

子应用之间样式隔离

Dynamic Stylesheet(动态样式表) 是目前主流的解决方案。当应用切换时,移除老应用的样式表,再添加新应用的样式表,保证在一个时间点内只有一个应用的样式表生效。

// qiankun 中的实现原理
function patchLooseStyle(appName) {
const styles = document.querySelectorAll('style');
styles.forEach(style => {
// 判断样式属于哪个子应用
const app = style.getAttribute('data-webpack');
if (app && app !== appName) {
style.disabled = true;
}
});
}

主应用与子应用之间的样式隔离

方案一:BEM 命名规范

通过约定项目前缀来避免样式冲突:

/* 主应用 */
.main-app-button { }

/* 子应用 */
.child-app-button { }

方案二:CSS Modules

打包时生成不冲突的选择器名:

// webpack 配置
{
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}

编译后的 class 名:

.App__button--abc12 { }

方案三:Shadow DOM

Shadow DOM 可以实现真正意义上的样式隔离,其内部的元素始终不会影响到它的外部元素:

<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM 隔离</title>
</head>
<body>
<div id="container"></div>
<script>
// 创建 Shadow DOM
let shadowDOM = document.getElementById('container').attachShadow({mode: 'open'});

// 添加样式(隔离在 Shadow DOM 内部)
let styleEle = document.createElement('style');
styleEle.textContent = `
p { color: red; }
.title { font-size: 20px; }
`;

// 添加内容
let pEle = document.createElement('p');
pEle.textContent = 'Hello Shadow DOM';
pEle.className = 'title';

// 追加到 Shadow DOM
shadowDOM.appendChild(styleEle);
shadowDOM.appendChild(pEle);
</script>

<!-- 全局样式不会影响 Shadow DOM 内部 -->
<p>这是外部的 p,不会被影响</p>
</body>
</html>

注意:React 和 Vue 中的弹框等组件通常挂载到 body 上,使用 Shadow DOM 会导致样式无法应用到这些全局元素。

方案四:CSS-in-JS

使用 CSS-in-JS 方案如 styled-components、emotion 等,可以将样式封装在组件级别:

import styled from 'styled-components';

const Button = styled.button`
background: blue;
color: white;
padding: 10px 20px;
`;

// 每个组件的样式都是独立的

qiankun 的样式隔离

qiankun 支持两种样式隔离方式:

  1. 动态样式表隔离:切换应用时移除旧应用的样式
// 内部实现简化版
function handleStyleElement(styleElement, appName) {
if (styleElement._use) {
// 禁用其他应用的样式
document.querySelectorAll('style[data-single]').forEach(el => {
if (el !== styleElement) {
el.disabled = true;
}
});
}
}
  1. Shadow DOM 隔离(严格模式):
start({
sandbox: {
strictStyleIsolation: true // 开启 Shadow DOM 隔离
}
});

主流方案综合对比

下表从多个关键维度对比各主流微前端方案:

方案 团队 GitHub Star JS 沙箱 CSS 隔离 多实例支持 框架无关 独立部署 适用场景
qiankun 蚂蚁金服 15.1k ✅ Proxy/快照 企业级中后台应用
single-spa Joel Denning 12.8k 微前端入门基础
MicroApp 京东 5k 需快速接入场景
无界 腾讯 3.5k ✅ Proxy ✅ ShadowDOM 对隔离性要求高
Garfish 字节跳动 2.3k 大型复杂应用
EMP YY 2.2k 部分 同技术栈应用
icestark 阿里巴巴 2k 阿里生态应用
Bit Bit 17.3k 组件复用优先
Module Federation Webpack - 构建时集成

选型建议与最佳实践

微前端选型需要综合考虑多个维度。框架限制是首要因素——如果后台只有一个前端框架,可选范围更大;如果需要兼容遗留系统,则需要选择跨技术栈能力强的方案。浏览器兼容性也很关键,如果需要支持 IE,很多现代方案将受限。依赖独立需求决定了各子应用的依赖是统一管理还是独立管理,统一管理可解决重复加载问题,独立管理会带来额外流量开销。

对于中大型企业级应用,推荐 qiankunGarfish,两者都经过大量生产环境验证,配套体系完善。对于遗留系统迁移场景,无界和 MicroApp 的接入成本较低。对于组件复用优先的场景,Bit 提供了更细粒度的解决方案。对于同技术栈团队且主要诉求是构建优化的场景,EMP 和 Module Federation 提供了更轻量的选择。

最后需要强调的是,一切技术选型都是权衡。微前端带来了系统复杂度提升、性能开销(体积增大)、额外问题的不可预见性等副作用。技术团队应该根据业务实际需求而非技术热度来做决策,避免「热闹驱动开发」。只有适用于业务场景的方案才是最好的方案。

总结

本文系统梳理了微前端五大主流落地方案的核心原理和特性。iframe 方案简单直接但体验欠佳;qiankun 作为国内最成熟的方案提供了完整的沙箱和隔离机制;Module Federation 提供了构建时集成的新思路;Web Components 代表了面向未来的浏览器原生标准;Bit 则将微前端粒度延伸到组件级别。

在技术选型时,团队应该重点关注四个方面:业务是否真正需要微前端(避免过度设计)、各方案的隔离能力和性能开销是否可接受、技术团队是否具备维护能力、方案是否有完善的生态和社区支持。微前端架构虽然在带来灵活性的同时增加了系统复杂度,但只要运用得当,它能够有效支撑大规模前端项目的团队协作和持续演进。

支持一下
扫一扫,支持poetries
  • 微信扫一扫
  • 支付宝扫一扫