Webpack 插件 — SplitChunksPlugin

2,671 阅读7分钟

默认值

开箱即用的 SplitChunksPlugin 应该对大多数用户都很好用。

默认情况下,它只影响随需应变的块,因为更改初始块会影响运行项目时包含的应有脚本标记 HTML 文件。

webpack 将根据以下条件自动分割块:

  • 新块可被共享的,或者来自 node_modules 文件夹
  • 新块将大于 30kb (在 min+gz 之前)
  • 按需加载块时,并行请求的最大数量将小于或等于 5
  • 初始页面加载时并行请求的最大数量将小于或等于 3

当试图满足后两个条件时,更大的块是首选。

配置

webpack为希望对该功能有更多控制的开发人员提供了一组选项。

选择默认配置是为了适应 web 性能最佳实践,但是不同项目的最佳策略可能有所不同。如果您正在更改配置,您应该度量更改的影响,以确保有真正的好处。

optimization.splitChunks

下面这个对象表示 SplitChunksPlugin 的默认行为:

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

splitChunks.automaticNameDelimiter

string

默认情况下,webpack 将使用块的来源和名称生成名称(例如,vendors~main.js )。此选项允许您指定用于生成名称的分隔符。

译者注:来源是指,这个块是来源于哪一个入口文件,如果是多入口公用,那么名字会加上这几个入口的名字:

entry: {
    polyfill: './src/utils/polyfill.js',
    main: './src/main.js',
    app: './src/index.js',
}

三个入口均引入了 vue 模块,最后打包的结果文件为 vendors~app~main~polyfill.8fe99da0cdc25ac2ef2f.bundle

splitChunks.chunks

function (chunk) | string

该选项表示将选择哪些块进行优化。当提供一个字符串时,有效值为 allasyncinitialall 的 功能特别强大,因为它意味着即使在异步块和非异步块之间也可以共享块。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      // 包含所有类型的块
      chunks: 'all'
    }
  }
};

或者,您可以提供一个函数来进行更多的控制。返回值将指示是否包含每个块。

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks (chunk) {
        // 排除 `my-excluded-chunk`
        return chunk.name !== 'my-excluded-chunk';
      }
    }
  }
};

您可以将此配置与 HtmlWebpackPlugin 组合使用。它将为您注入所有生成的 vendor块。

splitChunks.maxAsyncRequests

number

按需加载时并行请求的最大数量。

splitChunks.maxInitialRequests

number

入口点处并行请求的最大数量。

splitChunks.minChunks

number

模块进行分割前必须共享的块的最小数量。

splitChunks.minSize

number

生成块的最小大小(以字节为单位)。

splitChunks.maxSize

number

使用 maxSize (全局:optimization.splitChunks.maxSize、每个缓存组:optimization.splitChunks.cacheGroups[x].maxSize、每个回退缓存组:optimization.splitChunks.fallbackCacheGroup.maxSize) 告诉 webpack 尝试将大于 maxSize 的块分割成更小的部分。分割出来的部分的尺寸至少为 minSize (仅次于 maxSize)。 该算法是确定性的,对模块的更改只会产生局部影响。因此,当使用长期缓存时,它是可用的,并且不需要记录。maxSize 只是一个提示,当拆分后模块大于 maxSize 或拆分会违反 minSize 时,可以不遵循 maxSize

当块已经有名称时,每个部分将从该名称派生出一个新名称。取决于 optimization.splitChunks.hidePathInfo ,它将添加从第一个模块名或它的散列派生的键。

maxSize 选项的目是用于 HTTP/2 和长期缓存。它增加了请求数,以便更好地缓存。它还可以用来减小文件大小,以便更快地重新构建。

maxSizemaxInitialRequest/maxAsyncRequests 具有更高的优先级。实际优先级是 maxInitialRequest/maxAsyncRequests < maxSize < minSize

splitChunks.name

boolean: true | function (module, chunks, cacheGroupKey) | string

分割块的名称。提供 true 将根据块和缓存组键自动生成名称。提供字符串或函数将允许您使用自定义名称。如果名称与入口点名称匹配,则将删除入口点。

建议为生产构建将 splitChunks.name 设置为 false,这样就不会不必要地更改名称。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      name (module, chunks, cacheGroupKey) {
        // generate a chunk name...
        return; //...
      }
    }
  }
};

当为不同的分割块分配相同的名称时,所有供应商模块都被放置到一个共享块中,但不建议这样做,因为这会导致下载更多的代码。

splitChunks.cacheGroups

缓存组可以继承和/或覆盖 splitChunks.* 中的任何选项。但是 testpriorityreuseExistingChunk 只能在缓存组级别配置。若要禁用任何默认缓存组,请将它们设置为 false

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: false
      }
    }
  }
};

splitChunks.cacheGroups.priority

number

一个模块可以属于多个缓存组。优化将优先选择具有较高 priority 的缓存组。默认组具有负优先级,以允许自定义组具有更高的优先级(自定义组的默认值为 0)。

optimization: {
    splitChunks: {
      //...
      cacheGroups: {
        vendors1: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        vendors2: {
          test: /[\\/]node_modules[\\/]/,
          priority: 1
        }
      }
    }
  }

node_modules 的内容会被打包到的 vendors2 中

splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk

boolean

如果当前块包含已经从主包中分离出来的模块,那么它将被重用,而不是生成一个新的模块。这可能会影响块的结果文件名。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          reuseExistingChunk: true
        }
      }
    }
  }
};

splitChunks.cacheGroups.{cacheGroup}.test

function (module, chunk) | RegExp | string

控制此缓存组选择哪些模块。省略它将选择所有模块。它可以匹配绝对模块资源路径或块名称。当匹配块名称时,将对块中的所有模块进行选择。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test(module, chunks) {
            //...
            return module.type === 'javascript/auto';
          }
        }
      }
    }
  }
};

splitChunks.cacheGroups.{cacheGroup}.filename

string

允许仅当块是初始块时重写文件名。 所有在 output.filename 可用的占位符在这里都是可用。

可以在 splitChunks.filename 进行全局设置,但不建议这样做, 如果 splitChunks.chunks 未设置为 'initial',则可能导致错误。避免全局设置。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          filename: '[name].bundle.js'
        }
      }
    }
  }
};

splitChunks.cacheGroups.{cacheGroup}.enforce

boolean: false

告诉 webpack 忽略 splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequestssplitChunks.maxInitialRequests 选项,并始终为这个缓存组创建块。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          enforce: true
        }
      }
    }
  }
};

示例

Defaults: 示例 1

// index.js

import('./a'); // 动态导入
// a.js
import 'react';

//...

Result: 将创建一个包含 react 的单独块。在导入调用时,这个块与包含 ./a 的原始块并行加载。

Why:

  • 条件 1: 块包含来自 node_modules 的模块
  • 条件 2: react 大于 30kb
  • 条件 3: 导入调用的并行请求数为 2
  • 条件 4: 在初始页面加载时不影响请求

这背后的原因是什么?react 可能不会像应用程序代码那样频繁更改。通过将它移动到一个单独的块中,这个块可以与应用程序代码分开缓存(假设您使用的是 chunkhash、records、Cache-Control 或其他长期缓存方法)。

Defaults: 示例 2

// entry.js

// 动态导入
import('./a');
import('./b');
// a.js
import './helpers'; // helpers is 40kb in size

//...
// b.js
import './helpers';
import './more-helpers'; // more-helpers is also 40kb in size

//...

Result: 将创建一个包含 ./helpers 及其所有依赖项的单独块。在导入调用时,这个块与原始块并行加载。

Why:

  • 条件 1: 这个块在两个导入调用之间共享
  • 条件 2: helpers 超过 30kb
  • 条件 3: 导入调用的并行请求数为 2
  • 条件 4: 在初始页面加载时不影响请求

helpers 的内容放入每个块中,将导致其代码被下载两次。通过使用单独的块,这种情况只会发生一次。我们支付额外请求的成本,这可以看作是一种权衡。这就是最小大小为 30kb 的原因。

Split Chunks: 示例 1

创建一个 commons 块,其中包括入口点之间共享的所有代码。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          name: 'commons',
          chunks: 'initial',
          minChunks: 2
        }
      }
    }
  }
};

此配置会扩大初始包,建议在不立即需要模块时使用动态导入。

Split Chunks: 示例 2

创建一个 vendors 块,其中包含整个应用程序中来自 node_modules 的所有代码。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  }
};

这可能导致生成一个包含所有外部包的大块。建议只包含核心框架和实用程序,并动态加载其余依赖项。

Split Chunks: 示例 3

创建一个自定义 vendor 块,其中包含与 RegExp 匹配的某些 node_modules 包。

webpack.config.js

module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendor',
          chunks: 'all',
        }
      }
    }
  }
};

这将导致将 reactreact-dom 分割成单独的块。如果不确定块中包含了哪些包,可以参考 Bundle Analysis 部分了解详细信息。