webpack 打包构建优化主要从两方面入手,一个是构建的速度,一个是打包的体积,针对不同的场景可以用不同的方式来进行优化,以下是 webpack 配置优化的一些常见手段。
-
优化 webpack 构建速度,主要从几个方面入手,分别是定向查找、减少执行构建的模块、并行构建以提升总体的速度、并行压缩提高构建效率、合理使用缓存等,具体的配置如下:
- 定向查找:定向查找主要是配置
resolve
属性,通过指定路径和配置路径别名来优化查找路径等,缩短查找时间,配置如下:
module.exports = { // ...其他配置 resolve: { // 指定第三方模块的搜索路径,减少搜索步骤 // 如果没有指定,则会从当前路径逐层向上查找 modules: [path.resolve(__dirname, 'node_modules')], // 路径别名,可以减少书写繁琐的路径前缀,优化查找路径 alias: { "@": path.resolve(__dirname, "src"), "_c": path.resolve(__dirname, "src/components") }, // 在导入语句没有带文件后缀时,可以按照配置的列表,自动补上后缀 // 可以把高频率的后缀放在最前,减少没有必要的匹配 extensions: ['ts', 'tsx', 'js', 'jsx', 'scss'] } }
- 减少执行构建的模块:
noParse
忽略没有模块化的文件(例如 JQuery、lodash)、合理配置loader的include
、exclude
属性等
module.exports = { // ...其他配置 module: { // 忽略没有模块化的文件 noParse: /jquery|lodash/, rules: [ { test: /\.jsx?$/, include: [path.resolve(__dirname, 'src')], exclude: /node_modules/, use: ['babel-loader'] } ] } }
- 并行构建以提升总体的速度:可以使用
thread-loader
开启多个线程池进行并发执行构建,只有在代码量很多的时候开启多进程构建才会有明显的提升,因为官方也有说明,每一个 worker 需要耗费 600ms 的启动;
module.exports = { // ...其他配置 module: { rules: [ { test: /\.jsx?$/, use: [ // 开启多线程 { loader: 'thread-loader', options: { worker: 2 } }, { loader: 'babel-loader' } ] } ] } }
- 并行压缩提高构建效率:并行压缩主要是用了
terser-webpack-plugin
,在webpack5
中自带最新的terser-webpack-plugin
,在mode:'production'
时会自动开启,如果想要自定义配置的话,仍然需要安装依赖npm i terser-webpack-plugin --save-dev
;
const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin({ // test 匹配文件的正则表达式 // include 包括哪些文件 // exclude 排除哪些文件 // parallel 使用多进程并行提高速度,默认是 os.cpus().length - 1 // terserOptions 配置选项 // extractComments 是否保留注释 }), ], }, };
- 合理使用缓存: 利用 webpack5 的新特性
cache
,首次构建会增加耗时,但是之后再次构建会大幅度提高构建速度;babel-loader
增加缓存等
module.exports = { // ... 其他配置 module: { rules: [ { test: /\.jsx?/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true, } } ] } ], }, cache: { type: 'filesystem' } }
- 定向查找:定向查找主要是配置
-
优化 webpack 打包体积,主要从几个方面入手,分别是代码压缩、按需加载、提前加载、Code Splitting、Tree Shaking、Gzip、作用提升等,具体配置如下:
- 代码压缩:代码压缩主要压缩 html、css、js、image 文件,具体如下配置:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); module.exports = { optimization: { minimize: true, // 是否需要压缩 minimizer: [ new CssMinimizerPlugin({}), // 配置 css 压缩插件 new TerserPlugin(), // 压缩 js 文件,生产环境默认开启,自定义选项时需要下载依赖重新配置 ], }, module: { rules: [ // ... 其他配置 { test: /\.(png|jpg|gif|jpeg|webp|svg)$/, use: [ "file-loader", { // 压缩图片的loader loader: "image-webpack-loader", options: { mozjpeg: { progressive: true, }, optipng: { enabled: false, }, pngquant: { quality: [0.65, 0.9], speed: 4, }, gifsicle: { interlaced: false, }, }, }, ], }, ] }, plugins: [ new HtmlWebpackPlugin({ template: "./index.html", // 动态生成 html 文件 minify: { removeComments: true, // 移除HTML中的注释 collapseWhitespace: true, // 删除空⽩符与换⾏符 minifyCSS: true // 压缩内联css }, }) ] }
-
按需加载:按需加载主要依赖于
import
语法,主要分为配置第三方UI库按需加载、路由懒加载、import('xx').then()
语法等来实现按需加载; -
Code Splitting:代码分割主要是将公共的代码进行抽离,放到一个公共文件,这样不仅可以减少文件体积,也可以做到只要文件内容不变,就可以从浏览器缓存中读取,这里主要是用
SplitChunksPlugin
进行分割,具体配置如下:
module.exports = { //...其他配置 optimization: { splitChunks: { chunks: 'all', // 值有 all/async/initial/function,表示选择哪些 chunk 进行优化 minSize: 20000, // 生成 chunk 的最小体积。 minRemainingSize: 0, minChunks: 1, // 拆分前必须共享模块的最小 chunks 数。 maxAsyncRequests: 30, // 按需加载时的最大并行请求数。 maxInitialRequests: 30, // 入口点的最大并行请求数。 enforceSizeThreshold: 50000, // 强制执行拆分的阈值 cacheGroups: { defaultVendors: { test: /[\/]node_modules[\/]/, // 匹配的路径文件 priority: -10, // 缓存的优先级 reuseExistingChunk: true, // 复用当前 chunk 包含已从主 bundle 中拆分出的模块 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true, }, }, }, }, };
-
Tree Shaking
- 清除没有用到的 js、css 代码,主要依赖于
esmodule
的特性,可以在package.json
中配置sideEffects:false
选项,表示对所有文件进行tree shaking
,但是会有一定的影响,比如导入polyfill
包,这种会被处理掉,导致样式丢失和失去polyfill
的作用,这明显不是我们要的结果,那么基于这种情况,我们可以进一步对sideEffects
做配置,举个例子,以下代码表示遇到数组中的模块,就跳过,不进行tree shaking
操作;
{ "sideEffects": ["@babel/poly-fill", "*.less"] }
- css 代码进行
tree shaking
需要借助purgecss-webpack-plugin
插件,配置如下
const PurgecssPlugin = require("purgecss-webpack-plugin"); const glob = require("glob"); module.exports = { // ...其他配置 plugins: [ new PurgecssPlugin({ // 这里我的样式在根目录下的index.html里面使用,所以配置这个路径 paths: glob.sync(`${path.join(__dirname)}/**/*.css`, { nodir: true }), }) ] }
- 清除没有用到的 js、css 代码,主要依赖于
-
Gzip:Gzip 是对资源进行进一步的压缩,当浏览器支持 gzip 时,服务器返回 gzip 包,浏览器接收到 gzip 包后就进行解压操作,我们可以借助
compression-webpack-plugin
插件来打包;const CompressionWebpackPlugin = require("compression-webpack-plugin"); module.exports = { // ...其他配置 plugins: [ new CompressionWebpackPlugin() ] }