CommonsChunkPlugin學習小結

雙皮奶發表於2018-01-31

首先,重要的事情放在開頭:

Webpack4 下個月就出了,CommonsChunkplugin被幹掉了!

Chunk

如果你還有興趣,那就一起來看看吧。

首先弄明白chunk是什麼東西:webpack將多個模組打包之後的程式碼集合稱為chunk。

webpack裡, chunk有三種型別:

  1. entry chunk: 含有webpack runtime程式碼的模組程式碼集合。
  2. normal chunk:不含runtime程式碼的模組集合。
  3. initial chunk:文件裡講是一種特殊的normal chunk。 在載入的時候順序會在normal chunk前面(這個有興趣的同學可以深入瞭解一下)。

另外有幾點需要注意的:

  1. entry chunk是必須要先於normal chunk載入的,因為裡面包含的runtime程式碼定義了一些列webpack要用到的函式,不事先載入好,後面的程式碼webpack就沒法玩了。
  2. 每一個entry point都會對應生成一個entry chunk。
  3. 每一個用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,
}),
複製程式碼

出現2次的react,react-dom,classnames都被打包到vendor裡

case 2

繼續case1的例子,我們可以看到axios這個包依然和app的業務程式碼混在一起。再提取一下把他單獨拉出來:

new webpack.optimize.CommonsChunkPlugin({
    name: 'axios',
    chunks: ['app'],
    minChunks: function(module) {
        return /axios/.test(module.context);
    }
}),
複製程式碼

axios被單獨提取到axios.js

當然這裡是為了寫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做前端工程的程式碼切割也非常好用。 為了更好的利用快取,假設我們有如下需求:

  1. webpack runtime(entry chunk): 上面提到了,runtime的程式碼必須先於其他程式碼執行。並且由於runtime程式碼隨著module和chunk ID的變化會經常變動,所以建議單獨打包出來

  2. lib(normal chunk): lib裡放一些如react, react-dom, react-router等基本不會改變的基礎庫。

  3. vendor(normal chunk): vendor裡放一些如 axios,moment等偶爾變化的工具庫

  4. 業務程式碼(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工具程式碼等都完全分離。

參考文獻

CommonsChunkPlugin

Code Splitting

Vendor and code splitting in webpack 2

webpack: Unraveling CommonsChunkPlugin

webpack bits: Getting the most out of the CommonsChunkPlugin()

Added deepChildren support from ArcEglos' pull request

相關文章