拆包最佳实践

良好的拆包策略对于提升应用的加载性能是十分重要的,可以充分利用浏览器的缓存机制,减少请求数量,加快页面加载速度。

在 Rsbuild 中内置了多种拆包策略,可以满足大部分应用的需求,你也可以根据自己的使用场景,自定义拆包配置。

拆包策略

Rsbuild 的拆包配置集中在 performance.chunkSplit 中。

Rsbuild 支持设置以下几种拆包策略:

  • split-by-experience: 根据经验制定的拆分策略,自动将一些常用的 npm 包拆分为体积适中的 chunk。
  • split-by-module: 按 NPM 包的粒度拆分,每个 NPM 包对应一个 chunk。
  • split-by-size:根据模块大小自动进行拆分。
  • all-in-one: 将所有代码全部打包到一个 chunk 中。
  • single-vendor: 将所有 NPM 包的代码打包到一个单独的 chunk 中。
  • custom: 自定义拆包配置。

split-by-experience

分包策略

Rsbuild 默认采用 split-by-experience 策略,这是我们根据经验制定的策略。具体来说,当你的项目中引用了以下 npm 包时,它们会自动被拆分为单独的 chunk:

  • lib-polyfill.js:包含 core-js@swc/helperstslib
  • lib-axios.js:包含 axios 以及相关的包。

这种方式将常用的包进行分组,然后拆分为单独的 chunk,有助于浏览器缓存。

配置

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-experience',
    },
  },
};

注意事项

  • 如果项目中没有安装或引用以上 npm 包,则不会生成相应的 chunk。

split-by-module

分包策略

将每一个 NPM 包拆分为一个单独的 chunk。

配置

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-module',
    },
  },
};

注意事项

  • 这个配置会最细化地拆分 node_modules,产生大量的文件请求。
  • 在使用 HTTP/2 时,由于存在多路复用,会加快资源的加载时间,并提高缓存命中率。
  • 在未使用 HTTP/2 时,由于 HTTP 队头阻塞问题,会导致页面加载性能下降,请谨慎使用。

all-in-one

分包策略

此分包策略将所有源代码和第三方依赖打包在同一个 chunk 中。

配置

export default {
  performance: {
    chunkSplit: {
      strategy: 'all-in-one',
    },
  },
};

注意事项

  • 这个配置会将构建生成的 JS 代码全部打包到一个文件里(除了 dynamic import 拆分的 chunk)。
  • 单个 JS 文件的体积可能会非常大,使页面加载性能下降。

如果你需要将 dynamic import 拆分的 chunk 也打包到单个文件中,可以将 Rspack 的 output.asyncChunks 选项设置为 false

export default defineConfig({
  performance: {
    chunkSplit: {
      strategy: 'all-in-one',
    },
  },
  tools: {
    rspack: {
      output: {
        asyncChunks: false,
      },
    },
  },
});

single-vendor

分包策略

此分包策略将第三方依赖打包在一个 chunk 中,源代码打包在另外的 chunk 中。

配置

export default {
  performance: {
    chunkSplit: {
      strategy: 'single-vendor',
    },
  },
};

注意事项

  • 单个 vendor 文件的体积可能会非常大,使页面加载性能下降。

split-by-size

分包策略

该策略下,设置 minSizemaxSize 为一个固定值后,Rsbuild 会自动进行拆分,无需干预。

配置

export default {
  performance: {
    chunkSplit: {
      strategy: 'split-by-size',
      minSize: 30000,
      maxSize: 50000,
    },
  },
};

自定义拆包

除了使用内置的拆包策略外,你也可以通过 Rsbuild 自定义拆包功能来满足更多的定制化需求。自定义拆包分为两部分:

  • 自定义拆包分组
  • 自定义 Rspack 的 splitChunks 配置

值得注意的是,这两种自定义拆包能力可以和内置的拆包策略一起使用,也就是说,你可以使用内置的拆包策略来拆分常用的包,然后再使用自定义拆包功能来拆分其他的包。

自定义分组

Rsbuild 支持自定义拆包分组,这样比内置拆包策略更灵活,同时比手写 Rspack 的 splitChunks 配置更简单。

比如将 node_modules 下的 axios 库拆分到 axios.js 中:

export default {
  performance: {
    chunkSplit: {
      forceSplitting: {
        axios: /node_modules[\\/]axios/,
      },
    },
  },
};

通过 forceSplitting 配置,你可以很方便把某些模块拆分为一个 chunk。

注意事项

通过 forceSplitting 配置拆分的 chunk 会通过 <script> 标签插入到 HTML 文件中,作为首屏请求的资源。因此,请根据实际场景来进行适当地拆分,避免首屏资源体积过大。

自定义拆包配置

你可以通过 override 配置项来自定义 Rspack 的 splitChunks 配置,比如:

  • 设置 minSize 为 30000,表示当模块大小小于 30000 字节时,不进行拆分。
export default {
  performance: {
    chunkSplit: {
      override: {
        chunks: 'all',
        minSize: 30000,
      },
    },
  },
};
  • 将所有 CSS 文件打包到一个 styles.css 中。
export default {
  performance: {
    chunkSplit: {
      override: {
        cacheGroups: {
          styles: {
            name: 'styles',
            minSize: 0,
            chunks: 'all',
            test: /\.(?:css|less|sass|scss|styl)$/,
            priority: 99,
          },
        },
      },
    },
  },
};

其中,override 配置会与 Rspack 的 splitChunks 配置进行合并,具体配置项请参考 Rspack - splitChunks

使用 Dynamic Import 拆包

除了 chunkSplit 配置,使用 dynamic import 拆包也是一项重要的优化手段,它可以有效减少首屏的包体积。

关于 dynamic import

Dynamic import 是 ECMAScript 2020 引入的一个新特性,它允许你动态地加载一些 JavaScript 模块。Rsbuild 底层的 Rspack 默认支持 dynamic import,所以你可以直接在代码中使用它。

当打包工具遇到 import() 语法时,它会自动将相关的代码分割成一个新的 chunk,并在运行时按需加载。

例如,项目中有一个大的模块 bigModule.ts(也可以是一个第三方依赖),你可以使用 dynamic import 来按需加载它:

// 在某个需要使用 bigModule 的地方
import('./bigModule.ts').then((bigModule) => {
  // 使用 bigModule
});

当你运行构建命令时,bigModule.ts 就会被自动分割成一个新的 chunk,并在运行时按需加载。