微前端落地大型项目:从怀疑人生到真香现场

Spring打工人
2025-12-27 02:17
阅读 917

上周五晚上十一点半,我正戴着耳机单曲循环《Blinding Lights》敲代码,钉钉突然弹出一条消息:“微前端方案下周必须上线,双11大促要用。”我盯着屏幕愣了三秒,心里默默问候了一下产品经理全家——不是说好用 monorepo 吗?怎么又改需求了?

但转念一想,也罢。在这家公司干了三年多,每天写着差不多的业务逻辑,连 bug 都开始重复出现。最近正在看新机会,简历上写“精通微前端架构”总比“熟练使用 if-else”听起来高级点。于是咬咬牙,撸起袖子,开始了这场“大型项目微前端化”的硬核改造。


为啥非得搞微前端?

事情还得从去年说起。我们主站是一个基于 React 的巨型应用,光是 node_modules 就有 2.3GB(别问,问就是历史包袱)。前端团队分成了五个小组,每个组负责不同模块,但共用同一个 Git 仓库、同一套构建流程。结果就是:

  • 每次发布都要全员对齐,否则一个小组改了个按钮样式,另一个小组的表单就崩了;
  • 老旧模块用的是 React 16,新模块想上 React 18,升级?门都没有,光是依赖冲突就能让你原地升天;
  • 测试同学每次回归都像在玩扫雷,不知道哪块会被“意外优化”。

更离谱的是,有一次运维把 staging 环境的缓存配错了,导致用户看到的是三个月前的 UI —— 而我们花了整整两天才定位到问题不在代码里,而在 CDN 缓存策略上。

这时候,微前端的概念就像救命稻草一样飘了过来。翻了几本技术书籍,《Micro Frontends in Action》《前端架构:从入门到微前端》,越看越觉得这玩意儿简直是为我们量身定做的:独立开发、独立部署、技术栈无关——听着就爽!


选型:qiankun 还是 Module Federation?

一开始团队内部吵得不可开交。有人力推 Webpack 5 的 Module Federation,理由是“原生支持,不用引入第三方库”;另一派则坚持用 qiankun,说“社区成熟,文档齐全”。

我作为那个被迫加班的人,决定亲自下场测一把。花了一个周末,分别搭了两个 demo:

  • Module Federation:配置确实灵活,但要求所有子应用必须用 Webpack 5 构建,而我们老项目还在用 Webpack 4,迁移成本太高;
  • qiankun:虽然要加个 runtime,但对子应用侵入性极小,甚至可以用 <script> 标签加载非 React 应用。

最终拍板用了 qiankun。理由很现实:老板不给时间重构老项目


踩坑实录:那些让我想砸键盘的瞬间

坑 1:CSS 样式污染,谁动了我的按钮?

微前端最怕的就是样式冲突。主应用用的是 Ant Design 4.x,子应用 A 用了 5.x,子应用 B 干脆自己写了一套 CSS-in-JS。结果一集成,按钮字体忽大忽小,input 边框时有时无。

起初我想着靠 CSS Modules 或者 CSS Scope 解决,但 qiankun 默认并不隔离样式。后来翻官方文档才发现,可以配合 Shadow DOMCSS Prefix。但我们项目 IE11 还没完全下线(别笑,真有),Shadow DOM 直接出局。

最后采用“暴力命名空间”方案:每个子应用的根容器加一个唯一 class,比如 subapp-ordersubapp-user,然后所有样式都包在里面:

.subapp-order .ant-btn {
  /* your styles */
}

虽然丑,但有效。测试同学终于不再提“按钮变透明”这种 issue 了。

坑 2:React Context 失效,状态传不动了!

主应用用 React Context 管理用户登录态和主题色。按理说子应用挂载后应该能继承这些 context,但实际发现子应用里的 useContext(AuthContext) 拿到的是 undefined

查了半天源码,才发现 qiankun 在加载子应用时,会把子应用的 React 实例和主应用隔离开——两个 React 实例,自然共享不了 context

解决方案有两个:

  1. 把共享状态提到全局 window 上(不推荐,脏);
  2. 通过 props 传递(qiankun 支持 props 注入)。

我们选了第二种。在注册子应用时,把主应用的状态作为 props 传进去:

registerMicroApps([
  {
    name: 'order-app',
    entry: '//localhost:8081',
    container: '#subapp-container',
    activeRule: '/order',
    props: {
      user: globalStore.user,
      theme: globalStore.theme,
      onThemeChange: (theme) => globalStore.setTheme(theme)
    }
  }
]);

子应用里通过 props 拿到状态,再用 useMemo 包一层模拟 context。虽然绕了点,但至少不炸。

坑 3:路由跳转混乱,页面刷没了!

我们的主应用用 React Router v5,子应用有的用 v5,有的用 v6。当用户从主应用跳转到子应用路由时,经常出现“白屏”或“404”。

问题出在 路由同步机制 上。qiankun 默认监听 popstatehashchange,但如果你在子应用里用 history.push,主应用的路由并不会自动更新,导致刷新后找不到页面。

解决办法是统一使用 主应用的 history 实例。我们在主应用初始化时导出 history:

// main-app/history.js
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();

然后通过 props 把这个 history 传给所有子应用。子应用内部不再创建自己的 history,而是直接用传进来的:

// subapp/router.js
const App = ({ history }) => (
  <Router history={history}>
    {/* routes */}
  </Router>
);

这样一来,无论在哪跳转,URL 都由主应用统一管理,刷新也不怕了。


性能与体验:不能只顾着跑通

微前端搞定了功能,但性能差点翻车。首屏加载时间从 1.2s 暴涨到 3.8s!用户反馈“点一下卡半天”,产品经理差点把我钉在会议室墙上。

排查发现,问题出在 子应用懒加载 + 全量 JS 加载。qiankun 默认会在激活时加载整个子应用 bundle,哪怕你只用到一个页面。

我们做了三件事:

  1. 按路由拆包:子应用内部用 React.lazy + Suspense,确保只加载当前页面代码;
  2. 预加载:在用户 hover 导航菜单时,提前加载可能访问的子应用(参考淘宝做法);
  3. 资源缓存:利用 localStorage 缓存子应用的 entry HTML 和关键 JS,配合 ETag 校验。

优化后,首屏回到 1.5s,勉强能接受。

顺便提一句,千万别在子应用里用 document.title!因为多个子应用会互相覆盖。我们统一用主应用监听路由变化来设置 title。


开发体验:工具链救我狗命

微前端最大的痛点其实是本地开发。以前一个 npm start 就搞定,现在要同时启动主应用 + 三个子应用 + mock 服务,终端开了七八个 tab,内存直接飙到 16GB。

后来我写了个脚本,用 concurrently 一键启停:

{
  "scripts": {
    "dev": "concurrently \"npm run dev:main\" \"npm run dev:order\" \"npm run dev:user\""
  }
}

还配了 VS Code 的 Remote Containers,把开发环境容器化,新人入职再也不用折腾 node 版本和 Python 环境了。

调试方面,Chrome DevTools 的 Network ThrottlingCoverage 工具帮了大忙。特别是 Coverage,一眼看出哪些 JS 根本没执行,果断删掉。


效果如何?值不值得搞?

上线一个月后,数据来了:

指标 微前端前 微前端后 变化
发布频率 2次/周 15次/周 ↑650%
构建失败率 18% 3% ↓83%
跨团队协作 issue 42个/月 7个/月 ↓83%
首屏加载 1.2s 1.5s +0.3s

虽然首屏慢了点,但团队效率提升是实打实的。现在每个小组可以自由选择 React 16/17/18,甚至 Vue(真的有个小组偷偷上了 Vue 3,没人发现)。

更重要的是,线上事故少了。以前一个小组的内存泄漏能让整个站点崩溃,现在最多影响自己的子应用。


写在最后:微前端不是银弹,但值得试试

说实话,微前端不是什么高深技术,它更像是一种工程妥协——当你无法一次性重构整个系统时,用架构手段把混乱控制在局部。

如果你的项目满足以下任一条件,建议认真考虑微前端:

  • 多团队并行开发,互相阻塞严重;
  • 技术栈陈旧,但无法整体升级;
  • 需要快速集成第三方系统(比如外包团队写的模块)。

但如果你是个五人小团队,项目才写半年,别折腾了,monorepo + TypeScript + ESLint 足够你爽到明年。

至于我?简历已经更新:“主导微前端架构落地,支撑日活百万级应用”。下周一面试,希望别被问得太细……毕竟有些坑,只有深夜加班的人才懂。

对了,如果你也在搞微前端,强烈推荐那本《Micro Frontends in Action》,作者踩过的坑,我基本都复现了一遍——站在巨人的肩膀上摔跤,至少姿势好看点。

评论 0

最热最新
暂无评论
Spring打工人Lv.1
0
影响力
0
文章
0
粉丝