webpack CommonsChunkPlugin 学习小结
首先,重要的事情放在开头:Webpack4 中把 CommonsChunkplugin 干掉了!
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 怎么用呢?
常用参数:
1.决定生成 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标签没什么意义)。
2.决定被提取的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的所有下属节点。
3.决定提取条件: 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,
}),
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 工具代码等都完全分离。
参考文献
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论