前言
本文为性能优化系列的其中一篇文章,可以点击查看其他文章。
在现代前端开发中,构建速度和代码体积对应用的性能有着至关重要的影响。Webpack 作为最流行的模块打包工具之一,为我们提供了丰富的优化手段。通过合理的配置和插件使用,我们可以显著减少打包时间和输出文件的大小,从而提升应用的加载速度和用户体验。本文将深入探讨一些实用的 Webpack 优化策略,帮助你在开发中最大化地提升项目性能。
1. Mode
是的没错,mode也是有优化的空间的。这个mode的含义不仅限于webpack配置的mode,更在于我们应该清晰的知道我们引入的loader、plugins,哪些是只需作用于生产环境,哪些是只作用于开发环境,将他们区分开来,避免影响效率。通常的做法就是创建多个配置文件,在运行指令时通过--config
参数指定配置文件。例如创建webpack.common.js
、webpack.development.js
、webpack.production.js
分别对应通用配置、开发环境配置、生产环境配置。
下面列出一些常见的只用于生产环境的插件
TerserPlugin
: 用于压缩和优化 JavaScript 代码,减少文件体积MiniCssExtractPlugin
: 将 CSS 提取到单独的文件中,避免 CSS 代码被内联到 JavaScript 文件中CompressionWebpackPlugin
: 使用 gzip 或 Brotli 压缩输出的静态文件,以减少文件传输体积BundleAnalyzerPlugin
: 分析打包后的文件,生成报告帮助开发者了解打包体积和模块依赖。
如果你发现你的webpack配置中有这些插件作用于开发环境,请在开发环境中移除,会给你带来很多惊喜噢。
2. output
2.1 filename
output的主要优化点在于output.filename
和output.chunkFilename
,开发环境中我们推荐使用[name].js
进行文件命名,比[name].[contenthash].js
有更好的性能,更适合热更新等频繁更新的场景,且webpack自带的热更新会帮我们处理浏览器的缓存问题,绝大部分情况下都不需要担心我们改动了浏览器依旧使用缓存。
而对于生产环境,则建议使用[name].[contenthash].js
,避免更改了内容而浏览器依旧使用了缓存导致更新无效。
2.2 多入口共享依赖
默认情况下,每个入口 chunk 保存了全部其用的模块(modules)。使用 dependOn 选项你可以与另一个入口 chunk 共享模块。详细请看Dependencies
3. plugins
3.1 SpeedMeasurePlugin
SpeedMeasurePlugin 是一个测量打印每个loader、plugins耗时的插件,是在做性能优化时的好助手。 这个插件平时可以不用开启,在引入新loader、plugins时或者在做性能优化专项时启动就好。
3.2 WebpackBundleAnalyzer
WebpackBundleAnalyzer 是可以对生成物的体积大小通过treemap的形式直观的展示出来,方便做分析与优化,不推荐在平时日常开发时使用,理由同上。
3.3 EslintWebpackPlugin
包括但不限于EslintWebpackPlugin
等各种lint的webpack plugins都推荐从配置中移除,因为通常我们的IDE已经有针对各种lint开发出对应的插件辅助我们日常的开发,使用husky配合git的钩子以及CICD配置自动化流水线做检查等手段足以保证我们的代码质量,无需放到webpack中影响性能。
3.4 TerserPlugin
webpack默认的压缩插件,支持移除无用代码、压缩混淆、移除注释等功能,减少生成物的体积。只建议用于生产环境。
3.5 MiniCssExtractPlugin
将 CSS 提取到单独的文件中,避免在 JS 文件中内联 CSS。这样可以更好地利用浏览器的缓存机制,并减少主文件的大小。只建议用于生成环境。
3.6 CompressionWebpackPlugin
可以生成 Gzip 或 Brotli 格式的压缩文件,这些文件在 Web 服务器上提供给客户端时,可以显著减少传输的字节数。只建议用于生成环境。
3.7 SplitChunksPlugin
Webpack 内置的插件,用于将代码拆分成更小的块,以实现按需加载。它可以将公共模块(如依赖库)抽取出来,减少重复加载。一般只建议用于生成环境。
3.8 DllPlugin和DllReferencePlugin
webpack内置的插件,通过提前打包一些不经常更改的库文件(如 React、Vue、Lodash 等),可以加速主项目的构建时间。这些插件让你能够预先打包这些库,并在主项目中直接引用它们。
3.9 IgnorePlugin
webpack内置的插件,用于在打包过程中忽略指定的模块或文件。这可以帮助你减少打包体积、优化构建速度,尤其是在你知道某些依赖或模块在特定环境下不会被使用时。例如你确定你无需使用某些包的国际化包,那么可以配置忽略它们。
3.10 PurgeCSSPlugin
PurgeCSSPlugin 可以分析你的项目,移除未使用的 CSS,从而减少 CSS 文件的大小。PurgeCSSPlugin
3.11 ProgressPlugin
这个插件用于报告构建进度,把这个插件移除可以减少构建时间。需要你自行权衡是否使用这个插件。ProgressPlugin
4. module
4.1 noParse
防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中 不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。
4.2 loader相关
- 利用includes/excludes/test,合理配置loader生效范围,避免对所有文件进行处理
- 对于比较耗时的loader(例如babel-loader),使用
thread-loader
启用多线程并行处理,cache-loader
缓存编译结果。文档在这thread-loader,cache-loader。如果同时使用的话,cache-loader放第一,thread-loader放第二,最后再放耗时的loader,并且需要注意的是你必须要确定是耗时的loader,因为多开线程和缓存结果也是消耗资源的。 - 识别哪些loader应该适用于生产环境,哪些适用于开发环境,这里举个例子MiniCssExtractPlugin
4.3 babel-loader
babel-loader也有缓存选项,如果不想额外启用cache-loader,可以开启
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
],
exclude: /node_modules/,
}
4.4 ts-loader
如果你并非一个严格的纯typescript项目,不妨试试使用babel-loader + fork-ts-checker-webpack-plugin替代ts-loader,babel-loader 来编译 TypeScript 代码,同时通过 fork-ts-checker-webpack-plugin 在独立的进程中进行类型检查。这样可以在确保类型检查的同时,提升构建速度。
4.5 rule.type
Rule.type是webpack5提供的资源模块,它允许使用资源文件而无需额外配置loader。由于是内置的,所以免除了引入额外的loader,如file-loader
、url-loader
、raw-loader
。并且可能会有有更好的性能。推荐使用。
5. devtool
devtool控制生成source-map。需要考虑的不仅是性能,还有安全性和调试体验。所以参考这个表devtool和评估你自己的项目,选择最合适自己项目的source-map。如果还是拿不准,那么推荐开发环境用eval-source-map
,能提供最准确的调试信息,但是相对应的构建最慢。生产环境默认false,不生成source-map,避免泄露源码,但是相应的出错了调试困难。
6. resolve
- 减少 resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles 中条目数量,因为他们会增加文件系统调用的次数。
- 如果不使用 symlinks(例如 npm link 或者 yarn link),可以设置 resolve.symlinks: false。
- 如果使用自定义解析插件规则,并且没有指定上下文,可以设置 resolve.cacheWithContext: false。
7. Optimization
默认的Optimization配置已经能满足大部分中小型项目的优化需求了,比如
optimization.minimize
和optimization.minimizer
默认启用,在生产模式下会自动压缩 JavaScript 和 CSS 文件optimization.splitChunks
默认配置会自动分割代码,提取公共模块到独立的 chunksoptimization.moduleIds
和optimization.chunkIds
默认使用natural
,在大多数情况下,这已经足够好,但可以根据需求选择更稳定的deterministic
策略。
对于大型复杂项目就不太好给出标准答案,这里只简单提几点
minimizer
可以配置使用TerserPlugin
, 移除注释、debugger、console.log等不该出现在线上的代码,减少体积,通过parallel
选项启用并行,提升构建速度SplitChunks
针对自己的项目做更加细致的拆分和控制并行请求数量,可以配合WebpackBundleAnalyzer
以及lighthouse
做相对应的测试。
8. Externals
我们的项目可能在生产模式下可能引用部分CDN,那么我们不会希望在打包过程中把这些依赖打包进来。那么我们可以通过Externals来将依赖排除出去。
9. cache
cache默认是开发环境中开启memory模式,生产环境默认关闭。我们可以通过配置cache.type
来切换memory
和filesystem
模式
他们之间的区别是
memory
: 缓存保存在内存中,所以速度更快,webpack进程结束后缓存消失,不会对webpack重启构成影响。适合频繁变更构建时使用。filesystem
: 保存在磁盘中,所以构建速度稍有影响,但是因为是持久化存储,在webpack重启时依然生效。
不管生产环境还是开发环境都推荐使用filesystem
, 在二次启动时加速效果极其明显,对比memory
, 热更新场景下的影响不显著。
10. tree-shaking
10.1 usedExports
是用于标记哪些 ES6 模块的导出在最终的代码中被使用了。Webpack 会根据这些标记来移除未使用的导出,从而减少打包后的代码量,生产模式默认启用。由于 ES6 模块的静态结构特点,usedExports 只能对使用静态分析的方法标记未使用的导出,对于动态导入或复杂的使用情况可能不完全有效。可以使用/*#__PURE__*/
注释来标记函数无副作用,如无调用可以清除。
10.2 sideEffects
sideEffects 是用于指定哪些模块或文件具有副作用的配置选项。副作用是指模块或文件在导入时会产生影响(如修改全局变量、执行代码等),即使没有使用其中的导出。根据你的项目情况,合理的配置sideEffects,不正确的 sideEffects 配置可能导致必要的代码被错误移除,影响应用的正常运行。因此,在设置 sideEffects: false 时,需要确保模块或文件确实没有副作用。
总结
核心其实就几点
- 加速构建速度,手段无非就是缓存、多线程、更优的工具(版本)、少做无用功(includes/excludes)
- 降低生成物的体积
- 生成物拥有更优的缓存效果
掌握优化的思路,把握好核心要素,才能更好的针对实际项目情况做出更优的优化措施。