三石的webpack.config.js(optimization篇)
依据 mode
不同,执行的优化也不同
splitChunks
webpack已默认集成 splitChunksPlugin
插件,所以不需要单独安装,只需要提供相关配置即可;当然,不配置也可以,有默认配置
条件
webpack 会根据此值将文件拆分成多个 chunk 文件。默认情况下,它只会影响到 按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。 webpack 将根据以下条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于
node_modules
文件夹 - 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30 当尝试满足最后两个条件时,最好使用较大的 chunks
demo1
// index.js
import('./a'); // dynamic import
// a.js
import 'react';
//...
结果: 将创建一个单独的包含 react
的 chunk。在导入调用中,此 chunk 并行加载到包含 ./a
的原始 chunk 中。
原因:
- 条件1:chunk 包含来自
node_modules
的模块 - 条件2:
react
大于 30kb - 条件3:导入调用中的并行请求数为 2
- 条件4:在初始页面加载时不影响请求
demo2
// entry.js
// dynamic imports
import('./a');
import('./b');
// a.js
import './helpers'; // helpers is 40kb in size
// b.js
import './helpers';
import './more-helpers'; // more-helpers is also 40kb in size
//...
结果: 将创建一个单独的 chunk,其中包含 ./helpers
及其所有依赖项。在导入调用时,此 chunk 与原始 chunks 并行加载。
原因:
- 条件1:chunk 在两个导入调用之间共享
- 条件2:
helpers
大于 30kb - 条件3:导入调用中的并行请求数为 2
- 条件4:在初始页面加载时不影响请求
基本配置
默认参数
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\/]node_modules[\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
};
这里只解释部分参数,其他的看官网
- chunks
表明将选择哪些 chunk 进行优化。当提供一个字符串,有效值为
all
,async
和initial
。设置为all
意味着同步和异步引入都可以进行分割设置为initial
,也会同时打包同步和异步,但是异步引入文件的内部引入不再考虑分割,而是和该异步文件打包在一起 也可以设置成函数,由函数的返回值决定是否包含 chunk
module.exports = {
//...
optimization: {
splitChunks: {
chunks(chunk) {
// exclude `my-excluded-chunk`
return chunk.name !== 'my-excluded-chunk';
},
},
},
};
可以将此配置与 HtmlWebpackPlugin 结合使用。它将注入所有生成的 vendor chunks。
-
maxAsyncRequests 按需加载时的最大并行请求数。
-
maxInitialRequests 入口点的最大并行请求数。
-
minChunks 要提取的chunks最少被引用多少次
-
minSize 生成 chunk 的最小体积(以 bytes 为单位)。
-
minSizeReduction 生成 chunk 所需的主 chunk(bundle)的最小体积(以字节为单位)缩减。这意味着如果分割成一个 chunk 并没有减少主 chunk(bundle)的给定字节数,它将不会被分割,即使它满足
splitChunks.minSize
为了生成 chunk,
splitChunks.minSizeReduction
与splitChunks.minSize
都需要被满足。
- enforceSizeThreshold 强制执行拆分的体积阈值,满足此条件后其他限制(minRemainingSize,maxAsyncRequests,maxInitialRequests)将被忽略
- maxSize 默认为0,即没有最大体积限制 使用 maxSize 后 webpack 尝试将大于 maxSize 个字节的 chunk 分割成较小的部分。 这些较小的部分在体积上至少为 minSize(仅次于 maxSize)。
当 chunk 已经有一个名称时,每个部分将获得一个从该名称派生的新名称。 根据 optimization.splitChunks.hidePathInfo
的值,它将添加一个从第一个模块名称或其哈希值派生的密钥。
maxSize 选项旨在与 HTTP/2 和长期缓存一起使用。它增加了请求数量以实现更好的缓存。它还可以用于减小文件大小,以加快二次构建速度
maxSize 比 maxInitialRequest/maxAsyncRequests 具有更高的优先级。实际优先级是 maxInitialRequest/maxAsyncRequests < maxSize < minSize。
设置 maxSize 的值会同时设置 maxAsyncSize 和 maxInitialSize 的值。
-
maxAsyncRequests/maxInitialRequest 和maxSize一样,但是 maxAsyncRequests 只影响按需加载,它仅会影响初始加载 chunks
-
cacheGroups 缓存策略,默认设置了分割 node_modules 和公用模块。内部的参数可以覆盖外部的参数。当静态import的模块属于node_modules 目录时,会缓存到到defaultVendors模块下。其余的缓存到default模块下
-
cacheGroups.priority 一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority 的缓存组。默认组的优先级为负,以允许自定义组获得更高的优先级
-
cacheGroups.reuseExistingChunk 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
-
cacheGroups.type 允许按模块类型将模块分配给缓存组。
-
cacheGroups.test 缓存匹配规则,它可以匹配绝对模块资源路径或 chunk 名称,省略它会选择所有模块。
demo
一个优雅的
cacheGroup
配置:
splitChunks: {
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
minSize: 30000,
minChunks: 1,
chunks: 'initial',
priority: 1 // 该配置项是设置处理的优先级,数值越大越优先处理
},
commons: {
test: /[\/]src[\/]common[\/]/,
name: 'commons',
minSize: 30000,
minChunks: 3,
chunks: 'initial',
priority: -1,
reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块
}
}
}
首先是将node_modules的模块分离出来。异步加载的模块将会继承默认配置,这里我们就不需要二次配置了。 第二点是分离出共享模块,这里其公共代码(或者称为可复用的代码)应该是提取出来放到了src/common中
实例
- index.js中,动态引入 a.js
import (/*webpackChunkName:'add'*/'./a.js').then(()=>{
...
})
- 现在要进行chunk拆分,首先,要对 chunkName 配置
module.exports = {
...
entry: {
mainname: './src/index.js'
},
output:{
filename:'[name].[contenthash:10].js',
path:resolve(__dirname,'dist'),
//对import引入的文件做名字处理,增加contenthash
chunkFilename:'[name].[contenthash:10]_chunk.js'
},
}
- 然后,增加
splitChunks
配置,怎么配,看自己需求
module.exports = {
...
splitChunks:{
chunks:"all" // 这里采用其他值亦可以
// 其他的采用默认值
}
}
这时候我们打包看看,会发现打包出来两个文件
解释:
- 'mainame.xxx.js'是因为
entry
里key用的是 'mainname',这个值传到了filename:'[name].[contenthash:10].js',
中的[name]
里 - 'add.xxx.js'是因为动态引入的时候,
/*webpackChunkName:'add'*/
这里用了 'add',这个值传到了chunkFilename:'[name].[contenthash:10]_chunk.js'
中的[name]
里
- 这时候我们修改a.js内容,重新打包,这时候发现两个文件名都改变了
其原因如下:
由于 a.js 文件内容改变,所以它的chunk改变(因为使用了chunkContent)。而在'mainame.xxx.js' 文件里,保存了 add 文件的hash值!因此'mainame.xxx.js' 内容也改变了,所以它的chunk名也变了
很明显,这样是不合理的。webpack想出了一个方法,就是把hash值单独进行打包,这就用到了runtimeChunk
runtimeChunk
作用
先来看看它的功能,就是将当前chunk引入其他chunk的hash单独打包成一个 runtimeChunk 文件
取值
- true/multiple:针对每个入口打包一个runtime文件
- single:统一打包一个共享的runtime文件
- 对象:其 name 属性决定runtimeChunk的名称,可以是函数也可以是字符串
- 默认值是
false
:每个入口 chunk 中直接嵌入 runtime
module.exports = {
//...
optimization: {
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`,
},
},
};
实例
我们接着splitChunks
的例子讲!
- 增加配置
//将当前模块记录其他模块的hash单独打包到一个文件runtime
//打包后,会生成runtime文件
//如果更改a.js文件,新生成的文件为add.js 和 runtime文件,mainname.js没变
runtimeChunk:{
name:entrypoint=>`runtime-${entrypoint.name}`
}
重新打包,结果如下:
打开看看,发现 'mainame.xxx.js' 里已经没有对 'add.xxx.js'的引用了,在 'runtime-mainame.xxx.js' 里才有
- 改变a.js内容,重新打包
'mainname.xx.js' 并没有改变哦!
chunkIds
告知 webpack 当选择 chunk id 时需要使用哪种算法。如果为 false
,此时 webpack 没有任何内置的算法会被使用,但自定义的算法会由插件提供
常见选项如下:
选项 | 描述 |
---|---|
'natural' | 按使用顺序的数字 id。 |
'named' | 对调试更友好的可读的 id。 |
'deterministic' | 在不同的编译中不变的短数字 id。有益于长期缓存。在生产模式中会默认开启,webpack5新增 |
- 如果环境是开发环境,推荐使用
'named'
,但当在生产环境中时,推荐使用'deterministic'
- 要将 optimization.chunkIds 设置为
false
,同时要使用webpack.ids.DeterministicChunkIdsPlugin
module.exports = {
//...
optimization: {
chunkIds: false,
},
plugins: [
new webpack.ids.DeterministicChunkIdsPlugin({
maxLength: 5,
}),
],
};
这里顺带讲下 webpack5 新增'deterministic'
的意义:
以前,natural 这类是按照顺序来命名文件的,这就会导致,若原先编译的文件是 1 2 和 3,后来 2 相关的代码删掉了,这时候编译的 1 不变,但是 3 就变成 2 了,这样就不能用缓存了。但是用'deterministic'的话,去掉一个 2,编译的依旧是 1 和 3,那么 1 和 3 都可以从缓存中去读取,这样就大大加快了打包速度。
moduleIds
告知 webpack 当选择 module id 时需要使用哪种算法。使用与 chunkIds 一致
minimizer
作用
webpack5 本身默认开启压缩功能,有默认的压缩插件。但可以通过 minimizer
配置项来使用一个或多个其它压缩插件覆盖默认压缩工具。
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({
...
}),
new OptimizeCssAssetsPlugin({
...
})
],
},
};
也可以使用函数格式:
module.exports = {
optimization: {
minimizer: [
(compiler) => {
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
new OptimizeCssAssetsPlugin({
...
}).apply(compiler);
},
],
},
};
常见minimizer
-
optimize-css-assets-webpack-plugin压缩css
-
terser-webpack-pluginwebpack的默认压缩js插件
sideEffect
告知 webpack 要不要去识别该项目代码中是否有副作用,从而为Tree-shaking提供更大的压缩空间。 这里的副作用指的是模块执行时除了导出成员之外所做的事情。
开启了 optimization.sideEffects
配置后,webpack在打包时就会先检查需要打包的项目的 package.json
中有没有sideEffects的标识,以此来判断这个模块是不是有副作用。如果这个模块没有副作用,这些没被用到的模块就不会被打包。(这个特性在production模式下会自动开启)
例如:在package.json中配置以下"sideEffects":false
表示整个项目没有副作用,那项目实际出现的一些未使用代码,webpack就不会再打包了
如果该项目中确实有一些副作用,即使没有使用,也不想webpack在打包时删掉,那就以数组的方式提供
...
"sideEffects": ["./src/side-effects.js","*.css"]
optimization.sideEffects
取决于optimization.providedExports
被设置成启用。这个依赖会有构建时间的损耗,但去掉模块会对性能有正面的影响,因为更少的代码被生成。该优化的效果取决于你的代码库, 可以尝试这个特性以获取一些可能的性能优化- 该选项有风险,例如你项目实际是有副作用的,但你判断失误,那么就可能造成打包文件错误
Other
- nodeEnv
设置
process.env.NODE_ENV
,如果不是 false ,则会使用DefinePlugin
。它的默认值取决于mode
- removeAvailableModules
将
optimization.removeAvailableModules
设置为true
后,如果模块已经包含在所有父级模块中,那么 webpack 将从 chunk 中检测出这些模块,或移除这些模块。在当前production
模式中默认会被开启,下个版本中会被默认禁用,原因是这个开启后会影响 webpack 性能 - removeEmptyChunks
将
optimization.removeEmptyChunks
设置为true
后,如果 chunk 为空,那么 webpack 将移出这些chunk。默认为true
- emitOnErrors 编译失败时,是否生成资源
转载自:https://juejin.cn/post/7076743589505531917