微前端落地:一场在React项目里缝合“代码人生”的硬仗

RAG小工匠
2026-01-04 02:20
阅读 1440

早上八点,咖啡刚冲好,我坐在家里的工位上,盯着屏幕上那个已经跑了一整晚的 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。在微前端环境下,这会导致:

  1. 内存泄漏:因为 innerHTML 会丢弃原有 DOM,但 React/Vue 的事件监听器没被移除
  2. 样式冲突:它自己引入的 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,现在要统一。我们搞了个“主应用中心化鉴权”:

  1. 用户在主应用登录后,主应用通过 props 把 token 传给子应用
  2. 子应用启动时检查 props.token,如果没有就跳回主应用登录页
  3. 所有子应用的 API 请求都走主应用代理(避免 CORS)

但 Angular 那边死活不认 props,最后发现是因为它的 main.ts 没等 mount 就执行了初始化。只好在 Angular 的 app.component.ts 里加了个 @Input() token,由 qiankun 在 mount 时动态设置。


性能优化:别让微前端变成“微卡顿”

微前端最大的性能陷阱是重复加载依赖。比如主应用和子应用都用了 Lodash,浏览器会下载两份。

我们的解法:

  1. 公共依赖 external 化:在 webpack 配置里把 React, ReactDOM, Lodash 等标为 external
  2. 主应用提供全局依赖
    // main.js
    window.React = React;
    window.ReactDOM = ReactDOM;
    
  3. 子应用按需加载:只在路由匹配时才加载子应用 JS

最终效果(本地开发环境):

场景 首屏加载时间 JS Bundle Size
未拆分前(单体应用) 6.2s 4.8MB
微前端(无优化) 8.7s 6.1MB(重复依赖)
微前端(优化后) 3.9s 3.2MB

首屏快了近 2 秒!主要得益于子应用按需加载 + 依赖去重。


教程级总结:微前端落地 Checklist

如果你也在考虑微前端,这份 checklist 能帮你少熬几个通宵:

  • 明确边界:哪些功能必须拆?别为了微而微
  • 统一构建规范:至少保证所有子应用都能输出标准 UMD 包
  • 设计通信协议:主子应用间用 props 传什么?事件怎么抛?
  • 处理样式隔离strictStyleIsolation or CSS Modules + BEM?
  • 公共依赖治理:列个清单,哪些库必须版本对齐
  • 错误监控:子应用崩溃不能拖垮主应用(qiankun 的 onError 回调要用起来)
  • 本地开发体验:确保子应用能独立运行 + 联调(推荐使用 concurrently 启动多个 dev server)

最后一点真心话

微前端不是银弹,它解决的是组织架构问题,而不是技术问题。当你的团队超过 20 人,多个小组并行开发一个巨型前端时,微前端才能真正发挥价值。

上周五晚上,我终于把最后一个子应用的内存泄漏修完,部署上线。测试同学跑过来拍我肩膀:“嘿,这次居然没崩!” 我笑着喝了口冷掉的咖啡——这大概就是 代码人生 的真实写照吧:在屎山里种花,在 deadline 前狂奔,偶尔还能收获一句“没崩”。

至于那个爬虫系统?听说下个季度就要重构了。但在那之前,它还得在我们的微前端壳子里,继续抓它的数据。

(完)

P.S. 如果你在折腾微前端,欢迎来 GitHub 找我聊——我的 Claude Code 终端里常驻着 git log -pgrep -r "qiankun"

评论 0

最热最新
暂无评论
RAG小工匠Lv.1
0
影响力
0
文章
0
粉丝