微前端落地:一场在React项目里缝合“代码人生”的硬仗
早上八点,咖啡刚冲好,我坐在家里的工位上,盯着屏幕上那个已经跑了一整晚的 npm run build —— 是的,又是一个远程办公的清晨。作为一名Claude Code的早期尝鲜用户(别问我为啥选它,主要是看中命令行集成得贼顺手),我对技术新潮向来有点上头。但工作中嘛,还是老老实实用着 React + TypeScript 的稳如狗组合。直到去年双11前两周,产品经理拍着我肩膀说:“兄弟,我们得把三个独立系统整合成一个统一门户,月底上线。”
我当时差点把咖啡喷到键盘上。
这不是要我在现有 React 主应用里塞进两个用 Vue 写的子系统,还有一个 Angular 的老古董?更要命的是,其中一个子系统居然是用爬虫抓数据后渲染的动态页面——对,你没听错,爬虫!不是后端爬,是前端用 Puppeteer 模拟登录再注入 DOM 的那种“野路子”。整个项目活脱脱一部《代码人生》纪录片:有人写优雅 hooks,有人还在用 jQuery 拼接字符串。
于是,微前端成了唯一出路。
为什么不用 iframe?
先说结论:iframe 在大型项目里就是个“短期止痛药”。
我一开始也偷懒想直接 <iframe src="/legacy-system" />,但立马被测试同学打脸:
- SEO 几乎归零(虽然内部系统无所谓,但领导要对外演示)
- 跨 iframe 通信靠
postMessage,写起来像在调试上世纪90年代的 COM 组件 - 登录态同步?别提了,每次跳转都要重新登录,用户体验直接崩盘
- 最致命的是:性能。三个 iframe 同时加载,首屏白屏 8 秒,产品经理的脸比终端背景色还黑
所以,微前端架构(Micro Frontends)成了不得不啃的骨头。
选型:qiankun 还是 Module Federation?
市面上主流方案就俩:阿里开源的 qiankun(基于 single-spa),和 Webpack 5 原生支持的 Module Federation。
我拉了个小表格对比:
| 维度 | qiankun | Module Federation |
|---|---|---|
| 子应用隔离性 | 强(沙箱) | 弱(共享 runtime) |
| 构建侵入性 | 需改造子应用入口 | 需配置 webpack |
| JS 沙箱 | 自带 | 需手动处理 |
| CSS 隔离 | 需额外方案 | 同上 |
| 对旧项目友好度 | ⭐⭐⭐⭐⭐ | ⭐⭐(需 webpack 5) |
| React 18 支持 | 官方适配中 | 原生支持 |
我们的 Vue 和 Angular 系统连 Webpack 3 都没升级,更别说 5 了。qiankun 成了唯一现实选择。
实战:让 React 主应用“收编”三个异构子系统
第一步:主应用改造(React)
安装 qiankun:
npm install qiankun --save
在 main.js 里注册子应用:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'legacy-crawler-app',
entry: '//localhost:8081', // 爬虫系统的本地开发地址
container: '#subapp-viewport',
activeRule: '/crawler'
},
{
name: 'vue-dashboard',
entry: '//localhost:8082',
container: '#subapp-viewport',
activeRule: '/dashboard'
},
{
name: 'angular-report',
entry: '//localhost:8083',
container: '#subapp-viewport',
activeRule: '/report'
}
]);
start({
sandbox: { strictStyleIsolation: true } // 开启严格样式隔离
});
注意那个 #subapp-viewport —— 这是个空 div,所有子应用都会挂载到这里。千万别用 React 组件动态渲染容器,否则 qiankun 找不到 DOM 节点会直接报错 container not found。
第二步:子应用改造(以爬虫系统为例)
这个爬虫系统最头疼:它根本不是 SPA,而是服务端渲染 + 前端 JS 动态注入。但 qiankun 要求子应用暴露 bootstrap, mount, unmount 三个生命周期函数。
我们在它的 HTML 里加了一段“胶水代码”:
<script>
if (window.__POWERED_BY_QIANKUN__) {
// 微前端环境
window.vueApp = null;
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
export async function bootstrap() {
console.log('Crawler app bootstrap');
}
export async function mount(props) {
// 手动触发原有初始化逻辑
initCrawlerUI();
}
export async function unmount() {
// 清理定时器、事件监听
destroyCrawlerUI();
}
} else {
// 独立运行
initCrawlerUI();
}
</script>
关键点:必须判断 __POWERED_BY_QIANKUN__,否则独立开发时会报错找不到 export。
第三步:解决“爬虫”的特殊问题
这个爬虫子系统有个恶心的设计:它每隔 30 秒用 fetch 去后端拿新数据,然后用 innerHTML 直接替换某个 div。在微前端环境下,这会导致:
- 内存泄漏:因为
innerHTML会丢弃原有 DOM,但 React/Vue 的事件监听器没被移除 - 样式冲突:它自己引入的 Bootstrap 样式污染了全局
解决方案:
- 内存:在
unmount里强制清空定时器,并用MutationObserver监听 DOM 变化,手动解绑事件 - 样式:开启 qiankun 的
strictStyleIsolation,它会给子应用 DOM 加上shadow root(类似 Shadow DOM)
但注意:Shadow DOM 不支持 IE,还好我们内部系统最低要求 Chrome 80+。
踩坑实录:那些让我想砸电脑的瞬间
坑1:子应用的 publicPath 动态注入
子应用里如果有 img src="/assets/logo.png",在微前端下会请求 主应用域名/assets/logo.png 而不是子应用自己的静态资源服务器。
解决方法:在子应用入口顶部加:
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
Webpack 会自动把所有静态资源路径前缀替换成这个值。
坑2:React 18 的并发模式(Concurrent Mode)冲突
主应用升级到 React 18 后,突然发现子应用的 mount 函数被调用了两次!查了半天才发现,React 18 的 createRoot 在 StrictMode 下会 double invoke effects。
临时 workaround:在 mount 里加个防重锁:
let mounted = false;
export async function mount(props) {
if (mounted) return;
mounted = true;
// ... actual mount logic
}
官方 issue 里说 qiankun v3 会修复,但现在只能这么凑合。
坑3:登录态共享
三个系统原本各自维护 cookie,现在要统一。我们搞了个“主应用中心化鉴权”:
- 用户在主应用登录后,主应用通过
props把 token 传给子应用 - 子应用启动时检查
props.token,如果没有就跳回主应用登录页 - 所有子应用的 API 请求都走主应用代理(避免 CORS)
但 Angular 那边死活不认 props,最后发现是因为它的 main.ts 没等 mount 就执行了初始化。只好在 Angular 的 app.component.ts 里加了个 @Input() token,由 qiankun 在 mount 时动态设置。
性能优化:别让微前端变成“微卡顿”
微前端最大的性能陷阱是重复加载依赖。比如主应用和子应用都用了 Lodash,浏览器会下载两份。
我们的解法:
- 公共依赖 external 化:在 webpack 配置里把 React, ReactDOM, Lodash 等标为 external
- 主应用提供全局依赖:
// main.js window.React = React; window.ReactDOM = ReactDOM; - 子应用按需加载:只在路由匹配时才加载子应用 JS
最终效果(本地开发环境):
| 场景 | 首屏加载时间 | JS Bundle Size |
|---|---|---|
| 未拆分前(单体应用) | 6.2s | 4.8MB |
| 微前端(无优化) | 8.7s | 6.1MB(重复依赖) |
| 微前端(优化后) | 3.9s | 3.2MB |
首屏快了近 2 秒!主要得益于子应用按需加载 + 依赖去重。
教程级总结:微前端落地 Checklist
如果你也在考虑微前端,这份 checklist 能帮你少熬几个通宵:
- 明确边界:哪些功能必须拆?别为了微而微
- 统一构建规范:至少保证所有子应用都能输出标准 UMD 包
- 设计通信协议:主子应用间用 props 传什么?事件怎么抛?
- 处理样式隔离:
strictStyleIsolationor CSS Modules + BEM? - 公共依赖治理:列个清单,哪些库必须版本对齐
- 错误监控:子应用崩溃不能拖垮主应用(qiankun 的
onError回调要用起来) - 本地开发体验:确保子应用能独立运行 + 联调(推荐使用
concurrently启动多个 dev server)
最后一点真心话
微前端不是银弹,它解决的是组织架构问题,而不是技术问题。当你的团队超过 20 人,多个小组并行开发一个巨型前端时,微前端才能真正发挥价值。
上周五晚上,我终于把最后一个子应用的内存泄漏修完,部署上线。测试同学跑过来拍我肩膀:“嘿,这次居然没崩!” 我笑着喝了口冷掉的咖啡——这大概就是 代码人生 的真实写照吧:在屎山里种花,在 deadline 前狂奔,偶尔还能收获一句“没崩”。
至于那个爬虫系统?听说下个季度就要重构了。但在那之前,它还得在我们的微前端壳子里,继续抓它的数据。
(完)
P.S. 如果你在折腾微前端,欢迎来 GitHub 找我聊——我的 Claude Code 终端里常驻着
git log -p和grep -r "qiankun"。

评论 0