Webpack 打包速度与文件体积的平衡优化
2024-05-225.7k 阅读
Webpack 打包速度优化
1. 升级 Webpack 和相关插件
Webpack 自身在不断发展,新版本通常在性能上有优化。同时,一些常用插件如 babel - loader
、css - loader
等也需要及时更新。例如,Webpack 5 相较于 Webpack 4 在打包速度上有显著提升,其内置了更好的持久化缓存机制。
// package.json
{
"devDependencies": {
"webpack": "^5.0.0",
"webpack - cli": "^4.0.0",
"babel - loader": "^8.0.0"
}
}
通过 npm install
或 yarn install
安装新版本依赖,这有助于利用最新的性能优化成果。
2. 优化 loader 配置
- 减少 loader 执行次数:
- 配置
include
和exclude
选项,让 loader 只处理特定目录下的文件。例如,babel - loader
只需要处理src
目录下的 JavaScript 文件。
module.exports = { module: { rules: [ { test: /\.js$/, use: 'babel - loader', include: path.resolve(__dirname,'src'), exclude: /node_modules/ } ] } };
- 这样可以避免对庞大的
node_modules
目录进行不必要的处理,大大减少babel - loader
的执行次数,提高打包速度。
- 配置
- 优化 loader 性能:
- 对于
babel - loader
,可以启用缓存。在webpack.config.js
中配置如下:
module.exports = { module: { rules: [ { test: /\.js$/, use: { loader: 'babel - loader', options: { cacheDirectory: true } }, include: path.resolve(__dirname,'src'), exclude: /node_modules/ } ] } };
cacheDirectory
选项会启用缓存,babel 将编译结果缓存到文件系统中。下次编译相同文件时,直接从缓存中读取,而不需要重新编译,显著提升编译速度。
- 对于
3. 合理使用插件
- TerserPlugin 优化:
TerserPlugin
是 Webpack 内置的用于压缩 JavaScript 的插件。在 Webpack 5 中,默认启用了parallel
选项,它会并发运行代码压缩,利用多核 CPU 的优势提高压缩速度。但如果项目中 CPU 资源有限,可以适当调整parallel
的值。
const TerserPlugin = require('terser - webpack - plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ parallel: true, // 根据 CPU 核心数调整,默认自动检测 terserOptions: { compress: { drop_console: true // 移除 console.log 等语句,进一步优化体积 } } }) ] } };
- html - webpack - plugin 优化:
- 这个插件用于生成 HTML 文件并自动注入打包后的 JavaScript 和 CSS 文件。如果项目中有多个 HTML 页面,可以为每个页面单独配置
html - webpack - plugin
实例,避免不必要的重复操作。
const HtmlWebpackPlugin = require('html - webpack - plugin'); module.exports = { plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html' }), new HtmlWebpackPlugin({ template: './src/about.html', filename: 'about.html' }) ] };
- 这样每个 HTML 页面都能针对性地进行处理,而不是所有页面共用一个配置导致不必要的资源处理。
- 这个插件用于生成 HTML 文件并自动注入打包后的 JavaScript 和 CSS 文件。如果项目中有多个 HTML 页面,可以为每个页面单独配置
4. 配置 resolve
- 优化 alias:
- 使用
alias
可以为常用模块路径设置别名,减少 Webpack 查找模块的时间。例如,项目中经常引用src/components
目录下的组件,可以这样配置:
module.exports = { resolve: { alias: { '@components': path.resolve(__dirname,'src/components') } } };
- 这样在代码中引用组件时就可以使用
@components
别名,如import Button from '@components/Button';
,Webpack 能更快地找到模块位置。
- 使用
- 减少 resolve.extensions:
resolve.extensions
用于配置 Webpack 在解析模块时尝试的文件扩展名。尽量减少这个数组中的扩展名数量,因为 Webpack 会按照顺序逐一尝试这些扩展名来查找模块。例如,如果项目主要使用.js
和.jsx
文件,可以这样配置:
module.exports = { resolve: { extensions: ['.js', '.jsx'] } };
- 避免像
['.js', '.json', '.jsx', '.css', '.less', '.scss']
这样包含过多扩展名的配置,减少不必要的查找时间。
5. 启用多进程打包
- thread - loader:
thread - loader
可以将耗时的 loader 操作分配到多个子进程中并行执行。它需要在webpack.config.js
中配置在其他 loader 之前。例如:
module.exports = { module: { rules: [ { test: /\.js$/, use: [ 'thread - loader', 'babel - loader' ], include: path.resolve(__dirname,'src'), exclude: /node_modules/ } ] } };
thread - loader
会根据 CPU 核心数自动分配任务,在多核 CPU 的机器上能显著提升打包速度。不过要注意,进程间通信会有一定开销,对于小型项目可能提升效果不明显,甚至可能会因为开销导致打包变慢。
6. 分析打包结果
- 使用 Webpack Bundle Analyzer:
- 安装
webpack - bundle - analyzer
插件:npm install --save - dev webpack - bundle - analyzer
。 - 在
webpack.config.js
中配置:
const BundleAnalyzerPlugin = require('webpack - bundle - analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin() ] };
- 运行 Webpack 打包后,它会打开一个浏览器窗口,以可视化的方式展示打包后的文件体积分布。可以直观地看到哪些模块体积较大,哪些模块引用过多,从而针对性地进行优化。例如,如果发现某个第三方库体积过大,可以考虑是否有更小的替代品,或者是否可以只引入部分功能。
- 安装
Webpack 文件体积优化
1. 代码分割
- 动态导入(import()):
- 使用 ES2020 的动态导入语法
import()
可以实现代码的按需加载,从而将大的 JavaScript 文件分割成多个小的 chunk。例如,在一个大型的单页应用中,可能有一些路由组件不需要在首页加载时就全部引入。
const routes = [ { path: '/home', component: () => import('./components/Home') }, { path: '/about', component: () => import('./components/About') } ];
- 这样在 Webpack 打包时,
Home
和About
组件会被单独打包成各自的 chunk 文件。当用户访问对应的路由时,才会加载相应的 chunk,减少了初始加载的文件体积。
- 使用 ES2020 的动态导入语法
- splitChunks 配置:
splitChunks
是 Webpack 中用于代码分割的强大配置选项。它可以将第三方库、公共模块等提取出来,避免在多个 chunk 中重复包含。
module.exports = { optimization: { splitChunks: { chunks: 'all', minSize: 30000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: '~', name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } } };
chunks: 'all'
表示对所有类型的 chunk 都进行分割;minSize
设置分割的最小文件大小;cacheGroups
可以定义不同的缓存组,如vendors
组专门处理node_modules
中的第三方库,将它们提取到一个单独的文件中,减少主 bundle 的体积。
2. 优化图片资源
- 使用 image - webpack - loader:
- 安装
image - webpack - loader
:npm install --save - dev image - webpack - loader
。 - 在
webpack.config.js
中配置:
module.exports = { module: { rules: [ { test: /\.(png|jpg|jpeg|gif)$/i, use: [ { loader: 'image - webpack - loader', options: { mozjpeg: { progressive: true, quality: 65 }, // optipng.enabled: false will disable optipng optipng: { enabled: false }, pngquant: { quality: [0.65, 0.90], speed: 4 }, gifsicle: { interlaced: false }, // the webp option will enable WEBP webp: { quality: 75 } } } ] } ] } };
- 这个 loader 可以在打包时对图片进行压缩,通过调整各种图片格式的压缩参数,如
mozjpeg
的quality
,pngquant
的quality
和speed
等,可以在保证图片质量可接受的前提下,显著减小图片文件的体积。
- 安装
- 采用合适的图片格式:
- 对于色彩丰富的照片,JPEG 格式通常是较好的选择,但可以通过压缩降低质量。对于有透明度的图片,PNG 是常用格式,不过对于简单的图标,可以考虑使用 SVG。SVG 是矢量图形,无论如何缩放都不会失真,并且文件体积通常较小。在代码中可以直接引入 SVG 文件,Webpack 可以通过
svg - loader
等进行处理。
<img src="./icon.svg" alt="icon">
- 或者在 JavaScript 中使用:
import icon from './icon.svg'; // 然后在组件中使用 icon 进行渲染
- 对于色彩丰富的照片,JPEG 格式通常是较好的选择,但可以通过压缩降低质量。对于有透明度的图片,PNG 是常用格式,不过对于简单的图标,可以考虑使用 SVG。SVG 是矢量图形,无论如何缩放都不会失真,并且文件体积通常较小。在代码中可以直接引入 SVG 文件,Webpack 可以通过
3. CSS 优化
- 压缩 CSS:
- 使用
css - minimizer - webpack - plugin
插件来压缩 CSS 文件。安装:npm install --save - dev css - minimizer - webpack - plugin
。
const MiniCssExtractPlugin = require('mini - css - extract - plugin'); const CssMinimizerPlugin = require('css - minimizer - webpack - plugin'); module.exports = { module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css - loader'] } ] }, optimization: { minimizer: [ new CssMinimizerPlugin() ] }, plugins: [ new MiniCssExtractPlugin() ] };
- 该插件会移除 CSS 中的冗余空格、注释等,缩短选择器和属性名,从而减小 CSS 文件的体积。
- 使用
- Tree - shaking CSS:
- 虽然 Tree - shaking 主要用于 JavaScript,但对于 CSS 也有类似的概念。一些工具如
purgecss - webpack - plugin
可以帮助实现 CSS 的 Tree - shaking。它会分析 HTML 和 JavaScript 文件,找出实际使用的 CSS 规则,移除未使用的部分。 - 安装:
npm install --save - dev purgecss - webpack - plugin
。
const PurgeCSSPlugin = require('purgecss - webpack - plugin'); const glob = require('glob - all'); module.exports = { plugins: [ new PurgeCSSPlugin({ paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, {nodir: true}), safelist: function () { return { standard: ['body - dark'] }; } }) ] };
paths
配置需要分析的文件路径,safelist
可以指定一些不被移除的 CSS 类,比如可能通过 JavaScript 动态添加的类。
- 虽然 Tree - shaking 主要用于 JavaScript,但对于 CSS 也有类似的概念。一些工具如
4. 移除未使用的代码
- TerserPlugin 移除未使用代码:
- 前面提到的
TerserPlugin
不仅可以压缩代码,还能移除未使用的代码。在terserOptions
中配置compress.drop_console: true
可以移除console.log
等语句。同时,它还能进行死代码消除。例如,如果有一个函数从未被调用,TerserPlugin 会将其移除。
const TerserPlugin = require('terser - webpack - plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { compress: { drop_console: true } } }) ] } };
- 前面提到的
- ESLint 规则辅助移除未使用代码:
- 可以通过 ESLint 规则来发现未使用的变量、函数等。例如,
no - unused - vars
规则会检测未使用的变量。在.eslintrc.json
文件中配置:
{ "rules": { "no - unused - vars": "error" } }
- 开发过程中,遵循 ESLint 规则及时清理未使用的代码,有助于减少最终打包文件的体积。
- 可以通过 ESLint 规则来发现未使用的变量、函数等。例如,
5. 优化第三方库引入
- 按需引入:
- 对于一些大型的第三方库,很多时候我们只需要使用其中的部分功能。例如,
lodash
是一个常用的工具库,如果项目中只需要使用debounce
函数,可以按需引入。
import debounce from 'lodash/debounce';
- 而不是
import _ from 'lodash';
这样引入整个库,大大减小了引入的代码体积。
- 对于一些大型的第三方库,很多时候我们只需要使用其中的部分功能。例如,
- 寻找轻量级替代品:
- 如果某个功能有多个第三方库可供选择,在满足项目需求的前提下,优先选择体积小的库。例如,对于简单的日期处理,
day - js
的体积就比moment.js
小很多。
// 使用 day - js import dayjs from 'day - js'; const now = dayjs();
- 相比之下,
moment.js
功能更全面,但体积也更大,如果项目中只是简单的日期格式化,day - js
是更好的选择。
- 如果某个功能有多个第三方库可供选择,在满足项目需求的前提下,优先选择体积小的库。例如,对于简单的日期处理,
平衡打包速度与文件体积
1. 权衡优化策略
- 不同阶段的重点:
- 在开发阶段,打包速度更为重要,因为频繁的代码修改和编译需要快速反馈。此时可以适当放宽对文件体积的要求,例如减少对图片等资源的过度压缩,关闭一些耗时但对体积优化效果显著的插件(如
purgecss - webpack - plugin
在开发阶段可以先不启用)。而在生产阶段,文件体积则成为关键,需要全面启用各种体积优化策略,哪怕这可能会稍微增加一些打包时间。
- 在开发阶段,打包速度更为重要,因为频繁的代码修改和编译需要快速反馈。此时可以适当放宽对文件体积的要求,例如减少对图片等资源的过度压缩,关闭一些耗时但对体积优化效果显著的插件(如
- 硬件环境考虑:
- 如果部署服务器的带宽有限,那么文件体积优化就尤为重要,因为较小的文件体积可以加快用户的加载速度。而如果开发机器配置较低,过多的多进程打包等优化可能会导致机器资源耗尽,反而降低打包速度,此时需要根据实际硬件情况调整优化策略。例如,在低配置开发机器上,减少
thread - loader
的并行数量。
- 如果部署服务器的带宽有限,那么文件体积优化就尤为重要,因为较小的文件体积可以加快用户的加载速度。而如果开发机器配置较低,过多的多进程打包等优化可能会导致机器资源耗尽,反而降低打包速度,此时需要根据实际硬件情况调整优化策略。例如,在低配置开发机器上,减少
2. 持续监控与调整
- 定期分析打包结果:
- 随着项目的不断发展,代码结构和依赖会发生变化。定期使用
webpack - bundle - analyzer
分析打包结果,观察文件体积和模块引用情况。如果发现某个模块体积突然增大,或者出现了新的大体积依赖,及时进行调查和优化。
- 随着项目的不断发展,代码结构和依赖会发生变化。定期使用
- 性能指标设定:
- 为项目设定明确的打包速度和文件体积性能指标。例如,规定打包时间不能超过 30 秒,初始加载的 JavaScript 和 CSS 文件总体积不能超过 200KB 等。每次发布或重大代码变更后,检查是否满足这些指标,不满足时及时调整优化策略。
3. 自动化优化流程
- 脚本集成:
- 可以编写一些脚本将优化过程自动化。例如,在
package.json
中定义不同的脚本用于开发和生产环境的打包。
{ "scripts": { "dev": "webpack serve --config webpack.dev.js", "build": "webpack --config webpack.prod.js" } }
- 在
webpack.dev.js
中可以侧重于打包速度优化,而webpack.prod.js
则重点进行文件体积优化。这样通过简单的命令就可以切换不同的优化模式。
- 可以编写一些脚本将优化过程自动化。例如,在
- CI/CD 集成:
- 将优化流程集成到 CI/CD 管道中。在每次代码提交或合并时,自动运行打包和优化过程,并检查是否满足性能指标。如果不满足,CI/CD 流程可以失败并通知开发人员进行调整,确保项目始终保持较好的打包速度和文件体积平衡。