一、代码分片
(1)代码分片是webpack作为打包工具所特有的一项技术,通过这项技术我们可以把代码按照特定的形式进行拆分,使用户不必一次全部加载,而是按需加载。
(2)代码分片可以有效降低首屏加载资源的大小。
二、通过入口划分代码
(1)webpack中每个入口(entry)都将生成一个对应的资源文件,通过入口的配置我们可以进行一些简单有效的代码划分。
(2)对于web应用来说通常会有一些库和工具是不常变动的,我们可以把它们放在一个单独的入口,由该入口产生的资源不会经常更新,因此可以有效地利用客户端缓存,让用户不必在每次请求页面时都重新加载。
//webpack.config.js
entry: {
app: './app.js',
lib: ['lib-a', 'lib-b', 'lib-c']
}
//index.html
<script src="dist/lib.js"></script>
<script src="dist/app.js"></script>
(3)这种拆分方法主要适合于那些将接口绑定在全局对象上的库,因为业务代码中的模块无法直接引用库中的模块,二者属于不同的依赖树。
三、CommonsChunkPlugin
(1) CommonsChunkPlugin是webpack4之前内部自带的插件(webpack4之后替换为SplitChunks)。它可以将多个Chunk中公共的部分提取出来。
(2)公共模块的提取可以为项目带来几个收益
1、开发过程中减少了重复模块打包,可以提升开发速度。
2、减小整体资源体积。
3、合理分片后的代码可以更有效地利用客户端缓存。
(3)配置
const webpack = require('webpack')
module.exports = {
entry: {
index: './src/index.js',
lib: './src/lib.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",//对应于上面的entry的key
filename: 'commons.js'
})
]
}
插件配置项:
name:用于指定公共chunk的名字。
filename:提取后的资源文件名。
记得在页面中添加一个scrcipt标签来引入common.js,并且注意,该JS一定要在其他JS之前引入。
(4)虽然CommonsChunkPlugin主要用于提取多入口之间公共模块,但这不代表对于单入口应用就无法使用。我们仍然可以用它来提取第三方类库及业务中不常更新的模块,只要单独为它们创建一个入口即可。
const webpack = require('webpack')
module.exports = {
entry: {
app: './app.js',
vendor: ['react']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: 'vendor.js'
})
]
}
(5)设置提取范围
配置项 chunks 可以规定从那些入口中提取公共模块。
const webpack = require('webpack')
module.exports = {
entry: {
a: './a.js',
b: './b.js',
c: './c.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
filename: 'commons.js',
chunks: ['a', 'b']
})
]
}
(6)设置提取规则
1、默认的规则是只要一个模块被两个入口chunk所引用就会被提取出来。
2、使用配置项:minChunks(可为数字、Infinity、函数)
- 数字
(1)为n时,只有该模块被n个入口同时引用才会进行提取。另外,这个阈值不会影响通过数组形式传入模块的提取。
- Infinity
(1)设置为无穷代表提起的阈值无限高,也就是说所有模块都不会被提取。
(2)配置项的意义
1、只想让webpack提取特定的几个模块,并将这些模块通过数组型入口传入,这样做的好处是提取那些模块是完全可控的。
2、为了生成一个没有任何模块而仅仅包含webpack初始化环境的文件,这个文件我们通常称为mainifest。
- 函数
(1)可以让我们更细粒度地控制公共模块。
(2)webpack打包过程中地每个模块都会经过这个函数地处理,当函数地返回值是true时进行提取。
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: 'vendor.js',
minChunks: function (module, count) {
//module.context模块目录路径
if (module.context && module.context.includes('node_modules')) {
return true
}
//module.resource包含模块名的完整路径
if (module.resource && module.resource.endsWith('util.js')) {
return true
}
//count为模块的引用次数
if (count > 5) {
return true
}
}
})
(7)hash与长效缓存
1、当我们用这个插件提取公共模块时,提取后的资源内部不仅仅是模块的代码,往往还包括webpack的运行时。webpack运行时指的是初始化环境的代码,如创建模块缓存对象、声明模块加载函数等。
2、问题:运行时包含模块id,id改变会导致运行时的代码发生变动,进一步影响chunk hash 的生成。其改变会导致用户频繁地更新资源。
解决方案:将运行时的代码单独提取出来。
const webpack = require('webpack')
module.exports = {
entry: {
app: './app.js',
vendor: ['react']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest",
}),
]
}
注意:
1、manifest必须出现在最后,否则webpack将无法正常提取模块。
2、在页面中manifest.js应该最先被引入,用来初始化webpack环境。
(8)CommonsChunkPlugin的不足
1、一个CommonsChunkPlugin只能提取一个vendor。提取多个要配置多个插件。
2、manifest实际上上会使浏览器多加载一个资源,这对于页面渲染速度是不友好的。
3、由于内部设计的缺陷,在提取公共模块的时候会破坏掉原有Chunk中模块的依赖,导致难以进行更多的优化。比如在异步chunk的场景下,其并不会按照我们预期正常工作。
四、optimization.SplitChunks
module.exports = {
entry: { app: "./src/index.js" },
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist")
},
mode:'development',
optimization: {
splitChunks: {
//对所有的chunks生效(默认情况下,只对异步chunks生效,并且不需要配置)
chunks: "all"
}
},
};
mode是webpack4中新增的配置项,可以针对当前是开发环境还是生成环境自动添加对应的一些webpack配置。
(1)SplitChunks默认情形下的提取条件
1、提取后的chunk可被共享或者来自node_modules目录。
2、提取后的JavaScript chunk体积大于30KB(压缩和gzip之前),CSS chunk 体积大于50KB。
3、在按需加载过程中,并行请求的资源最大值小于等于5。
4、在首次加载时,并行请求的资源数最大值小于等于3.
(2)配置
chunks 选项,决定要提取哪些模块
1、默认是 async :只提取异步加载的模块出来打包到一个文件中。
异步加载的模块:通过 import(‘xxx’) 或 require([‘xxx’],() =>{}) 加载的模块。2、initial:只对入口chunk生效;
3、all:不管异步加载还是同步加载的模块都提取出来,打包到一个文件中;
minSize 选项:规定被提取的模块在压缩前的大小最小值,单位为字节;
默认为30000,只有超过了30000字节才会被提取。maxSize 选项:把提取出来的模块打包生成的文件大小不能超过maxSize值;
如果超过了,要对其进行分割并打包生成新的文件。
单位为字节,默认为0,表示不限制大小。minChunks 选项:表示要被提取的模块最小被引用次数,引用次数超过或等于minChunks值,才能被提取。
maxAsyncRequests 选项:最大的按需(异步)加载次数,默认为 6;
maxInitialRequests 选项:打包后的入口文件加载时,还能同时加载js文件的数量(包括入口文件),默认为4。
优先级 :maxInitialRequests / maxAsyncRequests < maxSize < minSize;
automaticNameDelimiter 选项:打包生成的js文件名的分割符,默认为:~
name选项:打包生成 js 文件的名称;
cacheGroups 选项,核心重点,配置提取模块的方案,里面每一项代表一个提取模块的方案。
下面是 cacheGroups 每项中特有的选项,其余选项和外面一致,若 cacheGroups 每项中有,就按配置的,没有就使用外面配置的;
test 选项:用来匹配要提取的模块的资源路径或名称,值是正则或函数;
priority 选项:方案的优先级,值越大表示提取模块时优先采用此方案,默认值为0;
reuseExistingChunk 选项:true / false。
为true时,如果当前要提取的模块,在已经在打包生成的js文件中存在,则将重用该模块,而不是把当前要提取的模块打包生成新的 js 文件。enforce选项:true / false。
为true时,忽略minSize,minChunks,maxAsyncRequests和maxInitialRequests外面选项
(3)资源异步加载(按需加载)
1、webpack中有两种异步加载的方式:import函数(ES6:目前还没有原生支持,需要babel转换)和require.ensure(webpack1中支持的)(CJS)。
2、与正常ES6中的import语法不同,通过import函数加载的模块及其依赖会被异步地进行加载,并返回一个promise对象。
import('./bar.js').then(({ add }) => {
console.log(add(2, 3));
})
export function add(a, b) {
return a + b;
}
3、使用
a、安装Babel插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
b、配置
{
"plugins": ["@babel/plugin-syntax-dynamic-import"],
//...
}
4、原理
通过javacript在页面地head标签里插入一个script标签/dist/0.js。
5、ES6中的import必须出现在代码的顶层作用域,而webpack的import函数则可以在任何我们希望的时候调用。
(5)异步chunk的配置
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
//指定异步chunk的文件名。命名规则与filename一致,
//异步chunk默认有名字,其默认值为[id].js
chunkFilename: '[name].js'
},