前端小伙伴都知道,为了下降包大小,常常会把依赖的前端模块独立打包,好比把 vue
、vue-router
打到一个单独的包 vendor
中。另外,常会将存在多个路由的复杂页面的每一个页面都单独打一个包,只有访问某个页面的时候,再去下载该页面的js包,以此来加快首页的渲染。html
不管是 react
仍是 vue
都提供了完善的工具,帮咱们屏蔽了繁琐的配置工做。当咱们对代码进行构建时,已经自动帮咱们完成了代码的拆分工做。前端
因此,不少小伙伴并不知道背后到底发生了什么事。至于为何这么拆分,到底如何控制代码的拆分,更是一头雾水了。vue
讲解开始以前,你们先看一个问题。若是你已经知道问题的答案,并且明白为何,就没必要往下阅读了。若是不知道答案或者知道答案,但不知道缘由。那么,强烈建议阅读本文。node
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: { app: "./src/index.js" },
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist")
},
optimization: {
splitChunks: {
chunks: "all"
}
},
plugins: [
new HtmlWebpackPlugin()
]
};
复制代码
// index.js
import "vue"
import(/*webpackChunkName: 'a' */ "./a");
import(/*webpackChunkName: 'b' */ "./b");
复制代码
// a.js
import "vue-router";
import "./someModule"; // 模块大小大于30kb
复制代码
// b.js
import "vuex";
import "./someModule"; // 模块大小大于30kb
复制代码
// someModule.js
// 该模块大小超过30kb
// ...
复制代码
webpack 中如下三种常见的代码分割方式:react
entry
配置手动地分离代码。splitChunks
去重和分离 chunk。 第一种方式,很简单,只须要在 entry
里配置多个入口便可:entry: { app: "./index.js", app1: "./index1.js" }
复制代码
第二种方式,就是在代码中自动将使用 import()
加载的模块分离成独立的包:webpack
//...
import("./a");
//...
复制代码
第三种方式,是使用 splitChunks
插件,配置分离规则,而后 webpack
自动将知足规则的 chunk
分离。一切都是自动完成的。web
前两种拆分方式,很容易理解。本文主要针对第三种方式进行讨论。vue-router
splitChunks
默认配置splitChunks: {
// 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
chunks: "async",
// 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
minSize: 30000,
// 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
minChunks: 1,
// 表示按需加载文件时,并行请求的最大数目。默认为5。
maxAsyncRequests: 5,
// 表示加载入口文件时,并行请求的最大数目。默认为3。
maxInitialRequests: 3,
// 表示拆分出的chunk的名称链接符。默认为~。如chunk~vendors.js
automaticNameDelimiter: '~',
// 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
name: true,
// cacheGroups 下能够能够配置多个组,每一个组根据test设置条件,符合test条件的模块,就分配到该组。模块能够被多个组引用,但最终会根据priority来决定打包到哪一个组中。默认将全部来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
//
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
复制代码
以上配置,归纳以下4个条件:vuex
node_modules
文件夹// index.js
import("./a");
// ...
复制代码
// a.js
import "vue";
// ...
复制代码
以上代码,在默认配置下的构建结果以下:bash
缘由分析:
index.js
做为入口文件,属于入口起点手动配置分割代码的状况,所以会独立打包。(app.js)a.js
经过 import()
进行加载,属于动态导入的状况,所以会独立打出一个包。(1.js)vue
来自 node_modules
目录,而且大于30kb;将其从 a.js
拆出后,与 a.js
并行加载,并行加载的请求数为2,未超过默认的5;vue
拆分后,并行加载的入口文件并没有增长,未超过默认的3。vue
也符合 splitChunks
的拆分条件,单独打了一个包(2.js)chunks
用以告诉 splitChunks
的做用对象,其可选值有 async
、 initial
和 all
。默认值是 async
,也就是默认只选取异步加载的chunk进行代码拆分。这个咱们在开头的例子里已经验证。这里咱们经过两个例子来看一下当chunks的值为 initial
和 all
时,打包结果如何。 首先将chunks值改成 initial
:
chunks: "initial"
复制代码
构建结果以下:
缘由分析:
当 chunks
值为 initial
时,splitChunks
的做用范围变成了非异步加载的初始 chunk,例如咱们的 index.js
就是初始化的时候就存在的chunk。而 vue 模块是在异步加载的chunk a.js
中引入的,因此并不会被分离出来。
chunks
仍使用 initial
, 咱们对 index.js
和 a.js
稍做修改:
// index.js
import 'vue'
import('./a')
复制代码
// a.js
console.log('a')
复制代码
构建结果以下:
缘由分析:
vue
在 index.js
直接被引入,而 index.js
是初始chunk,因此分离出来打到了 vendors~app.js
中。
能不能让 splitChunks
既处理初始chunk也处理异步chunk呢?答案是能够,只须要将 chunks
改成 all
:
chunks: "all"
复制代码
对 index.js
和 a.js
稍做修改:
// index.js
import 'vue-router'
import('./a')
复制代码
// a.js
import 'vue'
console.log('a')
复制代码
构建结果以下:
缘由分析:
chunks
值为 all
时,splitChunks
的处理范围包括了初始chunk和异步chunk两种场景,所以 index.js
中的 vue-router
被分拆到了 vendors~app.js
中,而异步加载的chunk a.js
中的 vue
被分拆到了 3.js
中。推荐在开发中将 chunks
设置为 all
。
maxIntialRequests
表示 splitChunks
在拆分chunk后,页面中须要请求的初始chunk数量不超过指定的值。所谓初始chunk,指的是页面渲染时,一开始就须要下载的js,区别于在页面加载完成后,经过异步加载的js。
对 splitChunks
作如下修改,其余使用默认配置:
chunks: 'initial',
maxInitialRequests: 1
复制代码
对 index.js 稍做修改:
// index.js
import 'vue'
复制代码
构建结果以下:
缘由分析:
由于 maxInitialRequests
为1,若是 vue
从 index.js
中拆出的话,新建立的chunk做为初始chunk index.js
的前置依赖,是须要在页面初始化的时候就先请求的。那么初始化时的请求数变成了2,所以不知足拆分条件,因此 splitChunks
没有对 index.js
进行拆分。
与 maxInitialRequests
相对,maxAsyncRequests
表示 splitChunks
在拆分chunk后,并行加载的异步 chunk 数不超过指定的值。
对 splitChunks
作如下修改,其余使用默认配置:
maxAsyncRequests: 1
复制代码
对 index.js
稍做修改:
// index.js
import('./a')
复制代码
// a.js
import 'vue'
console.log('a')
复制代码
构建结果以下:
缘由分析: 由于 maxAsyncRequests
为1,因为 a.js
是经过 import()
异步加载的,此时并行的异步请求数是1。若是将 vue
从 a.js
中拆出的话,拆出的包也将成为一个异步请求chunk。这样的话,当异步请求 a.js
的时候,并行请求数有2个。所以,不知足拆分条件,因此 splitChunks
没有对 a.js
进行拆分。
minChunks
表示一个模块至少应被指定个数的 chunk 所共享才能分割。默认为1。
对 splitChunks
作如下修改,其余使用默认配置:
chunks: 'all',
minChunks: 2
复制代码
对 index.js
稍做修改:
// index.js
import 'vue'
复制代码
构建结果以下:
缘由分析:
由于 minChunks
为 2,因此只有当 vue
至少被2个 chunk 所共享时,才会被拆分出来。
思考题
请问以下代码,构建结果是什么?
chunks: 'all',
minChunks: 2
复制代码
// index.js
import 'vue'
import './a'
复制代码
// a.js
import 'vue'
console.log('a')
复制代码
cacheGroups
继承 splitChunks
里的全部属性的值,如 chunks
、minSize
、minChunks
、maxAsyncRequests
、maxInitialRequests
、automaticNameDelimiter
、name
,咱们还能够在 cacheGroups
中从新赋值,覆盖 splitChunks
的值。另外,还有一些属性只能在 cacheGroups
中使用:test
、priority
、reuseExistingChunk
。
经过 cacheGroups
,咱们能够定义自定义 chunk 组,经过 test
条件对模块进行过滤,符合条件的模块分配到相同的组。
cacheGroups
有两个默认的组,一个是 vendors
,将全部来自 node_modules
目录的模块;一个 default
,包含了由两个以上的 chunk 所共享的模块。
前面的例子中,你可能注意到了怎么有的拆分出的chunk名字这么奇怪,例如 vendors~app
(默认由 cacheGroups
中组的 key + 源chunk名组成)。咱们看一下如何自定义拆分出的chunk名。
首先找到该chunk所属的分组,该例为 vendors
分组,做以下修改,其余使用默认配置:
chunks:'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "customName",
priority: -10
}
}
复制代码
对 index.js 稍做修改:
// index.js
import 'vue'
复制代码
构建结果以下:
缘由分析:
vue 来自 node_modules
目录,被分配到了默认的 vendors
组中,若是不指定 name
的话,会使用默认的chunk名,这里咱们指定了 name
,所以最终的chunk名为customName
。
模块还能够分配到多个不一样的组,但最终会根据 priority
优先级决定打包到哪一个 chunk。
新增一个分组:
chunks:'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "customName",
priority: -10
},
customGroup: {
test: /[\\/]node_modules[\\/]/,
name: "customName1",
priority: 0
}
}
复制代码
构建结果:
缘由分析:
虽然 vendors
和 customGroup
这个两个组的条件都符合,但因为后者的优先级更高,因此最终将 vue
打包到了 customName1.js
中。
讲解到这里,想必你对 webpack
如何进行代码分割有了深入地理解了。对于文章开头的问题,能够给出你的答案了吧?