构建性能

本指南包含一些提高构建/编译性能的实用技巧。


通用

无论您是在开发环境还是生产环境运行构建脚本,以下最佳实践都应有所帮助。

保持最新

使用最新版本的 webpack。我们一直在进行性能改进。最新推荐的 webpack 版本是

latest webpack version

保持 Node.js 最新也有助于提高性能。除此之外,保持您的包管理器(例如 npmyarn)最新也有助于提升性能。新版本会创建更高效的模块树并提高解析速度。

加载器

将加载器应用于最少数量的必要模块。而不是

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
      },
    ],
  },
};

使用 include 字段只将加载器应用于那些实际需要通过它进行转换的模块

const path = require('path');

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'),
        loader: 'babel-loader',
      },
    ],
  },
};

启动

每个额外的加载器/插件都有一个启动时间。尽量少使用工具。

解析

以下步骤可以提高解析速度

  • 最小化 resolve.modulesresolve.extensionsresolve.mainFilesresolve.descriptionFiles 中的项数,因为它们会增加文件系统调用次数。
  • 如果您不使用符号链接(例如 npm linkyarn link),请设置 resolve.symlinks: false
  • 如果您使用不特定于上下文的自定义解析插件,请设置 resolve.cacheWithContext: false

Dlls

使用 DllPlugin 将不常更改的代码移动到单独的编译中。这将提高应用程序的编译速度,尽管它确实增加了构建过程的复杂性。

更小 = 更快

减小编译的总大小以提高构建性能。尽量保持块小。

  • 使用更少/更小的库。
  • 在多页应用程序中使用 SplitChunksPlugin
  • 在多页应用程序中以 async 模式使用 SplitChunksPlugin
  • 移除未使用的代码。
  • 只编译您当前正在开发的代码部分。

工作线程池

thread-loader 可用于将昂贵的加载器分载到工作线程池。

持久缓存

在 webpack 配置中使用 cache 选项。在 package.json"postinstall" 脚本中清除缓存目录。

自定义插件/加载器

对它们进行性能分析,以避免在此处引入性能问题。

进度插件

通过从 webpack 配置中移除 ProgressPlugin,可以缩短构建时间。请记住,ProgressPlugin 对于快速构建可能也提供不了太多价值,因此请确保您正在利用使用它的好处。


开发

以下步骤在开发环境中特别有用。

增量构建

使用 webpack 的观察模式。不要使用其他工具来观察您的文件并调用 webpack。内置的观察模式将跟踪时间戳并将此信息传递给编译,以进行缓存失效。

在某些设置中,观察模式会回退到轮询模式。当有许多文件被观察时,这会导致大量的 CPU 负载。在这种情况下,您可以使用 watchOptions.poll 增加轮询间隔。

内存编译

以下实用程序通过在内存中编译和提供资源而不是写入磁盘来提高性能

  • webpack-dev-server
  • webpack-hot-middleware
  • webpack-dev-middleware

stats.toJson 速度

Webpack 4 默认会通过其 stats.toJson() 输出大量数据。除非在增量步骤中必要,否则请避免检索 stats 对象的某些部分。v3.1.3 之后的 webpack-dev-server 包含一个实质性的性能修复,以最小化每个增量构建步骤从 stats 对象中检索的数据量。

Devtool

请注意不同 devtool 设置之间的性能差异。

  • "eval" 具有最佳性能,但对转译后的代码没有帮助。
  • 如果可以接受稍微差一点的映射质量,cheap-source-map 变体性能更高。
  • 对于增量构建,请使用 eval-source-map 变体。

避免生产环境专用工具

某些实用程序、插件和加载器仅在构建生产环境时才有意义。例如,在开发过程中使用 TerserPlugin 压缩和混淆代码通常没有意义。这些工具通常应在开发环境中排除

  • TerserPlugin
  • [fullhash]/[chunkhash]/[contenthash]
  • AggressiveSplittingPlugin
  • AggressiveMergingPlugin
  • ModuleConcatenationPlugin

最小入口块

Webpack 只会向文件系统发出更新的块。对于某些配置选项(HMR、output.chunkFilename 中的 [name]/[chunkhash]/[contenthash][fullhash]),除了已更改的块之外,入口块也会失效。

通过保持入口块小来确保其发出开销低。以下配置为运行时代码创建了一个额外的块,因此生成开销很低

module.exports = {
  // ...
  optimization: {
    runtimeChunk: true,
  },
};

避免额外的优化步骤

Webpack 会进行额外的算法工作,以优化输出的大小和加载性能。这些优化对于较小的代码库性能良好,但对于较大的代码库可能会付出高昂的代价

module.exports = {
  // ...
  optimization: {
    removeAvailableModules: false,
    removeEmptyChunks: false,
    splitChunks: false,
  },
};

不带路径信息的输出

Webpack 能够在输出包中生成路径信息。然而,这会给捆绑数千个模块的项目带来垃圾回收压力。在 options.output.pathinfo 设置中关闭此功能

module.exports = {
  // ...
  output: {
    pathinfo: false,
  },
};

Node.js 版本 8.9.10-9.11.1

Node.js 版本 8.9.10 - 9.11.1 中的 ES2015 MapSet 实现存在性能退化。Webpack 大量使用这些数据结构,因此此退化会影响编译时间。

更早和更晚的 Node.js 版本不受影响。

TypeScript 加载器

为了在使用 ts-loader 时提高构建时间,请使用 transpileOnly 加载器选项。此选项本身会关闭类型检查。要重新获得类型检查,请使用 ForkTsCheckerWebpackPlugin。这通过将 TypeScript 类型检查和 ESLint linting 移至单独的进程来加速它们。

module.exports = {
  // ...
  test: /\.tsx?$/,
  use: [
    {
      loader: 'ts-loader',
      options: {
        transpileOnly: true,
      },
    },
  ],
};

生产

以下步骤在生产环境中特别有用。

Source Maps

Source maps 开销非常大。您真的需要它们吗?


特定工具问题

以下工具存在某些可能降低构建性能的问题

Babel

  • 最小化预设/插件的数量

TypeScript

  • 使用 fork-ts-checker-webpack-plugin 在单独的进程中进行类型检查。
  • 配置加载器以跳过类型检查。
  • happyPackMode: true / transpileOnly: true 模式下使用 ts-loader

Sass

  • node-sass 有一个 bug,会阻塞 Node.js 线程池中的线程。当与 thread-loader 一起使用时,请设置 workerParallelJobs: 2

6 贡献者

sokratbroadleybyzykmadhavarshneywizardofhogwartsanikethsaha