# 共享依赖 Tree Shaking ## 背景 在使用 { props.name || 'Module Federation' } 时,我们常常通过 `shared` 配置来共享通用依赖(如 antd, React 等),以减少重复打包和提升应用间的一致性。然而,传统 `shared` 机制存在一个痛点:它总是会打包并提供**完整的**依赖包,即便你的应用实际只用到了其中的一小部分功能(例如,只用了一个 antd 的 `Button` 组件)。 这会导致: - **构建产物体积过大**:不必要的代码被打包,增加了最终产物的体积。 - **运行时性能损耗**:浏览器需要下载并执行更多非必需的 JavaScript,延长了页面加载和渲染时间。 **Shared Tree Shaking** 正是为了解决这一问题而生。它能够智能地分析你的代码,精确识别出实际被使用的模块导出(exports),并仅打包这部分代码。最终,你的应用将获得一个经过“摇树”优化、体积更小的共享依赖。 :::tip 核心收益 - **更小的构建体积**:从源头上减少不必要的代码,大幅缩减产物尺寸。 - **更快的加载速度**:用户只需下载真正需要的代码,加速页面响应。 - **更优的渲染性能**:减少浏览器解析和执行的 JavaScript,提升运行时体验。 ::: ## 快速上手 为你的项目开启 Shared Tree Shaking 非常简单,只需在配置文件中添加 `treeShaking` 选项即可。 ### 示例 ```ts title="rspack.config.ts" export default { //... plugins: [ new ModuleFederationPlugin({ //... shared: { 'antd': { treeShaking: { mode: 'runtime-infer' } } } }) ] }; ``` ### 本地验证 在本地构建后,观测对应的共享依赖(antd)产物体积以及导出内容,期望的内容是只包含使用的模块。 ## 配置项 核心配置为 [sharedItem.treeShaking](/zh/configure/shared.md#treeshaking),你也可以通过设置以下配置来控制 Tree Shaking 产物路径: - [treeShakingDir](/zh/configure/treeShakingDir.md) - [injectTreeShakingUsedExports](/zh/configure/injectTreeShakingUsedExports.md) - [treeShakingSharedPlugins](/zh/configure/treeShakingSharedPlugins.md) - [treeShakingSharedExcludePlugins](/zh/configure/treeShakingSharedExcludePlugins.md) ## 模式切换 我们提供了两种模式来以适应不同团队和项目的需求。 ### runtime-infer `runtime-infer` 是一种轻量级的、无需中心化服务支持的策略。 - **如何启用**:将 `mode` 设置为 `runtime-infer` 。 - **适用场景**: - 本地开发环境,快速验证效果。 - 没有部署或 CI 服务的团队。 - 规模较小、消费者单一的项目。 - **工作逻辑**:运行时,如果当前页面需要的功能已经被某个已加载的、经过 Tree Shaking 的共享包所满足,则直接复用。否则,**回退加载完整的依赖包**,以确保功能完备。 - **提升复用率**:由于缺少全局视角,`runtime-infer` 的复用率可能不高。你可以通过手动在配置中补充 `usedExports` 列表,提前声明可能会用到的模块,从而引导编译器生成更优化的共享包,提升复用效率。 ### server-calc `server-calc` 是**强烈推荐的最佳实践**。它通过中心化的服务来最大化 Tree Shaking 的收益,实现全局最优。 {!props.name && ( <CreateSharedTreeShakingDeployServer /> )} ## 验证与回退 ### 如何确认 Tree Shaking 生效? 1. **网络面板检查**:在浏览器开发者工具的“网络 (Network)”面板中,筛选加载的 JS 文件。确认加载的是二次构建生成的、带有特定标识(如 `secondary` 或哈希值)的优化包,而不是原始的全量包。其体积也应该显著小于全量包。 2. **产物内容比对**:直接下载并查看加载的共享依赖 JS 文件内容。搜索你**未使用**的组件或函数名(例如,你只用了 `Button`,可以去搜索 `Modal`),如果搜索不到,说明它们已被成功摇掉。 3. **Chrome DevTools 验证**:在 Chrome DevTools 的「Shared」面板中选中目标共享依赖,查看其状态标签:出现 `Tree Shaking Loaded` 表示 Tree Shaking 生效并加载了裁剪产物;仅有 `Loaded` 则表示回退为全量包加载;`Tree Shaking Loading` 表示正在按 Tree Shaking 路径加载。 ![](https://module-federation-assest.netlify.app/document/guide/performance/shared-tree-shaking/tree-shaking-devtool.jpg) ### 如何安全回退? 共享依赖 Tree Shaking 的设计包含了**自动安全回退**机制。如果运行时检测到任何问题(如 Snapshot 未下发、网络错误、版本不匹配等),它会默认加载全量的共享依赖包,确保应用的稳定性。 如果你需要手动禁用此功能,只需在部署服务中停止二次构建和 Snapshot 更新流程即可。 {props.secondaryBuild || React.createElement(SecondaryBuild)} ## 常见问题 (FAQ) **1. Shared Tree Shaking 能和 `eager: true` 一起使用吗?** **不能。** `eager: true` 会将共享依赖直接打包进应用入口文件 (initial chunk),这与 Tree Shaking 按需动态加载的机制是互斥的。你需要在这两者之间做出取舍: - 若共享依赖体积不大,且追求极致的初始加载速度,可考虑 `eager: true`。 - 若共享依赖体积庞大(如组件库),强烈建议**关闭 `eager`**,使用 Tree Shaking 来获得显著的体积与性能收益。 **2. 在 `runtime-infer` 模式下,单例依赖需要注意什么?** 需要特别小心。如果一个共享依赖被配置为 `singleton: true`(必须全局单例),可能会出现以下场景: - 应用 A 只用到了 `antd` 的 `Button`,加载了**自己**的树摇包。 - 应用 B 用到了 `antd` 的 `Modal`,它会加载**自己**的全量包。 此时,页面上会同时存在两个不同版本的 `antd` 实例(一个极简版,一个完整版),这会破坏单例模式,可能导致样式冲突、状态不共享、甚至应用崩溃。 **建议**:对于必须保持单例的库,**优先使用 `server-calc` 策略**,确保所有消费者都使用同一个经过全局优化的共享包。如果只能使用 `runtime-infer`,请通过补充 `usedExports` 尽可能地让生成的包更完整,以降低冲突风险。 **3. Tree Shaking 的命中条件是什么?** 主要依赖于构建工具的静态分析能力。代码必须采用 ES Module (`import`/`export`) 语法,以便编译器能够分析出哪些导出被使用了。CommonJS (`require`/`module.exports`) 模块通常无法被有效 Tree Shaking。 **4. 它是否会破坏已发布项目的共享依赖?** **不会。** Tree Shaking 的数据源和加载路径与原有的共享机制是隔离的,并且有严格的命中条件和安全回退逻辑。它不会影响已经稳定运行的老项目。 ## 延伸阅读 - [共享依赖 Tree Shaking 工作原理](https://github.com/module-federation/core/discussions/4302)