写过微前端的都以为 shared 依赖装了就完事了,今天 Rspack 2.0 用 tree shaking 把这件事从根上翻了
微前端团队接 Module Federation,第一件事就是配 shared:React、lodash、Ant Design 统统 singleton: true。但 v1.x 时代这只是个「假共享」——你声明共享的是整个包,运行时加载的也是整个包。三个组件用了 Ant Design,照样给你打进去 1.4MB。今天 Rspack 2.0 把这件事用 shared tree shaking 从根上翻了。
配微前端的项目,shared 配置大概长这样:
new ModuleFederationPlugin({
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
antd: { singleton: true },
"lodash-es": { singleton: true },
},
})
这个配置在 v1.x 时代的真实含义是:不管你用了几个组件,整个包都进 shared scope。Ant Design 只用了 Badge 和 Button?不好意思,1.4MB 完整加载。lodash-es 只用了 debounce?对不起,整个库都要。
这不是 bug,是 v1.x 的设计局限——shared 依赖在运行时加载,bundler 没有能力在 build 时分析「remote 实际用到了哪些导出」,所以只能整包进整包出。
Rspack 2.0 改变了这件事。
新的 treeShaking 选项让 shared 依赖在打包阶段就按需裁剪:
new rspack.container.ModuleFederationPlugin({
shared: {
"lodash-es": {
singleton: true,
treeShaking: {
mode: "runtime-infer",
// mode: "manual",
// usedExports: ["debounce"],
},
},
antd: {
singleton: true,
treeShaking: {
mode: "manual",
usedExports: ["Badge", "Button", "List"],
},
},
},
})
两种模式的区别:runtime-infer 让 Rspack 在编译时自动追踪实际用到的导出,不需要你改代码;manual 则需要你显式声明,确定性更高但要维护列表。
实战数据:日本某 SaaS 团队的真实案例,用了 Ant Design 的 3 个组件,shared 体积从 1,404KB → 344KB,减少约 75%。
原理上,Rspack 2.0 在构建 shared 产物时,会对每个 shared 依赖生成一个裁剪后的版本,只包含实际被使用的导出。运行时如果裁剪版可用则加载裁剪版,否则 fallback 到完整包,行为完全兼容。
怎么判断该不该开?
如果你团队的 shared 依赖满足以下任一条件,tree shaking 收益明显:
- Ant Design / lodash / moment 这类「重库轻用」的场景
- remote 数量多,每个 remote 只用到大库的少量 API
- 微前端应用首次加载体感慢,Network 面板里 shared chunk 占比高
如果 shared 的都是 React / ReactDOM 这种几乎全量用到的框架库,开不开差别不大。
下一步:
- 升级到 Rspack 2.0(
npm i @rspack/core@latest) - 找一台机器跑
npx rspack build,观察 output 里 shared chunk 大小 - 对体积最大的 shared 依赖开启
treeShaking.mode: "runtime-infer",再对比一次
评论区
登录后可评论。