使用 Webpack 的 DllPlugin 提升專案構建速度

暖生發表於2019-02-15

本文介紹了 Webpack 中 DllPlugin 外掛的使用,以及配合使用 AddAssetHtmlPlugin 將構建好的 JS 檔案插入到 html 頁面中。

本文 Demo 地址

本文專案程式碼位置:原始碼地址

歡迎 Star!


DLLPlugin 和 DllReferencePlugin 簡介

DLLPlugin 就是將包含大量複用模組且不會頻繁更新的庫進行編譯,只需要編譯一次,編譯完成後存在指定的檔案(這裡可以稱為動態連結庫)中。在之後的構建過程中不會再對這些模組進行編譯,而是直接使用 DllReferencePlugin 來引用動態連結庫的程式碼。因此可以大大提高構建速度。一般會對常用的第三方模組使用這種方式,例如 react、react-dom、lodash 等等。只要這些模組不升級更新,這些動態連結庫就不需要重新編譯。


在 Webpack 中進行使用

需要外掛

Webpack 已經內建了對動態連結庫的支援,需要通過兩個內建外掛的配合使用。它們分別是:

  • DllPlugin 外掛:用於打包出一個個單獨的動態連結庫檔案
  • DllReferencePlugin 外掛:用於在主配置檔案中去引入 DllPlugin 外掛打包好的動態連結庫檔案

建立專案

找一個空資料夾,開啟命令列,執行命令

# 建立專案目錄
$ mkdir webpack-dll-demo

# 初始化 package.json 檔案
$ npm init -y 

# 建立 src 資料夾
$ mkdir src

# 建立 public 資料夾
$ mkdir public

# 安裝需要用到的外掛
$ npm install webpack webpack-cli html-webpacl-plugin clean-webpacl-plugin friendly-errors-webpack-plugin -D

# 安裝 lodash 外掛,用於演示 DllPlugin 用法
$ npm install lodash
複製程式碼

在 public 目錄下建立 index.html 檔案

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpak DllPlugin 的使用</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
複製程式碼

在 src 目錄下建立 index.js 檔案

index.js

import { join } from 'lodash';

function createSpan(){
    const element = document.createElement('span');
    element.innerHTML = join(['Hello', 'DllPlugin'], ' , ');
    return element;
}

document.querySelector('#root').appendChild(createSpan());
複製程式碼

當前專案目錄結構

webpack-prod-demo
|- /public
  |- index.html
|- /src
  |- index.js
|- package.json
複製程式碼

使用 DllPlugin 和 DllReferencePlugin(分為三步)

一、先編寫一個配置檔案專門用來編譯生成動態連結庫(使用 DllPlugin)

webpack_dll.config.js

const path = require('path');
const webpack = require('webpack');
const CleanWebpaclPlugin = require('clean-webpack-plugin');
const FirendlyErrorePlugin = require('friendly-errors-webpack-plugin');

module.exports = {
    mode: 'production',
    entry: {
        // 將 lodash 模組作為入口編譯成動態連結庫
        lodash: ['lodash']
    },
    output: {
        // 指定生成檔案所在目錄
        // 由於每次打包生產環境時會清空 dist 資料夾,因此這裡我將它們存放在了 public 資料夾下
        path: path.resolve(__dirname, 'public/vendor'),
        // 指定檔名
        filename: '[name].dll.js',
        // 存放動態連結庫的全域性變數名稱,例如對應 lodash 來說就是 lodash_dll_lib
        // 這個名稱需要與 DllPlugin 外掛中的 name 屬性值對應起來
        // 之所以在前面 _dll_lib 是為了防止全域性變數衝突
        library: '[name]_dll_lib'
    },
    plugins: [
        new CleanWebpaclPlugin(['vendor'], {
            root: path.resolve(__dirname, 'public')
        }),
        new FirendlyErrorePlugin(),
        
        // 接入 DllPlugin
        new webpack.DllPlugin({
            // 描述動態連結庫的 manifest.json 檔案輸出時的檔名稱
            // 由於每次打包生產環境時會清空 dist 資料夾,因此這裡我將它們存放在了 public 資料夾下
            path: path.join(__dirname, 'public', 'vendor', '[name].manifest.json'),
            // 動態連結庫的全域性變數名稱,需要和 output.library 中保持一致
            // 該欄位的值也就是輸出的 manifest.json 檔案 中 name 欄位的值
            // 例如 lodash.manifest.json 中就有 "name": "lodash_dll_lib"
            name: '[name]_dll_lib'
        })
    ]
}
複製程式碼

二、編寫配置檔案用來打包專案(使用 DllReferencePlugin)

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const CleanWebpaclPlugin = require('clean-webpack-plugin');
const FirendlyErrorePlugin = require('friendly-errors-webpack-plugin');

module.exports = {
    mode: 'production',
    devtool: 'source-map',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'build-[hash:5].js'
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'Webpak DllPlugin 的使用',
            template: './public/index.html'
        }),
        new CleanWebpaclPlugin(['dist']),
        new FirendlyErrorePlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
        }),
        // 告訴 Webpack 使用了哪些動態連結庫
        new webpack.DllReferencePlugin({
            // 描述 lodash 動態連結庫的檔案內容
            manifest: require('./public/vendor/lodash.manifest.json')
        })
    ]
}
複製程式碼

三、在 index.html 檔案中引入動態連結庫

由於動態連結庫我們一般只編譯一次,之後就不用編譯,複用模組都被打包到了動態連結庫中,因此入口的 index.js 檔案中已經不包含這些模組了,所以要在 index.html 中單獨引入。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpak DllPlugin 的使用</title>
</head>
<body>
    <div id="root"></div>
    <script src="../public/vendor/lodash.dll.js"></script>
</body>
</html>
複製程式碼

注意:由於在打包專案的時候會清理掉 dist 檔案,所以我將生成的動態連結庫放到了 public 目錄下,所以這裡是引入 public 下的動態連結庫。

我們在 package.json 中新增兩條指令:

  • build:打包專案
  • build:dll:編譯生成動態連結庫

package.json

...
"scripts": {
    "build": "webpack --config webpack.config.js",
    "build:dll": "webpack --config webpack_dll.config.js"
}
...
複製程式碼

執行

根據上面所說的三個步驟,Dll 的用法已經結束了。現在我們執行一下看看結果。

開啟命令列,執行命令

# 生成動態連結庫,只需要執行一次這個指令,以後打包專案不需要再執行這個指令
$ npm run build:dll

# 打包專案
$ npm run build
複製程式碼

在瀏覽器中開啟 dist 資料夾下的 index.html 檔案,可以看到瀏覽器上出現:Hello , DllPlugin。說明專案配置成功。

DllPlugin 和 DllReferencePlugin 分別做了什麼

執行 npm run build:dll 指令之後,可以看到專案中 public 目錄下多出了一個 vendor 的資料夾,可以看到其中包含兩個檔案:

  • lodash.dll.js 裡面包含 lodash 的基礎執行環境,也就是 lodash 模組
  • lodash.manifest.json 也是由 DllPlugin 生成出,用於描述動態連結庫檔案中包含哪些模組

lodash.dll.js

var lodash_dll_lib=...  // 此處程式碼過多,進行省略
複製程式碼

lodash.manifest.json

{"name":"lodash_dll_lib","content":{"./node_modules/lodash/lodash.js":{"id":1,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/global.js":{"id":2,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/module.js":{"id":3,"buildMeta":{"providedExports":true}}}}
複製程式碼

對比之後可以明白

  • 一個動態連結庫檔案中包含了大量模組的程式碼,這些模組存放在一個陣列裡,用陣列的索引號作為 ID。 並且還通過 lodash_dll_lib 變數把自己暴露在了全域性中,也就是可以通過 window.lodash_dll_lib 可以訪問到它裡面包含的模組

  • manifest.json 檔案清楚地描述了與其對應的 dll.js 檔案中包含了哪些模組,以及每個模組的路徑和 ID

至此,Dll 的使用以及配置完成了。但是這裡還有值得思考的地方:目前看來,專案可以正常執行,但是現在動態連結庫是存放到 public 目錄下的,如果我們需要將專案打包上線的話,如何能夠讓動態連結庫自動也存放到 dist 目錄下呢?如何在我們不手動新增指令碼的情況下,自動將動態連結庫引入到 index.html 檔案中呢?如果有興趣的話,可以繼續往下來看一看配合 add-asset-html-webpack-plugin 的使用。


add-asset-html-webpack-plugin 的使用

上面也已經說了,雖然 Dll 的使用和配置沒有問題了,但是還不是很滿意,打包的時候不能將動態連結庫自動的存放到 dist 資料夾,也不能自動在 html 檔案中引入動態連結庫指令碼。所以這時候 add-asset-html-webpack-plugin 就派上用場了。

安裝外掛

$ npm install add-asset-html-webpack-plugin -D
複製程式碼

使用

在 webpack.config.js 檔案中進行使用

webpack.config.js

...;
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
    ...,
    plugins: [
        ...,
        // 該外掛將把給定的 JS 或 CSS 檔案新增到 webpack 配置的檔案中,並將其放入資源列表 html webpack外掛注入到生成的 html 中。
        new AddAssetHtmlPlugin([
            {
                // 要新增到編譯中的檔案的絕對路徑,以及生成的HTML檔案。支援globby字串
                filepath: require.resolve(path.resolve(__dirname, 'public/vendor/lodash.dll.js')),
                // 檔案輸出目錄
                outputPath: 'vendor',
                // 指令碼或連結標記的公共路徑
                publicPath: 'vendor'
            }
        ])
    ]
}
複製程式碼

此時可以刪除 index.html 檔案中手動引入的指令碼了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpak DllPlugin 的使用</title>
</head>
<body>
    <div id="root"></div>
    <!-- 刪除下面這行引入指令碼 -->
-    <script src="../public/vendor/lodash.dll.js"></script>
</body>
</html>
複製程式碼

執行專案

開啟命令列,執行命令:

# 打包專案
$ npm run build
複製程式碼
  • 現在檢視專案中 dist 資料夾,可以看到 public 目錄下 vendor 資料夾中的 js 檔案已經全部自動拷貝到 dist 目錄中的 vendor 資料夾下了

  • 開啟 dist 資料夾中的 index.html 檔案,可以看到已經自動將生成的指令碼檔案引入了

  • 在瀏覽器中開啟 index.html,可以看到 'Hello , DllPlugin' 也能夠正常顯示

add-asset-html-webpack-plugin 更多配置請參考 github 地址:AddAssetHtmlPlugin 配置


總結

  • Dll 動態連結庫的使用可以提高專案構建速度,因為對於大量複用的模組可以提前進行編譯,且只需要編譯一次,之後的開發中,使用這些模組的地方都不會再重新進行編譯

  • DllPlugin 和 DllReferencePlugin 需要配合使用

    • DllPlugin 用於打包出一個個單獨的動態連結庫檔案並生成對應的主清單檔案用於描述動態連結庫中包含哪些模組
    • DllReferencePlugin 用於在主清單檔案中去引入 DllPlugin 外掛打包好的動態連結庫檔案
  • 可以使用 AddAssetHtmlPlugin 將生成的動態連結庫檔案拷貝到出口資料夾下,然後 HTMLWebpackPlugin 就會自動的將指令碼檔案注入到生成的 html 檔案中去

  • **注意:**如想測試一下構建速度是否有提升,可以將 webpack.config.js 中的 DllReferencePlugin 和 AddAssetHtmlPlugin 使用註釋起來,執行 npm run build,觀察打包時間;再將註釋開啟,執行 npm run build,觀察打包時間,進行對比,即可發現區別

如是第一次打包,請先執行 npm run build:dll 生成動態連結庫。

本文 Demo 地址:原始碼地址。 歡迎 Star!

相關文章