抽取公共代码
Posted negivup
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抽取公共代码相关的知识,希望对你有一定的参考价值。
如果要彻底明白 Webpack V4 版本如何抽取公共代码,就要设计一个场景来支持抽取公共代码的多种形式,能够从代码运行的结果中查看实际的效果,从效果中深入理解各个参数的作用。
场景设计
在设计场景之前,首先要明白公共代码抽取常见的几种情况:
- 抽取 Webpack 运行时代码
- 直接加载的代码抽取(静态引入)
- node_modules 中的代码
- 项目中的业务代码
- 按需加载的代码抽取(动态引入)
- node_modules 中的代码
- 项目中的业务代码
经过分析会发现,现在常见的场景就五种,设计一个应用场景包含这五种情况,就可以很好的理解 Webpack V4 如何抽取公共代码了。
设计场景如下:
其中带有
~
的表示是动态引入
module 是一个独立的功能模块,chunk 是很多 module 打包的结果,bundle 是很多 chunk 最后生成的结果
打包入口动态和静态导入的模块:
入口 | 模块A | 模块B | 模块C | 模块D |
---|---|---|---|---|
pageA | moduleA | moduleB | moudleC | moudleD~ |
pageB | moduleA | moduleB | moudleC~ | moudleD~ |
pageC | moduleA | moduleB~ | moudleC~ | moudleD~ |
pageD | moduleA~ | moduleB~ | moudleC~ | moudleD~ |
模块中动态和静态导入的 node_modules 中的模块:
模块 | react | vue | lodash | jquery |
---|---|---|---|---|
moduleA | react | vue | lodash | jquery~ |
moduleB | react | vue | lodash~ | jquery~ |
moduleC | react | vue~ | lodash~ | jquery~ |
moduleD | react~ | vue~ | lodash~ | jquery~ |
入口文件中的代码(以 pageA.js 为例,其他的入口文件中的代码类似):
import './module/moduleA';
import './module/moduleB';
import './module/moduleC';
import('./module/moduleD');
console.log('pageA');
模块中的代码(以 moduleA.js 为例,其他的模块文件中的代码类似):
import 'react';
import 'vue';
import 'lodash';
import('jquery');
console.log('moduleA');
export default 'moduleA';
最终打包之后的预期效果:
- node_modules 的按需加载模块在一个 chunk 中,包含 react、vue、lodash、jquery
- node_modules 的直接加载的模块在一个 chunk 中,包含 react、vue、lodash
- 项目中按需加载的模块在一个 chunk 中,包含 moduleA、moduleB、moduleC、moduleD
- 项目中直接加载的模块在一个 chunk 中,包含 moduleA、moduleB、moduleC
- 有一个 Webpack 的运行时 chunk:runtime.bundle.js
import(‘react‘);
import(‘vue‘);
import(‘lodash‘);
import(‘jquery‘);
认识配置
在 Webpack V4 中已经提供了抽取公共代码的默认配置。
官方给出的默认配置:
module.exports = {
optimization: {
splitChunks: {
automaticNameDelimiter: '~',
name: true,
chunks: 'async',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
各个选项的说明:
optimization.automaticNameDelimiter
:指定生成名字中的分隔符,Webpack 将使用 chunk 的名字和 chunk 的来源,如 vendors~main.jsoptimization.name
:分割块的名称,提供 true 会自动生成基于 chunk 和缓存组键的名称optimization.maxAsyncRequests
:按需加载时,并行请求的最大数量,默认是 5optimization.maxInitialRequests
:一个入口最大的并行请求数,默认是 3optimization.minChunks
:在分割之前,这个代码块最小应该被引用的次数,默认是 1optimization.chunks
:需要关心的属性,一般可以指定三种形式 all(全部的 chunk,包含所有类型的 chunk)、async(按需加载的 chunk) 和 initial(初始的 chunk)optimization.minSize
:需要关心的属性,一个新的 chunk 的最小体积,默认是 30000,即 30Koptimization.cacheGroups
:该属性是一个对象,上面的属性可以在该对象中使用,如果在该对象中不使用,则默认继承上面的属性值cacheGroups
:是一个对象,下面的所有属性都是自定义的,一般是打包后的模块名称cacheGroups.name
:需要关心的属性,抽取公共代码的 chunk 名字cacheGroups.priority
:需要关心的属性,抽取公共代码的优先级,数字越大,优先级越高cacheGroups.reuseExistingChunk
:需要关心的属性,是否重用 chunk,如果当前块包含已经从主bundle中分离出来的模块,那么它将被重用,而不是生成一个新的模块,一般设置为true
cacheGroups.test
:需要关心的属性,匹配规则,一般使用正则表达式来匹配,如/[\/]node_modules[\/]/
是匹配 node_modules 中的模块
下面的例子中主要是演示上面指出的需要关心的属性。
准备工作
为了能够更好的查看程序执行的效果,需要做以下几个准备工作。
1.创建 package.json 并安装相关依赖和指定运行的 scripts
"scripts": {
"build": "webpack --mode production"
},
"devDependencies": {
"webpack": "^4.17.1",
"webpack-cli": "^3.1.0"
},
"dependencies": {
"jquery": "^3.3.1",
"lodash": "^4.17.10",
"react": "^16.4.2",
"vue": "^2.5.17"
}
2.创建 Webpack 的配置文件 webpack.config.js,并输入公共的配置内容
const { resolve } = require('path');
const webpackConfig = {};
// 入口
webpackConfig.entry = {
pageA: './src/pageA.js',
pageB: './src/pageB.js',
pageC: './src/pageC.js',
pageD: './src/pageD.js'
};
// 出口
webpackConfig.output = {
path: resolve(__dirname, './dist'),
filename: '[name].bundle.js',
// chunkFilename 的作用就是设置 chunk 的名字,抽取公共代码的时候有用
chunkFilename: "[id].[name].chunk.js"
};
// 优化相关
webpackConfig.optimization = {
splitChunks: {
automaticNameDelimiter: '~',
name: true,
maxAsyncRequests: 5,
maxInitialRequests: 3,
minChunks: 1,
minSize: 0, // 这里自定义不管文件有多小,都要抽取公共代码
cacheGroups: {}
}
};
module.exports = webpackConfig;
3.执行命令
$ yarn build
查看未抽取公共代码的效果
上面准备工作中配置的 webpack.config.js 是没有经过抽取公共代码的,执行命令之后,会发现控制台中输出下面的结果:
Asset Size Chunks Chunk Names
6.6.chunk.js 284 bytes 6 [emitted]
0.0.chunk.js 219 bytes 0 [emitted]
2.2.chunk.js 7.21 KiB 2 [emitted]
3.3.chunk.js 69.3 KiB 3 [emitted]
4.4.chunk.js 64.3 KiB 4 [emitted]
5.5.chunk.js 85.3 KiB 5 [emitted]
1.1.chunk.js 311 bytes 1 [emitted]
7.7.chunk.js 217 bytes 7 [emitted]
pageA.bundle.js 143 KiB 8, 0, 2, 3, 4, 6, 7, 12 [emitted] pageA
pageB.bundle.js 143 KiB 9, 0, 2, 3, 4, 7, 12 [emitted] pageB
pageC.bundle.js 143 KiB 10, 0, 2, 3, 4, 12 [emitted] pageC
pageD.bundle.js 2.22 KiB 11 [emitted] pageD
12.12.chunk.js 190 bytes 12 [emitted]
从上面的结果中可以看出,没有抽取公共代码,下面就逐步优化,来抽取公共代码。
抽取 Webpack 运行时代码
在抽取 Webpack 运行时代码的时候,需要指定 runtimeChunk
属性:
true
:表示每个入口都抽取一个公共的运行时代码‘single‘
:表示多个入口抽取一个公共的运行时代码,一般使用这种方式
// 抽取运行时代码
webpackConfig.optimization.runtimeChunk = 'single';
执行命令之后,查看控制台:
Asset Size Chunks Chunk Names
7.7.chunk.js 284 bytes 7 [emitted]
0.0.chunk.js 219 bytes 0 [emitted]
2.2.chunk.js 7.21 KiB 2 [emitted]
3.3.chunk.js 69.3 KiB 3 [emitted]
4.4.chunk.js 64.3 KiB 4 [emitted]
5.5.chunk.js 85.3 KiB 5 [emitted]
runtime.bundle.js 2.16 KiB 6 [emitted] runtime
1.1.chunk.js 311 bytes 1 [emitted]
8.8.chunk.js 217 bytes 8 [emitted]
9.pageA.chunk.js 141 KiB 9, 0, 2, 3, 4, 7, 8, 13 [emitted] pageA
10.pageB.chunk.js 141 KiB 10, 0, 2, 3, 4, 8, 13 [emitted] pageB
11.pageC.chunk.js 141 KiB 11, 0, 2, 3, 4, 13 [emitted] pageC
12.pageD.chunk.js 328 bytes 12 [emitted] pageD
13.13.chunk.js 190 bytes 13 [emitted]
这时,会发现多了一个 runtime.bundle.js 文件,这个文件就是 Webpack 的运行时代码。
抽取公共代码
下面抽取的公共代码就只包含了四部分:项目中的静态导入、项目中的动态导入、node_modules 中的静态导入、node_modules 中的动态导入。
这四种情况,自己划分了一下优先级,可以在代码中看出来。
node_modules 中的直接加载的代码:
webpackConfig.optimization.splitChunks.cacheGroups.nodeSrc = {
name: 'nodeSrc',
reuseExistingChunk: true,
test: /[\/]node_modules[\/]/,
chunks: 'initial', // 指定为初始的 chunk
priority: 3
};
node_modules 中的按需加载的代码:
webpackConfig.optimization.splitChunks.cacheGroups.nodeAsync = {
name: 'nodeAsync',
reuseExistingChunk: true,
test: /[\/]node_modules[\/]/,
chunks: 'async', // 指定为按需加载的 chunk
priority: 2
};
项目中的直接加载的代码:
webpackConfig.optimization.splitChunks.cacheGroups.commonSrc = {
name: 'commonSrc',
reuseExistingChunk: true,
chunks: 'initial', // 指定为初始的 chunk
priority: 1
};
项目中的按需加载的代码:
webpackConfig.optimization.splitChunks.cacheGroups.commonAsync = {
name: 'commonAsync',
reuseExistingChunk: true,
chunks: 'async', // 指定为按需加载的 chunk
priority: 0
};
执行命令之后,可以看到打包之后的最终效果:
Asset Size Chunks Chunk Names
0.commonAsync.chunk.js 946 bytes 0 [emitted] commonAsync
1.nodeAsync.chunk.js 226 KiB 1, 4 [emitted] nodeAsync
2.commonSrc.chunk.js 1.22 KiB 2 [emitted] commonSrc
runtime.bundle.js 2.19 KiB 3 [emitted] runtime
4.nodeSrc.chunk.js 141 KiB 4 [emitted] nodeSrc
5.pageA.chunk.js 74 bytes 5 [emitted] pageA
6.pageB.chunk.js 74 bytes 6 [emitted] pageB
7.pageC.chunk.js 74 bytes 7 [emitted] pageC
8.pageD.chunk.js 72 bytes 8 [emitted] pageD
如果感觉这种带数字的名字查看不直观,就修改 output
中的 chunkFilename
的值为 "[name].chunk.js"
。
最终经过 Webpack 打包后的代码就是预期的结果。
参考资料
- 一步一步的了解webpack4的splitChunk插件
- 没有了CommonsChunkPlugin,咱拿什么来分包(译)
- webpack4 系列教程(三): 多页面解决方案--提取公共代码
- 迁移到webpack4:从webpack.optimize.CommonsChunkPlugin到config.optimization.splitChunk,以及有个搜出来的中文解决办法是错的
- 优化(optimization)
- SplitChunksPlugin
以上是关于抽取公共代码的主要内容,如果未能解决你的问题,请参考以下文章