开发多页应用的时候,若是不对webpack打包进行优化,当某个模块被多个入口模块引用时,它就会被打包屡次(在最终打包出来的某几个文件里,它们都会有一份相同的代码)。当项目业务愈来愈复杂,打包出来的代码会很是冗余,文件体积会很是庞大。大致积文件会增长编译时间,影响开发效率;若是直接上线,还会拉长请求和加载时长,影响网站体验。做为一个追求极致体验的攻城狮,是不能忍的。因此在多页应用中优化打包尤其必要。那么如何优化webpack打包呢?html
在一切开始前,有必要先理清一下这三个概念:node
首先,简单分析下,咱们刚才提到的打包问题:jquery
弄明白了问题的缘由,那么大体的解决思路也就出来了:webpack
webpack提供了一个很是好的内置插件帮咱们实现这一需求:CommonsChunkPlugin
。不过在 webpack4 中CommonsChunkPlugin
被删除,取而代之的是optimization.splitChunks
, 所幸的是optimization.splitChunks
更强大!git
经过一个多页应用的小demo,咱们一步一步来实现上述思路的配置。github
demo目录结构:web
|--public/
| |--a.html
| |--index.html
|--src/
| |--a.js
| |--b.js
| |--c.js
| |--index.js
|--package.json
|--webpack.config.js
复制代码
代码逻辑很简单,index
模块中引用了 a
和 b
2个模块,a
模块中引用了 c
模块和 jquery
库,b
模块中也引用了 c
模块和 jquery
库,c
是一个独立的模块没有其余依赖。正则表达式
index.js代码以下:npm
//index.js
import a from './a.js';
import b from './b.js';
function fn() {
console.log('index-------');
}
fn();
复制代码
a.js代码以下:json
//a.js
require('./c.js');
const $ = require('jquery')
function fn() {
console.log('a-------');
}
module.exports = fn();
复制代码
b.js代码以下:
//b.js
require('./c.js');
const $ = require('jquery')
function fn() {
console.log('b-------');
}
module.exports = fn();
复制代码
c.js代码以下:
//c.js
function fn() {
console.log('c-------');
}
module.exports = fn();
复制代码
webpack先不作优化,只作基本配置,看看效果。项目配置了2个入口,搭配html-webpack-plugin
实现多页打包:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
a: './src/a.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
}),
new HtmlWebpackPlugin({
template: './public/a.html',
filename: 'a.html'
})
]
}
复制代码
在开发模式下运行webpack:
能够看到,打包出两个html和两个体积很大的(300多K)的文件a.js
,index.js
。
进入dist目录检查js文件:
a.js
里包含c
模块代码和jquery
代码index.js
里包含a
模块、b
模块、c
模块和jquery
代码看,一样的代码c
和jquery
被打包了2遍。
首先解决相同代码打包2次的问题,咱们须要让webpack把c
和jquery
提取出来打包为公共模块。
在webpack配置文件添加splitChunks:
//webpack.config.js
optimization: {
splitChunks: {
cacheGroups: {
default: {
name: 'common',
chunks: 'initial'
}
}
}
}
复制代码
cacheGroups
是splitChunks
配置的核心,对代码的拆分规则全在cacheGroups
缓存组里配置。default
属性进行了配置,属性名能够不叫default能够本身定。index~a.js
这样的。all
, async
, initial
,all
表明全部模块,async
表明只管异步加载的, initial
表明初始化时就能获取的模块。若是是函数,则能够根据chunk参数的name等属性进行更细致的筛选。再次打包:
能够看到a.js
,index.js
从300多K减小到6点几K。同时增长了一个common.js
文件,而且两个打包入口都自动添加了common.js
这个公共模块:
进入dist目录,依次查看这3个js文件:
a.js
里不包含任何模块的代码了,只有webpack生成的默认代码。index.js
里一样不包含任何模块的代码了,只有webpack生成的默认代码。common.js
里有a
,b
,c
,index
,jquery
代码。发现,提是提取了,可是彷佛跟咱们预料的不太同样,全部的模块都跑到common.js
里去了。
这是由于咱们没有告诉webpack(splitChunks
)什么样的代码为公共代码,splitChunks
默认任何模块都会被提取。
splitChunks
是自带默认配置的,而缓存组默认会继承这些配置,其中有个minChunks
属性:
咱们上面没有配置minChunks
,只配置了name
和chunk
两个属性,因此minChunks
的默认值 1
生效。也难怪全部的模块都被抽离到common.js
中了。
优化一下,在缓存组里配置minChunks
覆盖默认值:
//webpack.config.js
optimization: {
splitChunks: {
cacheGroups: {
default: {
name: 'common',
chunks: 'initial',
minChunks: 2 //模块被引用2次以上的才抽离
}
}
}
}
复制代码
而后运行webpack
能够看到有2个文件的大小发生了变化:common.js
由314K减少到311K,index.js
由6.22K增大到7.56K。
进入dist目录查看:
a.js
里依然不包含任何模块的代码(正常,由于a
做为模块被index
引入了一次,又做为入口被webpack引入了一次,因此a
是有2次引用的)。index.js
里出现了b
和index
模块的代码了。common.js
里只剩a
,c
,和jquery
模块的代码。如今咱们把共同引用的模块a
, c
, jquery
,从a
和index
这两个入口模块里抽取到common.js
里了。有点符合咱们的预期了。
接下来,我但愿公共模块common.js
中,业务代码和第三方模块jquery可以剥离开来。
咱们须要再添加一个拆分规则。
//webpack.config.js
optimization: {
splitChunks: {
minSize: 30, //提取出的chunk的最小大小
cacheGroups: {
default: {
name: 'common',
chunks: 'initial',
minChunks: 2, //模块被引用2次以上的才抽离
priority: -20
},
vendors: { //拆分第三方库(经过npm|yarn安装的库)
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'initial',
priority: -10
}
}
}
}
复制代码
我给cacheGroups添加了一个vendors属性(属性名能够本身取,只要不跟缓存组下其余定义过的属性同名就行,不然后面的拆分规则会把前面的配置覆盖掉)。
minSize设置的是生成文件的最小大小,单位是字节。若是一个模块符合以前所说的拆分规则,可是若是提取出来最后生成文件大小比minSize要小,那它仍然不会被提取出来。这个属性能够在每一个缓存组属性中设置,也能够在splitChunks属性中设置,这样在每一个缓存组都会继承这个配置。这里因为个人demo中文件很是小,为了演示效果,我把minSize设置为30字节,好让公共模块能够被提取出来,正常项目中不用设这么小。
priority属性的值为数字,能够为负数。做用是当缓存组中设置有多个拆分规则,而某个模块同时符合好几个规则的时候,则须要经过优先级属性priority来决定使用哪一个拆分规则。优先级高者执行。我这里给业务代码组设置的优先级为-20,给第三方库组设置的优先级为-10,这样当一个第三方库被引用超过2次的时候,就不会打包到业务模块里了。
test属性用于进一步控制缓存组选择的模块,与chunks属性的做用有一点像,可是维度不同。test的值能够是一个正则表达式,也能够是一个函数。它能够匹配模块的绝对资源路径或chunk名称,匹配chunk名称时,将选择chunk中的全部模块。我这里用了一个正则/[\\/]node_modules[\\/]/
来匹配第三方模块的绝对路径,由于经过npm或者yarn安装的模块,都会存放在node_modules目录下。
运行一下webpack:
能够看到新产生了一个叫vendor.js
的文件(name属性的值),同时common.js
文件体积由原来的311k减小到了861bytes!
进入dist目录,检查js文件:
a.js
里不包含任何模块代码。common.js
只包含a
和c
模块的代码。index.js
只包含b
和index
模块的代码。vendor.js
只包含jquery
模块的代码。如今,咱们在上一步的基础上,成功从common.js
里把第三方库jquery
抽离出来放到了vendor.js
里。
若是咱们还想把项目中的某一些文件单独拎出来打包(好比工程本地开发的组件库),能够继续添加拆分规则。好比个人src下有个locallib.js
文件要单独打包,假设a.js
中引入了它。
//a.js
require('./c.js');
require('./locallib.js'); //引入本身本地的库
const $ = require('jquery')
function fn() {
console.log('a-------');
}
module.exports = fn();
复制代码
能够这么配置:
//webpack.config.js
optimization: {
splitChunks: {
minSize: 30, //提取出的chunk的最小大小
cacheGroups: {
default: {
name: 'common',
chunks: 'initial',
minChunks: 2, //模块被引用2次以上的才抽离
priority: -20
},
vendors: { //拆分第三方库(经过npm|yarn安装的库)
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'initial',
priority: -10
},
locallib: { //拆分指定文件
test: /(src\/locallib\.js)$/,
name: 'locallib',
chunks: 'initial',
priority: -9
}
}
}
}
复制代码
我在缓存组下又新增了一个拆分规则,经过test正则指定我就要单独打包src/locallib.js
文件,而且把优先级设置为-9,这样当它被屡次引用时,不会进入其余拆分规则组,由于另外两个规则的优先级都比它要低。
运行webpack打包后:
能够看到新产生了一个locallib.js
文件。进入dist目录查看:
a.js
里不包含任何模块代码。common.js
只包含a
和c
模块的代码。index.js
只包含b
和index
模块的代码。vendor.js
只包含jquery
模块的代码。locallib.js
里只包含locallib
模块的代码。如今咱们又在上一步的基础上独立打包了一个指定的模块locallib.js
。
至此,咱们就成功实现了抽离公共模块、业务代码和第三方代码剥离、独立打包指定模块。
对比一下,优化前,打包出来js一共有633KB:
优化后,打包出来js一共不到330KB:
优化打包后的文件分类清晰,体积比优化前缩小了几乎50%,有点小完美是否是!击掌!这还只是我举的一个简单例子,在实际多页应用中,优化力度说不定还不止这么多。
webpack很强大,以上只是冰山一角,可是只要掌握了上述optimization.splitChunks
的核心配置,咱们就能够几乎为所欲为地按照本身的想法来拆分优化代码控制打包文件了,是否是很酷?玩转代码拆分,你也能够!
若是以为这些依然不能知足你的需求,还想更精(bian)细(tai)地定制打包规则,能够到webpack官网查看
optimization.splitChunks
的更多配置。
欢迎交流~
本文的完整webpack配置和demo源码能够在这里获取: github.com/yc111/webpa…
--
欢迎转载,转载请注明出处:
champyin.com/2019/11/15/…