微前端
What — 是什么
微前端将大型前端应用拆分为多个独立的小应用,各自开发、部署和运行,在运行时组合为统一体验。
核心概念:
- 主应用(Container/Shell):负责加载和编排子应用
- 子应用(Micro App):独立开发、独立部署的功能模块
- JS 沙箱:隔离子应用的 JS 执行环境(Proxy 沙箱 / Snapshot 沙箱)
- 样式隔离:Shadow DOM / CSS Modules / CSS Scope / 动态加载卸载
主流方案:
| 方案 | 原理 | 框架耦合 | 沙箱 | 适用场景 |
|---|---|---|---|---|
| qiankun | HTML Entry + Proxy 沙箱 | 不耦合 | 完善 | 大部分场景 |
| Module Federation | Webpack 5 模块共享 | 不耦合 | 无(共享运行时) | Webpack 项目 |
| single-spa | 路由级应用挂载 | 不耦合 | 需自建 | 定制化需求 |
| 无框架 | Web Components | 不耦合 | Shadow DOM | 轻量场景 |
关键特性:
- 子应用独立仓库、独立部署、独立技术栈
- 运行时动态加载,不需要重新部署主应用
- qiankun 的 HTML Entry 方式接入成本最低
Why — 为什么
适用场景:
- 大型项目多团队并行开发
- 渐进式技术栈迁移(老项目 jQuery → 新项目 React)
- 独立部署,互不影响
何时不需要:
- 单团队中小项目
- 统一技术栈且应用不复杂
- 团队没有微前端运维经验
优缺点:
- ✅ 优点:
- 独立开发部署,团队自治
- 技术栈无关,可渐进迁移
- 局部故障不影响全局
- ❌ 缺点:
- 架构复杂度大幅增加
- 公共依赖重复加载
- 样式隔离不完美
- 调试困难(跨应用问题)
How — 怎么用
快速上手(qiankun)
主应用:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app-react',
entry: '//localhost:3001',
container: '#subapp-container',
activeRule: '/react',
},
{
name: 'app-vue',
entry: '//localhost:3002',
container: '#subapp-container',
activeRule: '/vue',
},
]);
start();
子应用(React):
// 子应用入口导出生命周期
export async function bootstrap() {}
export async function mount(props) {
ReactDOM.render(<App />, props.container.querySelector('#root'));
}
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container.querySelector('#root'));
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
ReactDOM.render(<App />, document.getElementById('root'));
}
代码示例
Module Federation(Webpack 5):
// app1: 暴露组件
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./UserCard': './src/components/UserCard',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});
// app2: 消费组件
new ModuleFederationPlugin({
name: 'app2',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});
// app2 中使用
const UserCard = React.lazy(() => import('app1/UserCard'));
主子应用通信:
// 主应用通过 props 传递
registerMicroApps([{
name: 'app-sub',
entry: '//localhost:3001',
container: '#container',
activeRule: '/sub',
props: {
mainStore, // 共享状态
globalEvent, // 通信事件
userInfo, // 用户信息
},
}]);
// 子应用 mount 中接收
export async function mount(props) {
props.onGlobalStateChange((state) => {
console.log('主应用状态变更:', state);
});
}
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 样式污染 | 子应用全局 CSS 影响其他应用 | qiankun 严格模式 / CSS Modules / Shadow DOM |
| 公共依赖重复 | 每个子应用独立打包 React 等 | Module Federation shared / CDN externals |
| 子应用路由冲突 | 多个应用路由模式冲突 | 统一用 history 模式,activeRule 精确匹配 |
| 本地开发跨域 | 子应用端口不同 | 子应用配置 CORS,开发环境用代理 |
最佳实践
- 优先用 Module Federation(Webpack 5 项目),次选 qiankun
- 公共依赖(React/Vue)用 externals 或 shared 共享
- 子应用避免全局样式,用 CSS Modules/Scoped CSS
- 主应用统一管理用户认证和路由
面试题
Q1: qiankun、Module Federation、single-spa 三种微前端方案各有什么特点?
qiankun 基于 HTML Entry + Proxy 沙箱,接入成本最低,沙箱隔离完善,适合大部分场景;Module Federation 基于 Webpack 5 模块共享,无沙箱但共享运行时,适合 Webpack 项目间组件共享;single-spa 是底层路由级挂载框架,灵活但需自建沙箱,适合定制化需求。
Q2: JS 沙箱隔离有哪些实现方式?
两种主流方式:Proxy 沙箱(qiankun 默认)通过
Proxy代理window对象,子应用对全局变量的读写被拦截和隔离,每个子应用有独立的fakeWindow;Snapshot 沙箱在子应用挂载前快照window状态,卸载时恢复,兼容性好但无法支持多实例并行。
Q3: 微前端的样式隔离有哪些方案?各有什么局限?
Shadow DOM 隔离最彻底,但弹窗挂载到
body时样式丢失且不支持全局 CSS 变量;CSS Modules/Scoped CSS 通过唯一选择器前缀隔离,需构建工具配合;qiankun 严格模式在子应用卸载时移除其样式标签。目前没有完美方案,实践中常组合使用。
Q4: 微前端的公共依赖如何处理?
三种方案:Module Federation 的
shared配置,运行时共享单个 React/Vue 实例;externals+ CDN 引入,所有子应用从同一 CDN 地址加载公共库;qiankun 的prefetch预加载 + 浏览器缓存复用。需确保公共库只加载一个版本,避免多实例冲突。
相关链接: