本篇文章我们来聊聊 Webpack 中的 Chunk。
Chunk 不同于 entry、 output、module 这样的概念,它们对应着 Webpack 配置对象中的一个字段,Chunk 没有单独的配置字段,但却出现在 CommonsChunkPlugin(Webpack3 以前)、optimization.splitChunks(Webpack4 以后)这样的名称之中。
Chunk 是我们理解 Webpack 的一个重要概念,它指的是 Webpack 里的一个代码块。具体是什么样的代码块呢?
我们先来看一下 Module。
Webpack 可以看做是模块打包器,我们编写的任何文件,对于 Webpack 来说,都是一个个模块。所以 Webpack 的配置文件,有一个 module 字段,module 下有一个 rules 字段,rules 下有就是处理模块的规则,配置哪类的模块,交由哪类 loader 来处理。
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader"
}
]
},
...
]
}
Chunk 是 Webpack 打包过程中,一堆 Module 的集合。我们知道 Webpack 的打包是从一个入口文件开始,也可以说是入口模块,入口模块引用这其他模块,模块再引用模块。Webpack 通过引用关系逐个打包模块,这些 Module 就形成了一个 Chunk。
如果我们有多个入口文件,可能会产出多条打包路径,一条路径就会形成一个 Chunk。
除了入口 entry 会产生 Chunk 之外,还有其他途径也会产生 Chunk,下文中会介绍。
通常我们会弄混这两个概念,以为 Chunk 就是 Bundle,Bundle 就是我们最终输出的一个或多个打包文件。确实,大多数情况下,一个 Chunk 会生产一个 Bundle。但也不完全是一对一的关系,比如我们把 devtool 配置成 source-map
,然后只配置一个入口文件,不配置代码分割:
// webpack配置
entry: {
main: __dirname + "/app/main.js",
},
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "[name].js", //打包后输出文件的文件名
},
devtool: 'source-map'
这样的配置,会产生一个 Chunk,但是会产生两个 bundle,如下图:
图中注意 Chunk Names 一列,只有 main 这一个 Chunk,再看 Asset 这一列,产生了两个 bundle(main.js 和 main.js.map)。
这就是 Chunk 和 Bundle 的区别,Chunk 是过程中的代码块,而 Bundle 是结果的代码块。
接下来我们查看一下 Webpack 源码,发现有一个 Chunk.js:
/**
* A Chunk is a unit of encapsulation for Modules.
* 一个 Chunk 是一些模块的封装单元。
* Chunks are "rendered" into bundles that get emitted when the build completes.
* Chunk 在构建完成就呈现为 bundle。
*/
class Chunk {
}
根据翻译,可以得出 Webpack 在运行中,会生成 Chunk 对象,而一旦构建完成 Chunk 就会变成 Bundle。
Webpack 入口文件 entry 的配置有三种方式:
1、传递一个字符串,会产生一个 Chunk。
entry: './src/js/main.js'
2、传递一个数组,也只会产生一个 Chunk。
entry: ['./src/js/main.js','./src/js/other.js']
Webpack 会将数组里的源代码,最终都打包到一个 Bundle 里,原因就是只生成了一个 Chunk。
3、传递一个对象,可能产生多个 Chunk。
entry: {
main: './src/js/main.js',
other: './src/js/other.js'
},
output: {
// path: __dirname + "/public",
// filename:'bundle.js'
// 以上2行会报错
path: __dirname + "/public",//打包后的文件存放的地方
filename: "[name].js", //打包后输出文件的文件名
}
对象中一个字段就会产生一个 Chunk,所以在 output 中 filename 直接写死名称,会报错。因为上面的配置,产生了两个 Chunk,最终会生成两个 Bundle,一个名称肯定不够用了。需要用 [name]
变量来把 entry 下的字段名称,设置为对应的 Bundle 名称。
这里面 entry 的 key,也被用来当作它对应的 Chunk 的名称,上面传递字符串和数组的方式没有 key,Webpack 会默认给生成的 Chunk 命名为 main。
除了入口文件影响 Chunk 之外,异步加载的模块,也会产生 Chunk。
{
entry: {
"index": "pages/index.jsx"
},
output: {
filename: "[name].min.js",
chunkFilename: "[name].min.js"
}
}
const myModel = r => require.ensure([], () => r(require('./myVue.vue')), 'myModel')
这个时候 chunkFilename 字段就派上用场了,为异步加载的 Chunk 命名。
最后一种方法是代码分割产生 Chunk。
我们来分析一下,下面代码会产生几个 Chunk。
// main.js 和 other.js 都引用了同一个 greeter.js 文件。main.js 中使用了 react。
module.exports = {
entry: {
main: __dirname + "/app/main.js",
other: __dirname + "/app/other.js",
},
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "[name].js", //打包后输出文件的文件名
chunkFilename: '[name].js',
},
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
commons: {
chunks: "initial",
minChunks: 2,
maxInitialRequests: 5, // The default limit is too small to showcase the effect
minSize: 0 // This is example is too small to create commons chunks
},
vendor: {
test: /node_modules/,
chunks: "initial",
name: "vendor",
priority: 10,
enforce: true
}
},
}
}
}
答案是 5 个。
runtimeChunk: "single"
会将 Webpack 在浏览器端运行时,单独抽离到一个文件,生成一个 Chunk。最终构建图如下:
今天的研究就到这里。
如果你能看懂最后一个 Webpack 配置案例,并能清楚地分辨出能生成几个 Chunk,那么恭喜你,你已经掌握 Webpack Chunk 了,这对于后续 Webpack 的理解非常有帮助,你学到了吗