Chunk
首先弄明白chunk是什么東西:webpack將多個(gè)模塊打包之后的代碼集合稱為chunk。
webpack里, chunk有三種類型:
- entry chunk: 含有webpack runtime代碼的模塊代碼集合。
- normal chunk:不含runtime代碼的模塊集合。
- initial chunk:文檔里講是一種特殊的normal chunk。 在加載的時(shí)候順序會(huì)在normal chunk前面(這個(gè)有興趣的同學(xué)可以深入了解一下)草姻。
另外有幾點(diǎn)需要注意的:
- entry chunk是必須要先于normal chunk加載的,因?yàn)槔锩姘膔untime代碼定義了一些列webpack要用到的函數(shù),不事先加載好目尖,后面的代碼webpack就沒法玩了。
- 每一個(gè)entry point都會(huì)對(duì)應(yīng)生成一個(gè)entry chunk扎运。
- 每一個(gè)用import()懶加載的模塊會(huì)對(duì)應(yīng)生成一個(gè)normal chunk瑟曲,這個(gè)chunk會(huì)依賴于調(diào)用import()的entry chunk,成為其child.
CommonsChunkPlugin
先貼一段官網(wǎng)自己的介紹:
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的形式存儲(chǔ)的豪治。但是呢洞拨,不同chunk里可能存在相同的模塊,CommonsChunkplugin呢负拟,就是把這些不同chunk里重復(fù)的模塊提取出來放到一個(gè)公共chunk里烦衣。這個(gè)公共chunk只需要下載一次,就可以讓所有的chunk都使用了掩浙。而且這部分代碼可以放到緩存里琉挖,這樣以后就不用再下載了(另外有寫關(guān)于用webpack做緩存的文章,有興趣可以看看)涣脚。而且這么做每個(gè)chunk的代碼也少了示辈,所以每次加載的速度也更快。
那CommonsChunkplugin怎么用呢遣蚀?
常用參數(shù):
決定生成chunk的參數(shù): name, names, async
name: string: 公共chunk的名字矾麻。如果傳入一個(gè)已經(jīng)存在的chunk名纱耻,那這個(gè)chunk就作為公共chunk存放提取出來的公共代碼.否則webpack會(huì)新建一個(gè)公共chunk。
names: string[]: 和name一樣险耀,不過傳入的是一個(gè)數(shù)組弄喘。相當(dāng)于對(duì)數(shù)組中的每個(gè)元素做一次代碼切割。
async: boolean|string: 把公共代碼提取到一個(gè)懶加載的chunk甩牺,在被使用到時(shí)才進(jìn)行下載蘑志,當(dāng)傳入值為string的時(shí)候,該值會(huì)被用來當(dāng)做懶加載chunk的名字贬派。目前來看一般都是配合children使用(entry chunk在app初始化的時(shí)候就會(huì)被加載急但,增加async標(biāo)簽沒什么意義)。決定被提取的chunk: chunks, children, deepChildren
chunks: string[]: webpack會(huì)從傳入的chunk里面提取公共代碼搞乏,如果不傳則從所有的entry chunk中提取波桩。
children: boolean : 當(dāng)不設(shè)置children(deepChildren)的時(shí)候,webpack會(huì)從entry chunk中根據(jù)條件提取公共代碼请敦。 當(dāng)設(shè)置children為true時(shí)镐躲,webpack會(huì)從entry chunk的直接子chunk中提取代碼.
deepChildren: boolean: 和children一樣,不過選取公共chunk的所有下屬節(jié)點(diǎn)侍筛。決定提取條件: minChunks
minChunks: number|infinity|function(module,count)->boolean: 如果傳入數(shù)字或infinity(默認(rèn)值為3)萤皂,就是告訴webpack,只有當(dāng)模塊重復(fù)的次數(shù)大于等于該數(shù)字時(shí)匣椰,這個(gè)模塊才會(huì)被提取出來裆熙。當(dāng)傳入為函數(shù)時(shí),所有符合條件的chunk中的模塊都會(huì)被傳入該函數(shù)做計(jì)算窝爪,返回true的模塊會(huì)被提取到目標(biāo)chunk。
總結(jié)一下:
- webpack里面就entry chunk, normal chunk兩種齐媒,在沒有手動(dòng)設(shè)置chunks的情況下蒲每,如果要提取normal chunk里的公共代碼,那就把children(deepChildren)設(shè)為true喻括。否則提取的就是entry chunk邀杏。如果對(duì)webpack這兩種分類不滿意,那就用chunks手動(dòng)指定要選取的chunk唬血。
- 如果你希望對(duì)打包出來的公共chunk做一個(gè)懶加載望蜡,把a(bǔ)sync設(shè)成true。
- 通過minChunks來決定要把哪些模塊提取到公共chunk
再看幾個(gè)樣例:
case 1
兩個(gè)entry App 和 page1 都使用了 react拷恨, react-dom 和 classnames脖律, 我們要把重復(fù)出現(xiàn)2次以上的module都提取到一個(gè)公共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,
}),
!出現(xiàn)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
繼續(xù)case1的例子小泉,我們可以看到axios這個(gè)包依然和app的業(yè)務(wù)代碼混在一起芦疏。再提取一下把他單獨(dú)拉出來:
new webpack.optimize.CommonsChunkPlugin({
name: 'axios',
chunks: ['app'],
minChunks: function(module) {
return /axios/.test(module.context);
}
}),
當(dāng)然這里是為了寫chunks的使用樣例,實(shí)際操作中大可不必這樣提取兩次微姊。直接在第一次提取的時(shí)候把node_modules里面的庫都打到vendor里就好了(minChunks: 1也行)
case 3
子chunk存在的情況酸茴,這里我選擇把子chunk提取到一個(gè)新的懶加載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,
}),
如圖薪捍,相當(dāng)于告訴webpack,掃描app(entry chunk)直接子chunk(Home, Topics, About)里的模塊配喳,把出現(xiàn)次數(shù)不少于2次的提取出來放到一個(gè)叫vendor的懶加載模塊中去酪穿。
case 4
其實(shí)除了提取公共模塊之外,用CommonsChunkPlugin做前端工程的代碼切割也非常好用界逛。
為了更好的利用緩存昆稿,假設(shè)我們有如下需求:
webpack runtime(entry chunk): 上面提到了,runtime的代碼必須先于其他代碼執(zhí)行息拜。并且由于runtime代碼隨著module和chunk ID的變化會(huì)經(jīng)常變動(dòng)溉潭,所以建議單獨(dú)打包出來
lib(normal chunk): lib里放一些如react, react-dom, react-router等基本不會(huì)改變的基礎(chǔ)庫。
vendor(normal chunk): vendor里放一些如 axios少欺,moment等偶爾變化的工具庫
業(yè)務(wù)代碼(normal chunk): 隨時(shí)都在變喳瓣,單獨(dú)放一個(gè)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此時(shí)變?yōu)閚ormal chunk。
第三次打包: 從所有entry chunk(vendor)中提取出路徑含有react的模塊底循,放入chunk lib.
第四次打包: 新建一個(gè)manifest chunk,不放入任何模塊(minChunks:infinity)仿滔。由于manifest是此時(shí)唯一的entry chunk惠毁,則runtime代碼放入manifest。
如圖崎页,業(yè)務(wù)代碼和lib代碼鞠绰,vendor工具代碼等都完全分離。
參考文獻(xiàn)
Vendor and code splitting in webpack 2
webpack: Unraveling CommonsChunkPlugin
webpack bits: Getting the most out of the CommonsChunkPlugin()