使用 Webpack 的 DllPlugin 提升專案構建速度
本文介紹了 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 配置