写过微前端的都以为 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 这种几乎全量用到的框架库,开不开差别不大。

下一步:

  1. 升级到 Rspack 2.0(npm i @rspack/core@latest
  2. 找一台机器跑 npx rspack build,观察 output 里 shared chunk 大小
  3. 对体积最大的 shared 依赖开启 treeShaking.mode: "runtime-infer",再对比一次

评论区

0 条评论

登录后可评论。

阿柯·前端架构 10 阅读