构建性能
构建性能
本文包含一些有关提升构建/编译性能的实用技巧。
通用
以下最佳实践应能为您提供帮助,无论您是运行开发环境还是生产环境的构建脚本。
保持最新版本
使用最新的 webpack 版本。我们始终在进行性能改进。webpack 的最新推荐版本是:
保持 Node.js 的最新版本也有助于提升性能。此外,保持您的包管理器(例如 npm 或 yarn)为最新版本也有帮助。较新的版本可以创建更高效的模块树并提高解析速度。
Loader(加载器)
将 loader 仅应用于必要的最少模块数量。不要这样写:
js
export default {
// ...
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
},
],
},
};
而应使用 include 字段,仅对实际需要由 loader 转换的模块应用 loader:
js
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
// ...
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, "src"),
loader: "babel-loader",
},
],
},
};
引导(Bootstrap)
每个额外的 loader/plugin 都有启动时间。请尽量使用尽可能少的工具。
解析(Resolving)
以下步骤可以提高解析速度:
- 最小化
resolve.modules、resolve.extensions、resolve.mainFiles、resolve.descriptionFiles中的条目数量,因为它们会增加文件系统调用的次数。 - 如果不使用符号链接(例如
npm link或yarn link),请设置resolve.symlinks: false。 - 如果您使用非上下文相关的自定义解析插件,请设置
resolve.cacheWithContext: false。
Dll
使用 DllPlugin 将不经常变更的代码移到单独的编译中。这将提高应用程序的编译速度,尽管它会增加构建过程的复杂性。
更小 = 更快
减少编译的总大小以提高构建性能。尽量保持 chunk 较小。
- 使用更少/更小的库。
- 在多页面应用程序中使用
SplitChunksPlugin。 - 在多页面应用程序中以
async模式使用SplitChunksPlugin。 - 移除未使用的代码。
- 只编译您当前正在开发的部分代码。
工作线程池(Worker Pool)
可以使用 thread-loader 将开销较大的 loader 卸载到工作线程池中。
W> 不要使用过多的工作线程,因为 Node.js 运行时和 loader 存在启动开销。最小化工作线程与主进程之间的模块传输。IPC 的代价很高。
持久化缓存
在 webpack 配置中使用 cache 选项。在 package.json 的 "postinstall" 脚本中清除缓存目录。
T> 我们支持 yarn PnP 版本 3 yarn 2 berry 进行持久化缓存。
自定义插件/Loader
请对其进行分析,以免在此处引入性能问题。
Progress 插件
可以通过从 webpack 配置中移除 ProgressPlugin 来缩短构建时间。请记住,对于快速构建,ProgressPlugin 可能也无法提供太多价值,因此请确保您充分利用了使用它的好处。
开发环境
以下步骤在_开发_环境中特别有用。
增量构建
使用 webpack 的监听模式。不要使用其他工具来监听文件并调用 webpack。内置的监听模式会跟踪时间戳,并将此信息传递给编译过程以进行缓存失效。
在某些配置下,监听会回退到轮询模式。当监听的文件很多时,这可能导致大量的 CPU 负载。在这种情况下,您可以使用 watchOptions.poll 增加轮询间隔。
在内存中编译
以下工具通过在内存中编译和提供资源而不是写入磁盘来提高性能:
webpack-dev-serverwebpack-hot-middlewarewebpack-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变体。
T> 在大多数情况下,eval-cheap-module-source-map 是最佳选择。
避免使用生产环境专用工具
某些工具、插件和 loader 仅在生产环境构建时有意义。例如,在开发环境中使用 MinimizerPlugin 压缩和混淆代码通常没有意义。这些工具通常应在开发环境中排除:
MinimizerPlugin[fullhash]/[chunkhash]/[contenthash]AggressiveSplittingPluginAggressiveMergingPluginModuleConcatenationPlugin
极简入口 Chunk
webpack 只将更新的 chunk 写入文件系统。对于某些配置选项(HMR、output.chunkFilename 中的 [name]/[chunkhash]/[contenthash]、[fullhash]),除了更改的 chunk 之外,入口 chunk 也会失效。
通过保持入口 chunk 较小,确保其生成成本低廉。以下配置会为运行时(runtime)代码创建一个额外的 chunk,这样生成起来成本较低:
js
export default {
// ...
optimization: {
runtimeChunk: true,
},
};
避免额外的优化步骤
webpack 会执行额外的算法工作来优化输出的体积和加载性能。这些优化对于较小的代码库来说是高效的,但对于较大的代码库来说可能代价较高:
js
export default {
// ...
optimization: {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
};
输出时不包含路径信息
webpack 有能力在输出 bundle 中生成路径信息。然而,对于打包数千个模块的项目来说,这会给垃圾回收带来压力。可以通过 options.output.pathinfo 设置将其关闭:
js
export default {
// ...
output: {
pathinfo: false,
},
};
Node.js 版本 8.9.10-9.11.1
Node.js 版本 8.9.10 - 9.11.1 中,ES2015 Map 和 Set 的实现存在性能回退。webpack 大量使用这些数据结构,因此此回退会影响编译时间。
更早和更晚的 Node.js 版本不受影响。
TypeScript Loader
为了在使用 ts-loader 时缩短构建时间,请使用 transpileOnly loader 选项。单独使用此选项会关闭类型检查。要重新获得类型检查能力,请使用 ForkTsCheckerWebpackPlugin。通过将 TypeScript 类型检查和 ESLint 检查分别移到独立进程中,可以加快这些检查的速度。
js
export default {
// ...
test: /\.tsx?$/,
use: [
{
loader: "ts-loader",
options: {
transpileOnly: true,
},
},
],
};
T> ts-loader 的 GitHub 仓库中有一个完整示例。
生产环境
以下步骤在_生产_环境中特别有用。
W> 不要为了微小的性能提升而牺牲应用程序的质量! 请记住,在大多数情况下,优化质量比构建性能更重要。
Source Maps
Source map 的代价非常高。您真的需要它们吗?
特定工具问题
以下工具存在一些可能降低构建性能的问题:
Babel
- 最小化 preset/plugin 的数量
TypeScript
- 使用
fork-ts-checker-webpack-plugin在独立进程中进行类型检查。 - 配置 loader 跳过类型检查。
- 在
happyPackMode: true/transpileOnly: true模式下使用ts-loader。
Sass
node-sass存在一个会阻塞 Node.js 线程池中线程的 bug。当它与thread-loader一起使用时,请设置workerParallelJobs: 2。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
