Chunk
首先弄明白chunk是什么东西:webpack将多个模块打包之后的代码集合称为chunk。
webpack里, chunk有三种类型:
- entry chunk: 含有webpack runtime代码的模块代码集合。
- normal chunk:不含runtime代码的模块集合。
- initial chunk:文档里讲是一种特殊的normal chunk。 在加载的时候顺序会在normal chunk前面(这个有兴趣的同学可以深入了解一下)。
另外有几点需要注意的:
- entry chunk是必须要先于normal chunk加载的,因为里面包含的runtime代码定义了一些列webpack要用到的函数,不事先加载好,后面的代码webpack就没法玩了。
- 每一个entry point都会对应生成一个entry chunk。
- 每一个用import()懒加载的模块会对应生成一个normal chunk,这个chunk会依赖于调用import()的entry chunk,成为其child.
CommonsChunkPlugin
先贴一段官网自己的介绍:
The CommonsChunkPlugin is an opt-in feature that creates a separate file (known as a chunk), consisting of common modules shared between multiple entry points. By separating common modules from bundles, the resulting chunked file can be loaded once initially, and stored in cache for later use. This results in page speed optimizations as the browser can quickly serve the shared code from cache, rather than being forced to load a larger bundle whenever a new page is visited.
理解下来大概就是说:webpack打包的代码都是以chunk的形式存储的。但是呢,不同chunk里可能存在相同的模块,CommonsChunkplugin呢,就是把这些不同chunk里重复的模块提取出来放到一个公共chunk里。这个公共chunk只需要下载一次,就可以让所有的chunk都使用了。而且这部分代码可以放到缓存里,这样以后就不用再下载了(另外有写关于用webpack做缓存的文章,有兴趣可以看看)。而且这么做每个chunk的代码也少了,所以每次加载的速度也更快。
那CommonsChunkplugin怎么用呢?
常用参数:
决定生成chunk的参数: name, names, async
name: string: 公共chunk的名字。如果传入一个已经存在的chunk名,那这个chunk就作为公共chunk存放提取出来的公共代码.否则webpack会新建一个公共chunk。
names: string[]: 和name一样,不过传入的是一个数组。相当于对数组中的每个元素做一次代码切割。
async: boolean|string: 把公共代码提取到一个懒加载的chunk,在被使用到时才进行下载,当传入值为string的时候,该值会被用来当做懒加载chunk的名字。目前来看一般都是配合children使用(entry chunk在app初始化的时候就会被加载,增加async标签没什么意义)。决定被提取的chunk: chunks, children, deepChildren
chunks: string[]: webpack会从传入的chunk里面提取公共代码,如果不传则从所有的entry chunk中提取。
children: boolean : 当不设置children(deepChildren)的时候,webpack会从entry chunk中根据条件提取公共代码。 当设置children为true时,webpack会从entry chunk的直接子chunk中提取代码.
deepChildren: boolean: 和children一样,不过选取公共chunk的所有下属节点。决定提取条件: minChunks
minChunks: number|infinity|function(module,count)->boolean: 如果传入数字或infinity(默认值为3),就是告诉webpack,只有当模块重复的次数大于等于该数字时,这个模块才会被提取出来。当传入为函数时,所有符合条件的chunk中的模块都会被传入该函数做计算,返回true的模块会被提取到目标chunk。
总结一下:
- webpack里面就entry chunk, normal chunk两种,在没有手动设置chunks的情况下,如果要提取normal chunk里的公共代码,那就把children(deepChildren)设为true。否则提取的就是entry chunk。如果对webpack这两种分类不满意,那就用chunks手动指定要选取的chunk。
- 如果你希望对打包出来的公共chunk做一个懒加载,把async设成true。
- 通过minChunks来决定要把哪些模块提取到公共chunk
再看几个样例:
case 1
两个entry App 和 page1 都使用了 react, react-dom 和 classnames, 我们要把重复出现2次以上的module都提取到一个公共chunk vendor里:
App:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as axios from 'axios';
import * as classnames from 'classnames';
Page1:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as classnames from 'classnames';
webpack配置
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: 2,
}),
!出现2次的react,react-dom,classnames都被打包到vendor里](http://upload-images.jianshu.io/upload_images/10204068-1809ef63506b15b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
case 2
继续case1的例子,我们可以看到axios这个包依然和app的业务代码混在一起。再提取一下把他单独拉出来:
new webpack.optimize.CommonsChunkPlugin({
name: 'axios',
chunks: ['app'],
minChunks: function(module) {
return /axios/.test(module.context);
}
}),
当然这里是为了写chunks的使用样例,实际操作中大可不必这样提取两次。直接在第一次提取的时候把node_modules里面的库都打到vendor里就好了(minChunks: 1也行)
case 3
子chunk存在的情况,这里我选择把子chunk提取到一个新的懒加载chunk里:
App异步引用Home,Topics,About:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as moment from 'axios';
import * as classnames from 'classnames';
const Home = () => import('./Home');
const Topics = () => import('./Topics');
const About = () => import('./About');
Home,Topic,About都引用mobx和moment
import * as React from 'react';
import * as moment from 'moment';
import * as mobx from 'mobx';
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor',
children: true,
minChunks: 2,
}),
如图,相当于告诉webpack,扫描app(entry chunk)直接子chunk(Home, Topics, About)里的模块,把出现次数不少于2次的提取出来放到一个叫vendor的懒加载模块中去。
case 4
其实除了提取公共模块之外,用CommonsChunkPlugin做前端工程的代码切割也非常好用。
为了更好的利用缓存,假设我们有如下需求:
webpack runtime(entry chunk): 上面提到了,runtime的代码必须先于其他代码执行。并且由于runtime代码随着module和chunk ID的变化会经常变动,所以建议单独打包出来
lib(normal chunk): lib里放一些如react, react-dom, react-router等基本不会改变的基础库。
vendor(normal chunk): vendor里放一些如 axios,moment等偶尔变化的工具库
业务代码(normal chunk): 随时都在变,单独放一个chunk
直接上配置:
new webpack.optimize.CommonsChunkPlugin({
deepChildren: true,
async: 'async-vendor',
minChunks: function (module) {
return /node_modules/.test(module.context);
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module) {
return /node_modules/.test(module.context);
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: "lib",
minChunks: function (module) {
return /react/.test(module.context);
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
第一次打包:从所有entry chunk(app, page1)的直接子chunk(Home,Topics,About)中提取出公共模块(mobx, moment)放入懒加载chunk async-vendor中。
第二次打包: 从所有entry chunk(app, page1)中提取出node_modules里的模块放入chunk vendor中。app, page1此时变为normal chunk。
第三次打包: 从所有entry chunk(vendor)中提取出路径含有react的模块,放入chunk lib.
第四次打包: 新建一个manifest chunk,不放入任何模块(minChunks:infinity)。由于manifest是此时唯一的entry chunk,则runtime代码放入manifest。
如图,业务代码和lib代码,vendor工具代码等都完全分离。
参考文献
Vendor and code splitting in webpack 2
webpack: Unraveling CommonsChunkPlugin
webpack bits: Getting the most out of the CommonsChunkPlugin()