SplitChunks 插件是什么呢?
简单的来讲就是 Webpack 中一个提取或分离代码的插件,主要做用是提取公共代码,防止代码被重复打包,拆分过大的js文件,合并零散的js文件。
提到前端优化,提取公共代码是必不可少的手段。
在 Webpack 出现前,提取公共代码是人为去处理,而 SplitChunks 插件的做用就是经过配置让 Webpack 去帮你提取公共代码。Webpack创始人的初衷也是但愿能有更多时间写更多代码,因此这种纯体力的劳动交给Webpack去完成。javascript
由于 SplitChunks 插件会提取模块并打包生成js文件。
先学会给打包生成的js文件命名,否则很差确认打包生成的js文件是否是你想要的。
Webpack中给打包生成的js文件命名有如下几种方法:html
1.1 output.filename前端
此选项给打包后的入口 js 文件命名,下图中的 app.js 文件就是打包后的入口js文件。
单入口的 Vue项目中vue
打包前的入口文件,在 vue.config.js 中配置java
module.exports = { configureWebpack:{ entry:'./src/main.js', } }
打包后的入口文件,在 vue.config.js 中配置node
module.exports = { configureWebpack:{ output:{ filename: 'js/appCS.js' }, } }
为何前面要加个 js/ ,由于该 js 文件是 output:path 选项指定的目录下生成的,其默认为根目录 /。jquery
打包结果以下所示:
多入口的 Vue 项目中webpack
打包前的入口文件,在 vue.config.js 中配置web
module.exports = { configureWebpack:{ entry: { appCS: './src/main.js' }, } }
打包后的入口文件,在 vue.config.js 中配置npm
module.exports = { configureWebpack:{ output:{ filename: 'js/[name].js' }, } }
[name] 是入口文件模块名称
打包结果以下所示:
会发现多个 app.js,这是由于 configureWebpack 去采用合并策略来配置 Webpack。
要去掉 app.js 要用 chainWebpack 来配置:
module.exports = { chainWebpack: config =>{ config.entryPoints.delete('app').end().entry('appCS').add('./src/main.js') } }
打包结果以下所示:
1.2 output.chunkFilename
此选项给打包后的非入口 js 文件命名,那下图红框中所示就是非入口 js 文件
在 vue.config.js 中配置
module.exports = { configureWebpack:{ output:{ chunkFilename: 'CS.[name].js' }, } }
打包结果以下所示
然而 output.chunkFilename 并不能改变 chunk-0a4e15c9 这样名字字段
1.3 webpackChunkName
webpackChunkName:块的名称,[request]可解释为实际的解析文件名。
可在路由懒加载中使用:
function load(component) { return () => import(/* webpackChunkName: "[request]" */ `views/${component}`) } const routes = [ { { path: '/apiApply', name: 'apiApply', component: load('api_manage/api_apply'), } } ]
打包结果以下所示:
src/views/api_manage/api_apply/index.vue 这个组件打包后生成的 js 文件是 api_manage-api_apply.48227bf7.js。
若是不用 [request] 也能够这么写。
component: () =>import(/* webpackChunkName: "api_manage-api_apply"*/ 'src/views/api_manage/api_apply'),
再看一下其它 js 文件,发现还有 chunk-xxx.js 类的文件,以下图红框中所示
这些 js 文件的命名要在 SplitChunks 插件中设置了。
chunks 选项,决定要提取哪些模块
默认是 async :只提取异步加载的模块出来打包到一个文件中。
异步加载的模块:经过 import(‘xxx’) 或 require([‘xxx’],() =>{}) 加载的模块。
initial:提取同步加载和异步加载模块;
若是 xxx 在项目中异步加载了,也同步加载了,那么 xxx 这个模块会被提取两次,分别打包到不一样的文件中。
同步加载的模块:经过 import xxx 或 require(‘xxx’) 加载的模块。
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外面选项
配置选项不少,下面在实际项目中使用 SplitChunks,让你更深入理解这些配置选项。
首先了解一下 SplitChunks 在 Vue Cli3 中的默认配置。
整理后默认配置以下所示:
module.exports = { configureWebpack:config =>{ return { optimization: { splitChunks: { chunks: 'async', minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 6, maxInitialRequests: 4, automaticNameDelimiter: '~', cacheGroups: { vendors: { name: `chunk-vendors`, test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' }, common: { name: `chunk-common`, minChunks: 2, priority: -20, chunks: 'initial', reuseExistingChunk: true } } } } } } };
先安装 webpack-bundle-analyzer 插件,能够可视化分析打包后的文件。
npm install webpack-bundle-analyzer --save-dev
在 vue.config.js 中引入插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports={ configureWebpack:config =>{ return { plugins:[ new BundleAnalyzerPlugin() ] } } }
打包后,会在浏览器自动打开 http://127.0.0.1:8888/ ,内容以下所示
会发现里面有个 chunk-vendors.js 文件,是 cacheGroups 中 vendors 这个方案打包出来的 js 文件。
能够用 name 选项来修改 chunk-vendors.js 文件的名字,代码以下:
vendors: { name: `app-chunk-vendors`, test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' },
打包后,会发现 chunk-vendors.js 文件已经变成了 app-chunk-vendors.js 文件里面内容不变。
先去掉 cacheGroups 里面的方案,再打包一下。
cacheGroups: { vendors: false, common: false }
app.a502ce9a.js 和 chunk-be34ce9a.ceff3b64.js 这两个 js 文件是由项目中 main.js 这个入口文件打包生成的。
例如 app.js 文件中有 element-ui、moment、jquery、vue、router、store、jsencrypt 等内容,这些都是在 main.js 中引入。
import Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' import JsEncrypt from 'jsencrypt'; import $ from 'jquery'; import ElementUI from 'element-ui'; Vue.use(ElementUI); import treeSelect from 'fxft-tree-select'; Vue.use(treeSelect); import moment from 'moment'; Vue.prototype.moment = moment; //注册全局变量、全局函数 import base from 'service/base'; Vue.use(base); //注册打印插件 import print from 'service/print'; Vue.use(print); const vm = new Vue({ router, store, render: h => h(App) }).$mount('#app') window.vm = vm;
打包生成的 index.html 中有段代码是这么写。
<div> <div id=app></div> <script src=/js/app.a502ce9a.js></script> </div>
代表 app.js 是项目一开始就要加载的,会影响首屏的加载时间。
那么能够在 main.js 中去掉一些在首屏中暂时用不到的引入,好比这些均可以暂时去掉。
import JsEncrypt from 'jsencrypt'; import treeSelect from 'fxft-tree-select'; Vue.use(treeSelect); //注册打印插件 import print from 'service/print'; Vue.use(print);
打包后再看分析图,会发现 jsencrypt 等内容都在 app.js中 都消失了。
为何说 chunk-be34ce9a.js 也是从 main.js 打包生成的?由于在main.js中有段代码:
//注册全局变量、全局函数 import base from 'service/base'; Vue.use(base);
在看 service/base.js 文件中有
import('./Export2Excel').then(res => { res.export_json_to_excel(defaultOpition); })
Export2Excel.js 是异步加载,在 service/Export2Excel.js 中
import { saveAs } from 'file-saver' import XLSX from 'xlsx'
至关,file-saver、xlsx 也是异步加载,因此 file-saver、xlsx 会被提取出来打包生成 chunk-be34ce9a.js 文件。
在默认配置下,main.js 中异步加载或间接异步加载的模块,都会被另外打包生成一个 js 文件
若是要把从 node_modules 中加载的模块所有打包到一个js文件中,要怎么作呢?Vue Cli3中的已经帮咱们作了。
cacheGroups: { vendors: { name: `chunk-vendors`, test: /[\\/]node_modules[\\/]/, priority: -10, chunks: 'initial' }, }
其核心是 test 选项,匹配项目从 node_modules 中加载的模块并提取打包生成 chunk-vendors.js 文件。
打包后再从分析图中搜索node_modules,发现仍是由不少文件中含有从node_modules中加载的模块,和预期的不同。
这是 chunks 选项在做怪,值 initial 表示:若是 xxx 在项目中异步加载或同步加载多少次,那么xxx这个模块也会被提取多少次,分别打包到不一样的文件中。core-js库在项目中每一个文件都会加载到,故它会提取屡次。
只要把 chunks 选项的值改为 all(无论异步加载仍是同步加载的模块都是提取打包到一个文件中),就能够把从 node_modules中加载的模块所有打包到一个 js 文件中。
发现 chunk-vendors.js 的大小有点大了,有1.91MB,仍是项目初始化时须要加载的 js 文件,大小过大会致使首屏加载时间过长。
解决:用 SplitChunks优化,例如把 element 从 chunk-vendors.js 提取出来,要在 cacheGroups 中配置:
element: { chunks: 'all', name: `element-ui`, test: /[\\/]element-ui[\\/]/, priority: 0, },
注意 priority选项,要把 element 单独提取出来,priority 的值必须比 vendors 方案中的 priority 的值大,否则提取不出来。
打包后可看到 element 被打包生成新的 element-ui.js 文件,chunk-vendors.js 大小变成1.27MB,比原来的 1.91MB有减少。
此外能够本身提取 xlsx、moment、jquery 等第三方依赖。
在分析图中,除了入口文件,还有不少 js 文件,这些文件中有一大部分是项目中组件打包生成的。
若是在实现路由懒加载时,用到 /webpackChunkName:"[request]"/,那么在由组件打包生成的 js 文件名上,能够得知这个 js 文 件是哪一个组件打包生成的。
图中的
base_info_manage-group_info_set-ability_bind_set.85b419a1.js 是项目中views/base_info_manage/group_info_set/bility_bind_set/index.vue 这个组件打包生成的。
base_info_manage-group_info_set-ability_bind_set-edit.08f91768.js 是项目中views/base_info_manage/group_info_set/bility_bind_set/edit.vue 这个组件打包生成的。
另外会发现:chunk-5c1416e3.1cbcb0ec.js 的内容怎么跟 base_info_manage-group_info_set-ability_bind_set-edit.08f91768.js 的内容类似,只少了 src/api 的内容。
并且 api/common.js、api/ability_bind_set.js、edit.vue、mixins 等模块被重复打包了好几回。
能够用SplitChunks提取一下这些模块。避免重复打包,减小打包生成的文件整体大小。
在 cacheGroups 中配置:
api: { name: 'api', test: /[\\/]api[\\/]/, priority: 0, },
当提取多个模块打包生成文件时,name 选项为必填
打包后再看分析图会发现 api/common.js、api/ability_bind_set.js 已经被提取到 api.05ad5193.js 中了。
接着提取 mixins 模块,在 cacheGroups 中配置
mixins: { name: 'mixins', test: /[\\/]mixins[\\/]/, priority: 0, },
打包后再看分析图发现 mixins 模块已经被提取到 mixins.8d1d6f50.js 中
接着提取 edit.vue 模块,在 cacheGroups 中配置
base_info_manage: { name: 'base_info_manage', test: /[\\/]base_info_manage[\\/]/, minChunks: 2, priority: 0, },
其中 minChunks 选项,必须为2,由于从上面分析图来看,edit.vue 被引用了2次,而 index.vue 只被引用了1次。
若是为1,则 index.vue 也会被提取出来;
若是为2以上的值,则 edit.vue 不会被提取出来。
打包后再看分析图发现 edit.vue 模块已经被提取到 base_info_manage.d5c14c01.js 中。
若是以为 base_info_manage.d5c14c01.js 文件太大,有两种方法能够处理。
第一种是利用 maxSize 选项,提取模块后打包生成的文件大小不能超过 maxSize 值,若是超过了,要再提取并打包生成新的文件。
base_info_manage: { name: 'base_info_manage', test: /[\\/]base_info_manage[\\/]/, minChunks: 2, priority: 0, maxSize: 102400 },
打包后再看分析图会发现 base_info_manage.js 已经被分割成五个小 js 文件了
第二种是按 base_info_manage 文件夹下的子文件夹来继续提取,例如 base_info_manage 文件夹下有个子文件叫group_info_set。
在 cacheGroups 中配置
group_info_set: { name: 'group_info_set', test: /[\\/]base_info_manage[\\/]group_info_set[\\/]/, minChunks: 2, priority: 10, },
打包后再看分析图会发现 base_info_manage.js 中的 group_info_set 模块集合已经被提取到 group_info_set.js 文件中。base_info_manage.d5c14c01.js 文件也有相应的减少。
另外能够把 src/api、src/mixins、src/service 中的内容合并打包生成一个 js 文件,替代以前打包生成的 mixins.8d1d6f50.js 和api.05ad5193.js
common: { test: /[\\/]api[\\/]|[\\/]mixins[\\/]|[\\/]src[\\/]service[\\/]/, priority: 0, name: 'common', },
其余的就很少说,能够按照上面所讲在本身的项目用 SplitChunks 插件来控制 Webpack 打包生成本身想要的 js 文件,直到 dist/js 文件夹中每一个 js 都是你所预想的生成的 js 文件为止。
用 SplitChunks 插件来控制 Webpack 打包生成的 js 文件的内容的精髓在于:防止模块被重复打包,拆分过大的js文件,合并零散的js文件。最终的目的就是减小请求资源的大小和请求次数。因这二者是互相矛盾的,故要以项目实际的状况去使用SplitChunks插件,需切记中庸之道。