# 如何正确地进行分包

> 为什么需要进行分包，一个大的 bundle.js 不好吗？ 极其不建议，可从两方面进行考虑: 1. 一行代码将导致整个 bundle.js 的缓存失效 1. 一个页面仅仅需要 bundle.js 中 1/N 的代码，剩下代码属于其它页面，完全没有必要加载 > webpack(或其他构建工具) 运行时代码不容易变更，需要单独抽离出来，比

- 发布于: 2019-08-03
- 更新于: 2026-09-30
- 原文: https://shanyue.tech/posts/frontend-engineering/chunk-spliting/
- 标签: frontend-engineering
- 专栏: 前端工程化

---

## 为什么需要分包？

_为什么需要进行分包，一个大的 `bundle.js` 不好吗？_

极其不建议，可从两方面进行考虑:

1. 一行代码将导致整个 `bundle.js` 的缓存失效
1. 一个页面仅仅需要 `bundle.js` 中 1/N 的代码，剩下代码属于其它页面，完全没有必要加载

## 如何更好的分包？

### 打包工具运行时

> webpack(或其他构建工具) 运行时代码不容易变更，需要单独抽离出来，比如 `webpack.runtime.js`。由于其体积小，**必要时可注入 `index.html` 中**，减少 HTTP 请求数，优化关键请求路径

### 前端框架运行时

> React(Vue) 运行时代码不容易变更，且每个组件都会依赖它，可单独抽离出来 `framework.runtime.js`。请且注意，**务必将 React 及其所有依赖(react-dom/object-assign)共同抽离出来**，否则有可能造成性能损耗，见下示例

_假设仅仅抽离 React 运行时(不包含其依赖)为单独 Chunk_，且每个路由页面为单独 Chunk。_某页面不依赖任何第三方库_，则该页面会加载以下 Chunk

1. `webpack.runtime.js` 5KB ✅
2. `framework.runtime.js` 30KB ✅
3. `page-a.chunk.js` 50KB ✅
4. `vendor.chunk.js` 50KB ❌ (因 webpack 依赖其 `object-assign`，而 `object-assign` 将被打入共同依赖 `vendor.chunk.js`，因此此时它必回加载，但是该页面并不依赖任何第三方库，完全没有必要全部加载 `vendor.chunk.js`)

将 React 运行时及其所有依赖，共同打包，修复结果如下，拥有了更完美的打包方案。

1. `webpack.runtime.js` 5KB ✅
2. `framework.runtime.js` 40KB ✅ (+10KB)
3. `page-a.chunk.js` 50KB ✅

### 高频库

一个模块被 N(2个以上) 个 Chunk 引用，可称为公共模块，可把公共模块给抽离出来，形成 `vendor.js`。

问：那如果一个模块被用了多次 (2次以上)，但是该模块体积过大(1MB)，每个页面都会加载它(但是无必要，因为不是每个页面都依赖它)，导致性能变差，此时如何分包？

答：如果一个模块虽是公共模块，但是该模块体积过大，可直接 `import()` 引入，异步加载，单独分包，比如 `echarts` 等

问：如果公共模块数量多，导致 vendor.js 体积过大(1MB)，每个页面都会加载它，导致性能变差，此时如何分包

答：有以下两个思路

1. 思路一: 可对 vendor.js 改变策略，比如被引用了十次以上，被当做公共模块抽离成 verdor-A.js，五次的抽离为 vendor-B.js，两次的抽离为 vendor-C.js
1. 思路二: 控制 vendor.js 的体积，当大于 100KB 时，再次进行分包，多分几个 vendor-XXX.js，但每个 vendor.js 都不超过 100KB

## 使用 webpack 分包

在 webpack 中可以使用 [SplitChunksPlugin](https://webpack.js.org/plugins/split-chunks-plugin) 进行分包，它需要满足三个条件:

1. minChunks: 一个模块是否最少被 minChunks 个 chunk 所引用
1. maxInitialRequests/maxAsyncRequests: 最多只能有 maxInitialRequests/maxAsyncRequests 个 chunk 需要同时加载 (如一个 Chunk 依赖 VendorChunk 才可正常工作，此时同时加载chunk数为 2)
1. minSize/maxSize: chunk 的体积必须介于 (minSize, maxSize) 之间

以下是 `next.js` 的默认配置，可视作最佳实践

> [源码位置: next/build/webpack-config.ts](https://github.com/vercel/next.js/blob/v12.0.5-canary.10/packages/next/build/webpack-config.ts#L728)

```js
{
  // Keep main and _app chunks unsplitted in webpack 5
  // as we don't need a separate vendor chunk from that
  // and all other chunk depend on them so there is no
  // duplication that need to be pulled out.
  chunks: (chunk) =>
    !/^(polyfills|main|pages\/_app)$/.test(chunk.name) &&
    !MIDDLEWARE_ROUTE.test(chunk.name),
  cacheGroups: {
    framework: {
      chunks: (chunk: webpack.compilation.Chunk) =>
        !chunk.name?.match(MIDDLEWARE_ROUTE),
      name: 'framework',
      test(module) {
        const resource =
          module.nameForCondition && module.nameForCondition()
        if (!resource) {
          return false
        }
        return topLevelFrameworkPaths.some((packagePath) =>
          resource.startsWith(packagePath)
        )
      },
      priority: 40,
      // Don't let webpack eliminate this chunk (prevents this chunk from
      // becoming a part of the commons chunk)
      enforce: true,
    },
    lib: {
      test(module: {
        size: Function
        nameForCondition: Function
      }): boolean {
        return (
          module.size() > 160000 &&
          /node_modules[/\\]/.test(module.nameForCondition() || '')
        )
      },
      name(module: {
        type: string
        libIdent?: Function
        updateHash: (hash: crypto.Hash) => void
      }): string {
        const hash = crypto.createHash('sha1')
        if (isModuleCSS(module)) {
          module.updateHash(hash)
        } else {
          if (!module.libIdent) {
            throw new Error(
              `Encountered unknown module type: ${module.type}. Please open an issue.`
            )
          }

          hash.update(module.libIdent({ context: dir }))
        }

        return hash.digest('hex').substring(0, 8)
      },
      priority: 30,
      minChunks: 1,
      reuseExistingChunk: true,
    },
    commons: {
      name: 'commons',
      minChunks: totalPages,
      priority: 20,
    },
    middleware: {
      chunks: (chunk: webpack.compilation.Chunk) =>
        chunk.name?.match(MIDDLEWARE_ROUTE),
      filename: 'server/middleware-chunks/[name].js',
      minChunks: 2,
      enforce: true,
    },
  },
  maxInitialRequests: 25,
  minSize: 20000,
}
```
