webpack v4 中的斷舍離 - 程式碼分離 SplitChunksPlugin(一)

鉛筆畫不出的黑白發表於2018-08-27

  Time went by...  webpack已經從v3版本升級升級到了v4版本,剛剛我在官網看見升級到v4.16.5。最近webpack的升級速度很快,幾乎兩個星期就會有一個小版本的更新。軟體更新速度快是一件好事情,會讓一款軟體更加穩定和便捷,但對於使用著來說無疑是增加了學習成本。

  案例地址 github.com/z354392349/…

  程式碼分離主要目的是防止程式碼重複,減少程式碼體積,達到載入速度快減少伺服器壓力和頻寬等目的。webpack v3使用CommonsChunkPlugin外掛配置程式碼分離但在webpack v4中被廢除了,但新增了SplitChunksPlugin作為替代品,並且配置過程更為便捷高效。今天我就將程式碼分離的使用方法分享給大家。Remember me my name is 鉛筆畫不出的黑白

安裝方法

npm install webpack-cli webpack -g   // 在全域性環境 和 專案內安裝  
複製程式碼

層級目錄

   假設一個學校有兩個班級A班級和B班級,有三名老師分別是英語老師、數學老師、語文老師。(這個例子我想了很久,應該比較好理解吧,試想你上學的情景)老師給不同的班級上課,那麼此時班級內的學生就相當於公用變數。為防止程式碼的重複我們需要將學生打包到一個檔案內。

project
|- scr
    |- classes  
        |- class-a
        |- class-b
        |- class-c
    |- english
    |- math
    |- chinese
複製程式碼

package.json

{
  "name": "school",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },/[p-]
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "import-local": "^1.0.0",
    "lodash": "^4.17.10",
    "webpack": "^4.16.5",
    "webpack-cli": "^3.1.0"
  }
}

複製程式碼

minimize

   minimize 屬性不算是SplitChunksPlugin下的屬性,但事兩者之間是兄弟關係,都是Optimization(優化)下的屬性, 是負責程式碼壓縮的,在這裡介紹minimize的原因是webpackV4版本中預設將壓縮開啟了,為了更直觀的看到本文的效果,我們暫時將minimize設定為false,minimize的具體設定就不在這裡展開了。 (看例1)

automaticNameDelimiter

   連線符:此選項允許您指定用於生成名稱的分隔符。假設我們生成了一個公用檔名字叫version,class-a,和class-b都依賴他,並且我們設定的連線符是"~"那麼,最終生成的就是 version~class-a~class-b.js。(看例1)

minSize

   模組被分離前最小的模組大小以位元組(b)為單位,例如class-a(~137b)、class-b (~142b) class-c (~142b) 都將會被分離被打包為一個新檔案,如果minSize設定為421以上它們就不會被分離,如果設定為421以下(包括421)它們就會被分離合併為新的檔案。(看例1)

   注意:這裡可能會產生很小誤差,我在測試本案例時中文會有5kb的誤差,英文沒有產生誤差,另外編輯器大多是以UTF-8格式,webpack應該是GBk格式計算。如果你想精確的知道webpack計算出檔案的大小可以在打包完成後看webpack的輸出。

webpack v4 中的斷舍離 - 程式碼分離  SplitChunksPlugin(一)

maxSize

   使用maxSize告訴webpack嘗試將大於maxSize的塊拆分成更小的部分。拆解後的檔案最小值為minSize,或接近minSize的值。這樣做的目的是避免單個檔案過大,增加請求數量,達到減少下載時間的目的。但是這個值設定的時候要掌握好,如果設定的過小產生過多小檔案會適得其反。另外HTTP1.0中最大的請求數量為6。在HTTP2.0中則沒有限制。

  將案例1中 splitChunks.maxSize 設定為130你就會發現, class-a、class-b、class-c。會被分離為三個獨立的檔案。注意:這只是一個案例,為了演示效果,在實際開發中不能這麼用。

例1

// class-a.js
export default [
    {student: "大紅", age: 18},
    {student: "大米", age: 19},
    {student: "大愛", age: 17},
    {student: "大明", age: 20}
]
複製程式碼
// class-b.js
export default [
    {student: "小紅", age: 18},
    {student: "小米", age: 19},
    {student: "小愛", age: 17},
    {student: "小明", age: 20}
]
複製程式碼
// class-c.js
export default [
    {student: "張三", age: 18},
    {student: "李四", age: 19},
    {student: "王五", age: 17},
    {student: "趙六", age: 20}
]
複製程式碼
// webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    mode: "production",
    entry: {
        english: "./src/english.js",
        math: "./src/math.js",
        chinese: "./src/chinese.js",
    },
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(['dist'])
    ],
    optimization: {
        minimize: false,
        splitChunks: {
            chunks: "all",  //  async
            minSize: 400,
            automaticNameDelimiter: '~',
        }
    },
};

複製程式碼
// english.js
import classA from './classes/class-a';
import classB from './classes/class-b';
import classC from './classes/class-c';

let engligh = {
    teacher: 'english', age: 47
};

classA.push(engligh);
classB.push(engligh);
classC.push(engligh);

複製程式碼
// chinese.js
import classA from './classes/class-a';
import classB from './classes/class-b';
import classC from './classes/class-c';

let engligh = {
    teacher: 'english', age: 47
};

classA.push(engligh);
classB.push(engligh);
classC.push(engligh);
複製程式碼
// math.js
import classA from './classes/class-a';
import classB from './classes/class-b';
import classC from './classes/class-c';


let math = {
    teacher: 'math', age: 47
};

classA.push(math);
classB.push(math);
classC.push(math);
複製程式碼

   執行webpack 你會發現在 dist 下會有三個檔案chinese.build.js、english.build.js、math.bundle.js 和公共檔案chinese~english~math.bundle.js。

chunks

   表示對哪些模快進行優化,可選字串值有 allasyncinitial、和函式。

  • async 表示對動態(非同步)匯入的模組進行分離。(看例2.1)
  • initial 表示對初始化值進行分離優化。(看例2.2)
  • all 表示對所有模組進行分離優化,一般情況下都用all (看例2.3)
  • 函式 代表對指定的模組快分離優化,相當於定製優化。(看例2.4)

例2.1

更改下列檔案,其他檔案不變和例1一樣。

// chinese.js
import classC from './classes/class-c';

let engligh = {
    teacher: 'english', age: 47
};


import(/* webpackChunkName: "async-class-a" */ './classes/class-a').then(classA =>{
    classA.push(engligh);
});
import(/* webpackChunkName: "async-class-b" */ './classes/class-b').then(classB =>{
    classB.push(engligh);
});
classC.push(engligh);

複製程式碼
// english.js
import classB from './classes/class-b';
import classC from './classes/class-c';

let engligh = {
    teacher: 'english', age: 47
};

import( /* webpackChunkName: "async-class-a" */  './classes/class-a').then(classA =>{
    classA.push(engligh);
});

classB.push(engligh);
classC.push(engligh);

複製程式碼
// math.js
import classB from './classes/class-b';
import classC from './classes/class-c';


let math = {
    teacher: 'math', age: 47
};
import(/* webpackChunkName: "async-class-a" */  './classes/class-a').then(classA =>{
    classA.push(engligh);
});
classB.push(math);
classC.push(math);

複製程式碼
webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    mode: "production",
    entry: {
        english: "./src/english.js",
        math: "./src/math.js",
        chinese: "./src/chinese.js",
    },
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(['dist'])
    ],
    optimization: {
        minimize: false,
        splitChunks: {
            chunks: "async",  // async、 initial 、 all
            minSize: 0,
            automaticNameDelimiter: '~',
        }
    },
};

複製程式碼

webpack v4 中的斷舍離 - 程式碼分離  SplitChunksPlugin(一)

  執行webpakck dist下會有五個檔案,chinese.bundle.js、english.bundle.js、math.bundle.js 以及動態載入的 async-class-a.bundle.js、async-class-b.bundle.js 而非動態匯入的 class-c.js則不會被拆分為一個新檔案。

 注意為什麼會是這個名字,因為我匯入時寫了 /* webpackChunkName: "async-class-a" */ , /* webpackChunkName: "async-class-b" */ output.chunkFilename 此選項決定了非入口(non-entry) chunk 檔案的名稱。

  該項操作webpack只關心非同步(動態)匯入,即使 english.js、 math.js 、chinese.js 都引入了 class-c.js,也不會被打包為一個獨立的檔案。

例2.2

  將webpack.config.jsoptimization.splitChunks.chunks的值更改為 initial,這個屬性的意思是告訴webpack,我希望將動態匯入的檔案和非動態匯入的檔案分別打包,如果一個模組被動態引入,也被非動態引入。那麼這個模組將會被分離2次。被分別打包到不同的檔案中。

   執行webpack,dist下會有7個檔案,不要驚慌我將會解答每個檔案生成的過程。 english.bundle.js、math.bundle.js、chinese.bundle.js、這三個是入口檔案不做過多的解釋。

   async-class-a.bundle.js 是因為chinese.js、english.js、math.js。 都非同步引入了class-a。

   async-class-b.bundle.js 是因為chinese.js非同步引入了class-b。然而english~math.bundle.js 檔案的內容和async-class-b.bundle.js幾乎完全一樣,都包含了class-b模組。這是因為 english.js、math.js都非非同步的引入了class-b。

  chinese~english~math.bundle.js 是因為chinese.js、english.js、math.js。 都非非同步引入了class-c.js。

例2.3

webpack v4 中的斷舍離 - 程式碼分離  SplitChunksPlugin(一)
  將webpack.config.jsoptimization.splitChunks.chunks的值更改為 all ,此時的webpack就猶如上面的哈士奇一樣,根本不會區分動態還是非動態,只是將需要的檔案分離出一份。

  執行webpack 你會發現dist下有6個檔案,其中三個是入口檔案english.bundle.js 、 math.bundle.js、 chinese.bundle.js , 這沒什麼好說的。class-a.js、class-b.js、class-c.js 檔案被分別打包為 async-class-a.bundle.js、async-class-b.bundle.js、chinese~english~math.bundle.js。

  從打包結果來看,我猜測webpack的內部原理就是隻要這個模組被動態載入了一次,就按動態載入處理。然後共享給其他非動態的模組。這裡點可以從async-class-b.bundle.js的處理結果中看出。

例2.4

  將webpack.config.js的optimization.splitChunks.chunks的值更改為一個函式,這個函式返回Boolean值。如果值為ture 則分離優化這個模組。為false則不分離優化。
  改操作在我測試過程中可選項為入口檔案math、english、chinese (注意:這是入口檔名稱,不是檔名)

// webpack.config.js  optimization.splitChunks.chunks

chunks: function (chunk) {
            return chunk.name !== ''
        },
複製程式碼

  執行webpack你會發現,輸出結果和 chunks: 'all' 一樣。因為所有返回值都是ture,下面我們在測試入口檔案為false的情況。

// webpack.config.js  optimization.splitChunks.chunks

chunks: function (chunk) {
            return chunk.name !== 'math'
        },
複製程式碼

  執行webpack,結果還是和chunks: 'all' 幾乎一樣,但是仔細看chinese~english~math.bundle.js檔案變成了 chinese~english.bundle.js。開啟math.bundle.js檔案,在檔案的下面你會發現class-b.js,class-c.js模組的全部內容,他們並沒有被分離出去。但是class-a.js模組卻被分離出去。仔細看檔案最後的幾行程式碼,class-a.js模組是動態載入的。

  由此我們可以得出結論,當一個模組是非動態載入的那麼他將不會被分離出去,如果這個模組是動態載入的,她就會被分離出去,並且還是動態引入關係。

maxInitialRequests

  入口點處的最大並行請求數,換句話來講就是每一個入口檔案打包完成後最多能有多少個模組組成。為了更好的理解本案例(例3),我將會更改下列程式碼。

webpack v4 中的斷舍離 - 程式碼分離  SplitChunksPlugin(一)

例3

// chinese.js 
import classC from './classes/class-c';
import classB from './classes/class-b';

let chinese = {
    teacher: 'chinese', age: 47
};


classB.push(chinese);
classC.push(chinese);
複製程式碼
// english.js
import classA from './classes/class-a';
import classC from './classes/class-c';

let engligh = {
    teacher: 'english', age: 47
};


classA.push(engligh);
classC.push(engligh);
複製程式碼
// math.js
import classA from './classes/class-a';
import classB from './classes/class-b';


let math = {
    teacher: 'math', age: 47
};
classA.push(math);
classB.push(math);
複製程式碼
// webpack.config.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    mode: "production",
    entry: {
        english: "./src/english.js",
        math: "./src/math.js",
        chinese: "./src/chinese.js",
    },
    output: {
        filename: "[name].bundle.js",
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: [
        new CleanWebpackPlugin(['dist'])
    ],
    optimization: {
        minimize: false,
        splitChunks: {
            chunks: 'all',
            maxInitialRequests: 1,
            minSize: 0,
            automaticNameDelimiter: '~',
        }
    },
};
複製程式碼

例3.1

  將maxInitialRequests設定為了1,執行webpack在dist資料夾內只有三個入口檔案,也就是說打包完成後每個入口檔案最多之只能由1個檔案組成。所以沒有分離出來任何獨立的模組。

例3.2

   將maxInitialRequests設定為了2,執行webpack相比於例3.1在dist下會多出一個english~math.bundle.js檔案。檔案內是class-a.js模組,但是class-b.js和class-c.js模組也被引入了兩次。為什麼偏偏class-a.js被分離出來了呢。我帶著這個疑問反覆的測試。最終結果是可能和模組名稱有關。如果將class-a.js更換為class-d.js那麼被分離的就是class-b.js檔案。

  一個入口打包完成後最多之能有2個檔案組成。english.bundle.js 和 math.bundle.js 都引入english~math.bundle.js 已經達到上限了。chinese.bundle.js 也沒有必要自找麻煩把模組分離出來再引入進去,所以 chinese.bundle.js就沒有任何模組分離出來。

例3.3

  將maxInitialRequests設定為了3,這就相當於每個入口檔案打包完成後最多可由3個模組組成, 執行webpack在dist資料夾下面有6個檔案,class-a.js、class-b.js、class-c.js 全部都被分離出來了。但是當你將maxInitialRequests設定為了3以上時在執行webpack,答案和3是一樣的。因為webpack 沒有必要再分離出更多的模組。

maxAsyncRequests

  按需載入時並行請求的最大數目,但是很抱歉這個屬性我理解的可能不是很到位,無法寫出案例給各位,如果哪位同學可以寫出這個屬性的demo請聯絡我。我會很感謝。engineering.wingify.com/posts/demys… 這個demo 是我在Google上看到的,是關於這個屬性的使用方法。這個屬性我還會繼續研究,如果有研究成果我寫出一個demo給大家。

總結

   maxSize的優先順序高於maxInitialRequest / maxAsyncRequests。 實際優先順序為maxInitialRequest / maxAsyncRequests <maxSize <minSize。

後語

   寫這篇文章用了我幾乎三個星期的業餘時間。如果有哪裡寫的不對的歡迎指教探討,也希望大家留言探討。學技術是一個尋尋漸進的過程,希望各位同學不要鬆懈,不要被技術的浪潮所淘汰。在下一篇文章SplitChunksPlugin(二中我會重點分析)的分組。

聯絡方式

   QQ/微信 48988840。

相關文章