webpack實戰——打包優化【中】

前言

上篇從多線程打包縮小打包作用域兩個方面入手,對webpack打包層面做出優化。本篇描述從動態鏈接庫思想方面繼續深入探究打包層面的深度優化。

動態鏈接庫與DLLPlugin

動態鏈接庫(Dynamic Link Library 或者 Dynamic-link Library,縮寫爲 DLL),是微軟公司在微軟Windows操作系統中,實現共享函數庫概念的一種方式。這些庫函數的擴展名是 ”.dll"、".ocx"(包含ActiveX控制的庫)或者 ".drv"(舊式的系統驅動程序)。

當一段相同的子程序被多個程序調用時,爲了減少內存消耗,可以將這段子程序存儲爲一個可執行文件,當被多個程序調用時只在內存中生成和使用同一個實例。

今天要介紹的主角“DLLPlugin”則借鑑了動態鏈接庫的思路,對於第三方模塊或者一些不常變化的模塊預先進行編譯和打包,然後再項目實際構建過程中直接取用。不過區別還是有的,DLLPlugin實際生成的文件是JS文件而不是動態鏈接庫。在打包vendor的時候還會附加生成一份vendor的模塊清單,這份清單將會在工程業務模塊打包時起到鏈接和索引的作用。

1 vendor配置

首先需要爲動態鏈接庫單獨創建一個Webpack配置文件,例如:webpack.vendor.config.js,注意要與webpack.config.js區分開來。

例:

// webpack.vendor.config.js
const path = require('path');
const webpack = require('webpack');
const dllAssetPath = path.join(__dirname, 'dll');
const dllLibraryName = 'dllExample';

module.exports = {
    entry: ['react'],
    output: {
        path: dllAssetPath,
        filename: 'vendor.js',
        library: dllLibraryName
    },
    plugins: [
        new webpack.DllPlugin({
            name: dllLibraryName,
            path: path.join(dllAssetPath, 'manifest.json')
        })
    ]
}

其中,entry指定了將哪些模塊打包爲vendor,plugins的部分引入了DLLPlugin,並有如下配置:

  • name: 導出的dll library的名字,需要與output.library的值對應;
  • path: 資源清單的絕對路徑,業務打包時將會使用這個清單進行模塊索引;

2 vendor打包

接下來就要打包vendor並且生成資源清單。爲後續方便操作,可以在package.json中配置一條運行指令:

// pachage.json
{
    ...
    "scripts": {
        ...
        "dll": "webpack --config webpack.vendor.config.js"
    }
}

然後執行npm run dll,會發現生成了一個dll目錄,裏面對應有兩個文件:

  • vendor.js: 庫的代碼
  • manifest.json: 資源清單

感興趣的可以打開這兩個文件閱讀一下。

3 鏈接到業務代碼

試過之後,我們就要考慮將vendor鏈接到項目中去了。這裏推薦與DLLPlugin配套的插件“DLLReferencePlugin”,它起到索引和鏈接作用。在工程的webpack配置文件中(注意是webpack.config.js,不是vendor的配置文件),通過DLLReferencePlugin來獲取剛纔打包好的資源清單,然後在頁面中添加vendor.js就可以引用。

// webpack.cinfig.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
    ...
    plugins: [
        new webpack.DllReferencePlugin({
            manifest: require(path.join(__dirname, 'dll/manifest.json')
        })
    ]
}

那麼 index.html 引入會即可:

...

<body>
...

<script src="dll/vendor.js"></script>
<script src="dist/app.js"></script>
</body>

設置完畢後,當頁面執行到vendor.js時,會聲明全局變量dllExample,而manifest相當於注入app.js的資源地圖,app.js會通過name字段找到名爲DLLExample的library,再進一步獲取其內部模塊。

4 潛在問題

細心的小夥伴或許已經發現了,在當前配置中會存在一個問題:當打開manifest.json文件後,可以發現每個模塊都會有一個id,其值是按照數字順序遞增的,而業務代碼在引用vendor中模塊時也是引用這個數字id,當我們更vendor時這個數字id也會隨之發生改變。

現假設我們工程目錄中有如下資源文件,並每個資源都加上了chunk hash:

  • vendor@[hasn].js
  • pageUser@[hasn].js
  • pageIndex@[hasn].js
  • util@[hasn].js

現在vendor中you一些模塊,例如包含了react,其id爲5.當嘗試添加更多模塊到vendor中的時候,那麼重新進行Dll構建時,moment.js可能出現在react之前,此時react的id會變爲6.而pageUser和pageIndex是通過id進行引用的,因此他們的文件內容也發生了改變。此時我們會面臨如下情況:

  1. 兩個頁面的chunk hash均發生了改變。這是我們不希望看到的,因爲他們本身並無變化,但是vendor的改變卻驅使用戶不得不重新下載所有資源。
  2. 兩個頁面chunk hash沒有改變,但是這種情況更爲糟糕:vendor中的模塊id改變了,但是用戶沒有更新緩存,使用的還是舊版本的內容,而引用不到新的vendor模塊,導致頁面發生錯誤。並且對於開發者而言,這個錯誤卻難以排查,因爲開發環境下一切正常!

針對上述的問題2,解決方法是在打包vendor時添加上HashedModuleIdsPlugin,如下:

// webpack.vendor.config.js
module.exports = {
    ...
    plugins: [
        new webpack.DllPlugin({
            name: dllLibraryName,
            path: path.join(dllAssetPath, 'manifest.json')
        }),
        // 添加HashedModuleIdsPlugin
        new webpack.HashedModuleIdsPlugin();
    ]
}

HashedModuleIdsPlugin是webpack3中被引入進來的,主要就是爲了解決數字id的問題。HashedModuleIdsPlugin可以把id的生成算法修改爲根據模塊的引用路徑生成一個字符串hash。

注:從webpack3開始,模塊id不僅可以是數字,也可以是字符串。

小結

本篇從動態鏈接庫思想着手,介紹了DLLPlugin與其配套插件DLLReferencePlugin使用,將第三方庫與一些不常改動的模塊編譯打包,處理爲類似於動態鏈接庫的JS文件,以此來節約服務器資源。
下一篇介紹打包優化最後一個環節:死代碼檢測與去除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章