目前我们打包的 js 代码,都在一个文件内,这会让这个文件变得非常大;项目中不可避免要使用第三方库,比如:Jquery react vue 等等,这些在项目上线后,依然需要依赖的库相关的 JS 代码,我们需要单独拆分出来,并打包到一个文件内,这些代码因为基不会发生改变,所以单独分离出来,因为有了缓存的缘故,也能加快编译速度。项目中一段代码在很多页面都有调用,这部分代码也需要拆分出来,单独生成一个文件。
一:单独打包
(1)修改webpack.test.conf.js的entry如下:
entry:{
app:[path.resolve(__dirname, 'src/index.js')],
vendor: ["jquery","vue",'vue-router']
},
先将jquery,vue,vue-router这三个项目依赖打包到一个文件中去,执行npm test。配置BundleAnalyzerPlugin,终端中会输出这样一句话Webpack Bundle Analyzer is started at http://127.0.0.1:9528 。在浏览器中打开这个链接地址,就可以看到打包的项目各js文件中的依赖关系,可以看到在vendor.js和app.js中都打包了jquery,这说明我们的打包是正确的,app.js中没有vue 是因为在页面中并没有引入vue,实际项目是肯定会引入的。
我们需要实现的是:vendor.js中存放所有我们引入的第三方库,而其他页面中的js文件不再再次打包这些文件,这样才是正确的打包。
(2)修改webpack.test.conf.js文件,添加optimization的配置:
optimization: {
splitChunks: {
cacheGroups: {
a: {
chunks:'initial',
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
priority: 10,
enforce: true,
},
}
},
// runtimeChunk: {
// name: 'manifest'
// },
},
再次运行npm test 控制台可以看到前后两次打包的app.js和vendor.js的体积大小,打开http://127.0.0.1:9528看文件依赖关系,一目了然,app.js已经没有了jquery。从控制台看两次打包文件后缀的hash值可以发现,两次打包的hash值都发生了变化,但是其实并没有修改任何js文件,hash值的变化,说明webpack每次都重新打包了这些js文件,但是我们没有做修改的文件,再次打包,那就是在浪费时间,项目大的话,打包速度可能会非常非常慢,这里我们要实现每次只打包有修改的文件,未做修改的文件,依然沿用上次打包的文件。
我们取消对optimization.runtimeChunk的注释,先运行一遍npm test,可以看到,这时是多了一个叫manifest.js的文件,
然后我们修改src/index.js文件,随便添加点什么东西~有修改就行,然后再次执行npm test,比较这两次打包的文件hash值变化,会发现,vendor.js的hash值并未发生变化,这说明我们的目的达到了。
二:多插件拆分,单独打包
(1)新增入口配置:
entry:{
app:[path.resolve(__dirname, 'src/index.js')],
superSlide: [path.resolve(__dirname, 'src/assets/js/jquery.SuperSlide.2.1.1.js')],
vendor: ["jquery","vue",'vue-router']
},
(2)新增缓存组:
cacheGroups: {
a: {
chunks:'initial',
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
priority: 10,
enforce: true,
},
b: {
chunks:'initial',
test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
name: 'superSlide',
priority: 10,
enforce: true,
},
}
需要注意这里test需要匹配到该插件的目录所在位置,否则拆分可不会成功~
我们再次执行npm test,然后运行http://127.0.0.1:9528,可以看到,app.js中已经不存在superSlide的依赖了,而是被单独打包成一个独立文件,app.js体积再次缩小~
三:打包优化-终章
对于webpack打包优化的方法,网上有一种使用 DllPlugin 和 DllReferencePlugin 配合来提取那些我们不需要经常更新,
并且每个页面都有引用的第三方库的方法,并且做到不让其他模块的变化污染dll库的hash缓存,在webpack4中我们不需要使用者两个插件,也能实现这种效果,我们只需要配置好optimization的配置即可。
首先说一个项目我们往往都会抽离的几个chunk包:
(1)common:将被多个页面同时引用的依赖包打到一个包中,一般都是引入2次以上,即打入该包中。也可以根据自己项目的页面数量来调整
(2)dll:这个就是网上使用 DllPlugin 和 DllReferencePlugin 配合打出的包。
(3)manifest: webpack运行时代码,每当依赖包变化,webpack运行时代码也会变化,这个需要单独抽离,以减少common包的hash值变化的可能性
(4)页面入口文件对应的app.js
我们要实现的效果是:项目的每次迭代发布,尽量减少 chunk hash值得改变,那么以上这些要求,在webpack4我们都可以通过optimization的配置项来实现。
1:runtimeChunk
在webpack4中,抽离manifest,只需要配置runtimeChunk即可:
optimization: {
runtimeChunk: {
name: 'manifest'
},
},
2:splitChunks
这个配置,可以让我们以一定的规则抽离想要的包,其中的cacheGroups字段,每增加一个key值,就相当于多一个抽包规则。该配置的maxInitialRequests字段,表示在一个入口中,最大初始请求chunk数(不包含按需加载的,也就是页面中通过script引入的chunk),默认是3个,但是我们抽离的包common,dll,manifest,app.js,一个页面最少引入4个js 所以这个也需要重新配置,修改我们现有的配置,修改后如下:
entry:{
app:[path.resolve(__dirname, 'src/index.js')],
superSlide: [path.resolve(__dirname, 'src/assets/js/jquery.SuperSlide.2.1.1.js')],
},
optimization: {
splitChunks: {
maxInitialRequests: 6,
cacheGroups: {
dll: {
chunks:'all',
test: /[\\/]node_modules[\\/](jquery|vue|vue-router)[\\/]/,
name: 'dll',
priority: 2,
enforce: true,
reuseExistingChunk: true
},
superSlide: {
chunks:'all',
test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
name: 'superSlide',
priority: 1,
enforce: true,
reuseExistingChunk: true
},
commons: {
name: 'commons',
minChunks: 2,//Math.ceil(pages.length / 3), 当你有多个页面时,获取pages.length,至少被1/3页面的引入才打入common包
chunks:'all',
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: 'manifest'
},
},
(1)修改过后,执行npm test 再次查看http://127.0.0.1:9528,发现core-js被多次打包,本项目在引用superSlide插件时,因为没有core-js而报错,
我们下载了该插件,此时它也属于项目依赖包,我们也需要将其打包到dll中,修改dll缓存组配置:
dll: {
chunks:'all',
test: /[\\/]node_modules[\\/](jquery|core-js|vue|vue-router)[\\/]/,
name: 'dll',
priority: 2,
enforce: true,
reuseExistingChunk: true
},
(2)再次执行npm test,可以看到,core-js 已经被提取到dll包中去了,我们看包依赖发现,虽然我们配置了vue vue-router 但是在打包后,项目中并没有打包这两个库的任何文件,那是因为虽然我们配置了这两个库需要打包到dll中,但实际项目中,我们并没有做任何引用~,所以不会打包进项目。
3:module chunk moduleIds namedChunks介绍
前端经常说到模块化,也就是module,而webpack打包又有chunk的概念,webpack有xxxModuleIdsPlugin以及xxxChunksPlugin这些插件,那么在webpack中module和chunk到底是一种什么样的关系呢?
(1)chunk: 是指代码中引用的文件(如:js、css、图片等)会根据配置合并为一个或多个包,我们称一个包为 chunk。
(2)module: 是指将代码按照功能拆分,分解成离散功能块。拆分后的代码块就叫做 module。可以简单的理解为一个 export/import 就是一个 module。
每个chunk包可以包含多个module,比如我们打包的dll.xxxxxxxx.js, chunk 的 id 为dll,包含了jquery,core-js两个module。
一个module 还能跨chunk引用另一个module,也就是跨js引用功能块。
webpack 内部维护了一个自增的 id,每个 module 都有一个 id。所以当增加或者删除 module 的时候,id 就会变化,导致其它文件虽然没有变化,但由于 id 被强占,只能自增或者自减,导致整个 id 的顺序都错乱了。
我们上面配置runtimeChunk时,保证了hash的稳定性,但是在chunk包内部的module的id因为webpack的这个机制,在我们对包做增删操作时,其实会有所改变,进而可能影响所有 chunk 的 content hash 值,这就会导致缓存失效,为了解决这个问题,我们不用它的自增id就行了,改为使用它的hash为id,在webpack4中,我们只需要这样配置就行:
optimization: {
moduleIds: 'hashed',
}
那么除了moduleId,每个分离出的chunk也有其chunkId,同样的,在webpack4中,我们只需配置如下参数即可:
optimization: {
namedChunks: true,
}
四:完整optimization配置
module.exports={
optimization: {
namedChunks: true,
moduleIds: 'hashed',
splitChunks: {
maxInitialRequests: 6,
cacheGroups: {
dll: {
chunks:'all',
test: /[\\/]node_modules[\\/](jquery|core-js|vue|vue-router)[\\/]/,
name: 'dll',
priority: 2,
enforce: true,
reuseExistingChunk: true
},
superSlide: {
chunks:'all',
test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
name: 'superSlide',
priority: 1,
enforce: true,
reuseExistingChunk: true
},
commons: {
name: 'commons',
minChunks: 2,//Math.ceil(pages.length / 3), 当你有多个页面时,获取pages.length,至少被1/3页面的引入才打入common包
chunks:'all',
reuseExistingChunk: true
}
}
},
runtimeChunk: {
name: 'manifest'
},
}
}
此时的webpack配置,已经可以说是非常完整了