Web 應(yīng)用日益復(fù)雜,相關(guān)開發(fā)技術(shù)也百花齊放,這對前端構(gòu)建工具提出了更高的要求。 Webpack 從眾多構(gòu)建工具中脫穎而出成為目前最流行的構(gòu)建工具,幾乎成為目前前端開發(fā)里的必備工具之一。 大多數(shù)人在使用 Webpack 的過程中都會遇到構(gòu)建速度慢的問題,在項(xiàng)目大時(shí)顯得尤為突出,這極大的影響了我們的開發(fā)體驗(yàn),降低了我們的開發(fā)效率。
本文將傳授你一些加速 Webpack 構(gòu)建的技巧,下面來一一介紹。
通過多進(jìn)程并行處理
由于有大量文件需要解析和處理,構(gòu)建是文件讀寫和計(jì)算密集型的操作,特別是當(dāng)文件數(shù)量變多后,Webpack 構(gòu)建慢的問題會顯得嚴(yán)重。 運(yùn)行在 Node.js 之上的 Webpack 是單線程模型的,也就是說 Webpack 需要處理的任務(wù)需要一件件挨著做,不能多個(gè)事情一起做。
文件讀寫和計(jì)算操作是無法避免的,那能不能讓 Webpack 同一時(shí)刻處理多個(gè)任務(wù),發(fā)揮多核 CPU 電腦的威力,以提升構(gòu)建速度呢?
使用 HappyPack
HappyPack 就能讓 Webpack 做到上面拋出的問題,它把任務(wù)分解給多個(gè)子進(jìn)程去并發(fā)的執(zhí)行,子進(jìn)程處理完后再把結(jié)果發(fā)送給主進(jìn)程。
接入 HappyPack 的相關(guān)代碼如下:
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HappyPack = require('happypack');
module.exports = {
module: {
rules: [
{ test: /\.js$/,
// 把對 .js 文件的處理轉(zhuǎn)交給 id 為 babel 的 HappyPack 實(shí)例
use:['happypack/loader?id=babel'],
// 排除 node_modules 目錄下的文件,node_modules目錄下的文件都是采用的 ES5 語法,沒必要再通過 Babel 去轉(zhuǎn)換
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// 把對 .css 文件的處理轉(zhuǎn)交給 id 為 css 的 HappyPack 實(shí)例
test: /\.css$/,
use:ExtractTextPlugin.extract({
use: ['happypack/loader?id=css'],
}),
},
] },
plugins: [
new HappyPack({
// 用唯一的標(biāo)識符 id 來代表當(dāng)前的HappyPack 是用來處理一類特定的文件
id: 'babel',
// 如何處理 .js 文件,用法和 Loader配置中一樣
loaders: ['babel-loader?cacheDirectory'],
}),
new HappyPack({
id: 'css',
// 如何處理 .css 文件,用法和Loader 配置中一樣
loaders: ['css-loader'], }),
new ExtractTextPlugin({
filename: `[name].css`,
}),
],
};
以上代碼有兩點(diǎn)重要的修改:
在 Loader 配置中,所有文件的處理都交給了 happypack/loader 去處理,使用緊跟其后的 querystring ?id=babel 去告訴 happypack/loader 去選擇哪個(gè) HappyPack 實(shí)例去處理文件。
在 Plugin 配置中,新增了兩個(gè) HappyPack 實(shí)例分別用于告訴 happypack/loader 去如何處理 .js 和 .css 文件。選項(xiàng)中的 id 屬性的值和上面 querystring 中的 ?id=babel 相對應(yīng),選項(xiàng)中的 loaders 屬性和 Loader 配置中一樣。
接入 HappyPack 后,你需要給項(xiàng)目安裝新的依賴:
安裝成功后重新執(zhí)行構(gòu)建,你就會看到以下由 HappyPack 輸出的日志:
Happy[babel]: Version: 4.0.0-beta.5. Threads: 3
Happy[babel]: All set; signaling webpack to proceed.Happy[css]: Version: 4.0.0-beta.5. Threads: 3Happy[css]: All set; signaling webpack to proceed.
說明你的 HappyPack 配置生效了,并且可以得知 HappyPack 分別啟動(dòng)了3個(gè)子進(jìn)程去并行的處理任務(wù)。
在整個(gè) Webpack 構(gòu)建流程中,最耗時(shí)的流程可能就是 Loader 對文件的轉(zhuǎn)換操作了,因?yàn)橐D(zhuǎn)換的文件數(shù)據(jù)巨多,而且這些轉(zhuǎn)換操作都只能一個(gè)個(gè)挨著處理。 HappyPack 的核心原理就是把這部分任務(wù)分解到多個(gè)進(jìn)程去并行處理,從而減少了總的構(gòu)建時(shí)間。
從前面的使用中可以看出所有需要通過 Loader 處理的文件都先交給了 happypack/loader 去處理,收集到了這些文件的處理權(quán)后 HappyPack 就好統(tǒng)一分配了。
每通過 new HappyPack() 實(shí)例化一個(gè) HappyPack 其實(shí)就是告訴 HappyPack 核心調(diào)度器如何通過一系列 Loader 去轉(zhuǎn)換一類文件,并且可以指定如何給這類轉(zhuǎn)換操作分配子進(jìn)程。
核心調(diào)度器的邏輯代碼在主進(jìn)程中,也就是運(yùn)行著 Webpack 的進(jìn)程中,核心調(diào)度器會把一個(gè)個(gè)任務(wù)分配給當(dāng)前空閑的子進(jìn)程,子進(jìn)程處理完畢后把結(jié)果發(fā)送給核心調(diào)度器,它們之間的數(shù)據(jù)交換是通過進(jìn)程間通信 API 實(shí)現(xiàn)的。
核心調(diào)度器收到來自子進(jìn)程處理完畢的結(jié)果后會通知 Webpack 該文件處理完畢。
使用 ParallelUglifyPlugin
在使用 Webpack 構(gòu)建出用于發(fā)布到線上的代碼時(shí),都會有壓縮代碼這一流程。 最常見的 JavaScript 代碼壓縮工具是 UglifyJS,并且 Webpack 也內(nèi)置了它。
用過 UglifyJS 的你一定會發(fā)現(xiàn)在構(gòu)建用于開發(fā)環(huán)境的代碼時(shí)很快就能完成,但在構(gòu)建用于線上的代碼時(shí)構(gòu)建一直卡在一個(gè)時(shí)間點(diǎn)遲遲沒有反應(yīng),其實(shí)卡住的這個(gè)時(shí)候就是在進(jìn)行代碼壓縮。
由于壓縮 JavaScript 代碼需要先把代碼解析成用 Object 抽象表示的 AST 語法樹,再去應(yīng)用各種規(guī)則分析和處理 AST,導(dǎo)致這個(gè)過程計(jì)算量巨大,耗時(shí)非常多。
為什么不把多進(jìn)程并行處理的思想也引入到代碼壓縮中呢?
ParallelUglifyPlugin 就做了這個(gè)事情。 當(dāng) Webpack 有多個(gè) JavaScript 文件需要輸出和壓縮時(shí),原本會使用 UglifyJS 去一個(gè)個(gè)挨著壓縮再輸出, 但是 ParallelUglifyPlugin 則會開啟多個(gè)子進(jìn)程,把對多個(gè)文件的壓縮工作分配給多個(gè)子進(jìn)程去完成,每個(gè)子進(jìn)程其實(shí)還是通過 UglifyJS 去壓縮代碼,但是變成了并行執(zhí)行。 所以 ParallelUglifyPlugin 能更快的完成對多個(gè)文件的壓縮工作。
使用 ParallelUglifyPlugin 也非常簡單,把原來 Webpack 配置文件中內(nèi)置的 UglifyJsPlugin 去掉后,再替換成 ParallelUglifyPlugin,相關(guān)代碼如下:
const path = require('path');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = {
plugins: [
// 使用 ParallelUglifyPlugin 并行壓縮輸出的 JS 代碼
new ParallelUglifyPlugin({
// 傳遞給 UglifyJS 的參數(shù)
uglifyJS: {
},
}),
],
};
接入 ParallelUglifyPlugin 后,項(xiàng)目需要安裝新的依賴:
npm i -D webpack-parallel-uglify-plugin
安裝成功后,重新執(zhí)行構(gòu)建你會發(fā)現(xiàn)速度變快了許多。如果設(shè)置 cacheDir 開啟了緩存,在之后的構(gòu)建中會變的更快。
縮小文件搜索范圍
Webpack 啟動(dòng)后會從配置的 Entry 出發(fā),解析出文件中的導(dǎo)入語句,再遞歸的解析。 在遇到導(dǎo)入語句時(shí) Webpack 會做兩件事情:
根據(jù)導(dǎo)入語句去尋找對應(yīng)的要導(dǎo)入的文件。例如 require(‘react') 導(dǎo)入語句對應(yīng)的文件是 ./node_modules/react/react.js,而require(‘./util')導(dǎo)入語句 對應(yīng)的文件是 ./util.js。
根據(jù)找到的要導(dǎo)入文件的后綴,使用配置中的 Loader 去處理文件。例如使用 ES6 開發(fā)的 JavaScript 文件需要使用 babel-loader 去處理。
以上兩件事情雖然對于處理一個(gè)文件非???,但是當(dāng)項(xiàng)目大了以后文件量會變的非常多,這時(shí)候構(gòu)建速度慢的問題就會暴露出來。 雖然以上兩件事情無法避免,但需要盡量減少以上兩件事情的發(fā)生,以提高速度。
接下來一一介紹可以優(yōu)化它們的途徑。
縮小 resolve.modules 的范圍
Webpack的resolve.modules 用于配置 Webpack 去哪些目錄下尋找第三方模塊。
resolve.modules 的默認(rèn)值是 [‘node_modules'],含義是先去當(dāng)前目錄下的 ./node_modules 目錄下去找想找的模塊,如果沒找到就去上一級目錄 ../node_modules 中找,再沒有就去 ../../node_modules 中找,以此類推,這和 Node.js 的模塊尋找機(jī)制很相似。
當(dāng)安裝的第三方模塊都放在項(xiàng)目根目錄下的 ./node_modules 目錄下時(shí),沒有必要按照默認(rèn)的方式去一層層的尋找,可以指明存放第三方模塊的絕對路徑,以減少尋找,配置如下:
module.exports = {
resolve: {
// 使用絕對路徑指明第三方模塊存放的位置,以減少搜索步驟
// 其中 __dirname 表示當(dāng)前工作目錄,也就是項(xiàng)目根目錄
modules: [path.resolve(__dirname, 'node_modules')]
},
};
縮小 Loader 的命中范圍
除此之外在使用 Loader 時(shí)可以通過 test 、 include 、 exclude 三個(gè)配置項(xiàng)來命中 Loader 要應(yīng)用規(guī)則的文件。 為了盡可能少的讓文件被 Loader 處理,可以通過 include 去命中只有哪些文件需要被處理。
以采用 ES6 的項(xiàng)目為例,在配置 babel-loader 時(shí),可以這樣:
module.exports = {
module: {
rules: [
{
// 如果項(xiàng)目源碼中只有 js 文件就不要寫成 /\.jsx?$/,提升正則表達(dá)式性能
test: /\.js$/,
// babel-loader 支持緩存轉(zhuǎn)換出的結(jié)果,通過 cacheDirectory 選項(xiàng)開啟
use: ['babel-loader?cacheDirectory'],
// 只對項(xiàng)目根目錄下的 src 目錄中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
]
},
};
你可以適當(dāng)?shù)恼{(diào)整項(xiàng)目的目錄結(jié)構(gòu),以方便在配置 Loader 時(shí)通過 include 去縮小命中范圍。
縮小 resolve.extensions 的數(shù)量
在導(dǎo)入語句沒帶文件后綴時(shí),Webpack 會自動(dòng)帶上后綴后去嘗試詢問文件是否存在。 Webpack 配置中的 resolve.extensions 用于配置在嘗試過程中用到的后綴列表,默認(rèn)是:
extensions: ['.js', '.json']
也就是說當(dāng)遇到 require(‘./data') 這樣的導(dǎo)入語句時(shí),Webpack 會先去尋找 ./data.js 文件,如果該文件不存在就去尋找 ./data.json 文件,如果還是找不到就報(bào)錯(cuò)。
如果這個(gè)列表越長,或者正確的后綴在越后面,就會造成嘗試的次數(shù)越多,所以 resolve.extensions 的配置也會影響到構(gòu)建的性能。 在配置 resolve.extensions 時(shí)你需要遵守以下幾點(diǎn),以做到盡可能的優(yōu)化構(gòu)建性能:
后綴嘗試列表要盡可能的小,不要把項(xiàng)目中不可能存在的情況寫到后綴嘗試列表中。
頻率出現(xiàn)最高的文件后綴要優(yōu)先放在最前面,以做到盡快的退出尋找過程。
在源碼中寫導(dǎo)入語句時(shí),要盡可能的帶上后綴,從而可以避免尋找過程。例如在你確定的情況下把 require(‘./data') 寫成 require(‘./data.json')。
相關(guān) Webpack 配置如下:
module.exports = {
resolve: {
// 盡可能的減少后綴嘗試的可能性
extensions: ['js'],
},
};
縮小 resolve.mainFields 的數(shù)量
Webpack 配置中的 resolve.mainFields 用于配置第三方模塊使用哪個(gè)入口文件。
安裝的第三方模塊中都會有一個(gè) package.json 文件用于描述這個(gè)模塊的屬性,其中有些字段用于描述入口文件在哪里,resolve.mainFields 用于配置采用哪個(gè)字段作為入口文件的描述。
可以存在多個(gè)字段描述入口文件的原因是因?yàn)橛行┠K可以同時(shí)用在多個(gè)環(huán)境中,針對不同的運(yùn)行環(huán)境需要使用不同的代碼。 以 isomorphic-fetchfetch API 為例,它是 的一個(gè)實(shí)現(xiàn),但可同時(shí)用于瀏覽器和 Node.js 環(huán)境。
為了減少搜索步驟,在你明確第三方模塊的入口文件描述字段時(shí),你可以把它設(shè)置的盡量少。 由于大多數(shù)第三方模塊都采用 main 字段去描述入口文件的位置,可以這樣配置 Webpack:
module.exports = {
resolve: {
// 只采用 main 字段作為入口文件描述字段,以減少搜索步驟
mainFields: ['main'],
},
};
使用本方法優(yōu)化時(shí),你需要考慮到所有運(yùn)行時(shí)依賴的第三方模塊的入口文件描述字段,就算有一個(gè)模塊搞錯(cuò)了都可能會造成構(gòu)建出的代碼無法正常運(yùn)行。
善用現(xiàn)存的文件
通過 module.noParse 忽略文件
Webpack 配置中的 module.noParse 配置項(xiàng)可以讓 Webpack 忽略對部分沒采用模塊化的文件的遞歸解析處理,這樣做的好處是能提高構(gòu)建性能。 原因是一些庫,例如 jQuery 、ChartJS, 它們龐大又沒有采用模塊化標(biāo)準(zhǔn),讓 Webpack 去解析這些文件耗時(shí)又沒有意義。
在上面的 優(yōu)化 resolve.alias 配置 中講到單獨(dú)完整的 react.min.js 文件就沒有采用模塊化,讓我們來通過配置 module.noParse 忽略對 react.min.js 文件的遞歸解析處理, 相關(guān) Webpack 配置如下:
module.exports = {
module: {
// 獨(dú)完整的 `react.min.js` 文件就沒有采用模塊化,忽略對 `react.min.js` 文件的遞歸解析處理
noParse: [/react\.min\.js$/],
},
};
注意被忽略掉的文件里不應(yīng)該包含 import 、 require 、 define 等模塊化語句,不然會導(dǎo)致構(gòu)建出的代碼中包含無法在瀏覽器環(huán)境下執(zhí)行的模塊化語句。
通過 resolve.alias 映射文件
Webpack 配置中的 resolve.alias 配置項(xiàng)通過別名來把原導(dǎo)入路徑映射成一個(gè)新的導(dǎo)入路徑。
在實(shí)戰(zhàn)項(xiàng)目中經(jīng)常會依賴一些龐大的第三方模塊,以 React 庫為例,庫中包含兩套代碼:
一套是采用 CommonJS 規(guī)范的模塊化代碼,這些文件都放在 lib 目錄下,以 package.json 中指定的入口文件 react.js 為模塊的入口。
一套是把 React 所有相關(guān)的代碼打包好的完整代碼放到一個(gè)單獨(dú)的文件中,這些代碼沒有采用模塊化可以直接執(zhí)行。其中 dist/react.js 是用于開發(fā)環(huán)境,里面包含檢查和警告的代碼。dist/react.min.js 是用于線上環(huán)境,被最小化了。
默認(rèn)情況下 Webpack 會從入口文件 ./node_modules/react/react.js 開始遞歸的解析和處理依賴的幾十個(gè)文件,這會時(shí)一個(gè)耗時(shí)的操作。 通過配置 resolve.alias 可以讓 Webpack 在處理 React 庫時(shí),直接使用單獨(dú)完整的 react.min.js 文件,從而跳過耗時(shí)的遞歸解析操作。
相關(guān) Webpack 配置如下:
module.exports = {
resolve: {
// 使用 alias 把導(dǎo)入 react 的語句換成直接使用單獨(dú)完整的 react.min.js 文件,
// 減少耗時(shí)的遞歸解析操作
alias: {
'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
}
},
};
除了 React 庫外,大多數(shù)庫發(fā)布到 Npm 倉庫中時(shí)都會包含打包好的完整文件,對于這些庫你也可以對它們配置 alias。
但是對于有些庫使用本優(yōu)化方法后會影響到后面要講的使用 Tree-Shaking 去除無效代碼的優(yōu)化,因?yàn)榇虬玫耐暾募杏胁糠执a你的項(xiàng)目可能永遠(yuǎn)用不上。 一般對整體性比較強(qiáng)的庫采用本方法優(yōu)化,因?yàn)橥暾募械拇a是一個(gè)整體,每一行都是不可或缺的。 但是對于一些工具類的庫,例如 lodash,你的項(xiàng)目可能只用到了其中幾個(gè)工具函數(shù),你就不能使用本方法去優(yōu)化,因?yàn)檫@會導(dǎo)致你的輸出代碼中包含很多永遠(yuǎn)不會執(zhí)行的代碼。
使用 DllPlugin
在介紹 DllPlugin 前先給大家介紹下 DLL。 用過 Windows 系統(tǒng)的人應(yīng)該會經(jīng)常看到以 .dll 為后綴的文件,這些文件稱為動(dòng)態(tài)鏈接庫,在一個(gè)動(dòng)態(tài)鏈接庫中可以包含給其他模塊調(diào)用的函數(shù)和數(shù)據(jù)。
要給 Web 項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫的思想,需要完成以下事情:
把網(wǎng)頁依賴的基礎(chǔ)模塊抽離出來,打包到一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫中去。一個(gè)動(dòng)態(tài)鏈接庫中可以包含多個(gè)模塊。
當(dāng)需要導(dǎo)入的模塊存在于某個(gè)動(dòng)態(tài)鏈接庫中時(shí),這個(gè)模塊不能再次被打包,而是去動(dòng)態(tài)鏈接庫中獲取。
頁面依賴的所有動(dòng)態(tài)鏈接庫需要被加載。
為什么給 Web 項(xiàng)目構(gòu)建接入動(dòng)態(tài)鏈接庫的思想后,會大大提升構(gòu)建速度呢? 原因在于包含大量復(fù)用模塊的動(dòng)態(tài)鏈接庫只需要編譯一次,在之后的構(gòu)建過程中被動(dòng)態(tài)鏈接庫包含的模塊將不會在重新編譯,而是直接使用動(dòng)態(tài)鏈接庫中的代碼。 由于動(dòng)態(tài)鏈接庫中大多數(shù)包含的是常用的第三方模塊,例如 react、react-dom,只要不升級這些模塊的版本,動(dòng)態(tài)鏈接庫就不用重新編譯。
接入 Webpack
Webpack 已經(jīng)內(nèi)置了對動(dòng)態(tài)鏈接庫的支持,需要通過2個(gè)內(nèi)置的插件接入,它們分別是:
DllPlugin 插件:用于打包出一個(gè)個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫文件。
DllReferencePlugin 插件:用于在主要配置文件中去引入 DllPlugin 插件打包好的動(dòng)態(tài)鏈接庫文件。
下面以基本的 React 項(xiàng)目為例,為其接入 DllPlugin,在開始前先來看下最終構(gòu)建出的目錄結(jié)構(gòu):
├── main.js
├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json
其中包含兩個(gè)動(dòng)態(tài)鏈接庫文件,分別是:
polyfill.dll.js 里面包含項(xiàng)目所有依賴的 polyfill,例如 Promise、fetch 等 API。
react.dll.js 里面包含 React 的基礎(chǔ)運(yùn)行環(huán)境,也就是 react 和 react-dom 模塊。
以 react.dll.js 文件為例,其文件內(nèi)容大致如下:
var _dll_react = (function(modules) {
// ... 此處省略 webpackBootstrap 函數(shù)代碼
}([
function(module, exports, __webpack_require__) {
// 模塊 ID 為 0 的模塊對應(yīng)的代碼
}
// ... 此處省略剩下的模塊對應(yīng)的代碼
]));
可見一個(gè)動(dòng)態(tài)鏈接庫文件中包含了大量模塊的代碼,這些模塊存放在一個(gè)數(shù)組里,用數(shù)組的索引號作為 ID。 并且還通過 _dll_react 變量把自己暴露在了全局中,也就是可以通過 window._dll_react 可以訪問到它里面包含的模塊。
其中 polyfill.manifest.json 和 react.manifest.json 文件也是由 DllPlugin 生成,用于描述動(dòng)態(tài)鏈接庫文件中包含哪些模塊, 以 react.manifest.json 文件為例,其文件內(nèi)容大致如下:
{
// 描述該動(dòng)態(tài)鏈接庫文件暴露在全局的變量名稱
"name": "_dll_react",
"content": {
"./node_modules/process/browser.js": {
"id": 0,
"meta": {}
},
// ... 此處省略部分模塊
}
}
可見 manifest.json 文件清楚地描述了與其對應(yīng)的 dll.js 文件中包含了哪些模塊,以及每個(gè)模塊的路徑和 ID。
main.js 文件是編譯出來的執(zhí)行入口文件,當(dāng)遇到其依賴的模塊在 dll.js 文件中時(shí),會直接通過 dll.js 文件暴露出的全局變量去獲取打包在 dll.js 文件的模塊。 所以在 index.html 文件中需要把依賴的兩個(gè) dll.js 文件給加載進(jìn)去,index.html 內(nèi)容如下:
<!--導(dǎo)入依賴的動(dòng)態(tài)鏈接庫文件-->
<script src="./dist/polyfill.dll.js"></script>
<script src="./dist/react.dll.js"></script>
<!--導(dǎo)入執(zhí)行入口文件-->
<script src="./dist/main.js"></script>
以上就是所有接入 DllPlugin 后最終編譯出來的代碼,接下來教你如何實(shí)現(xiàn)。
構(gòu)建出動(dòng)態(tài)鏈接庫文件
構(gòu)建輸出的以下這四個(gè)文件
├── polyfill.dll.js
├── polyfill.manifest.json
├── react.dll.js
└── react.manifest.json
和以下這一個(gè)文件
是由兩份不同的構(gòu)建分別輸出的。
與動(dòng)態(tài)鏈接庫相關(guān)的文件需要由一個(gè)獨(dú)立的構(gòu)建輸出,用于給主構(gòu)建使用。新建一個(gè) Webpack 配置文件 webpack_dll.config.js 專門用于構(gòu)建它們,文件內(nèi)容如下:
const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
// JS 執(zhí)行入口文件
entry: {
// 把 React 相關(guān)模塊的放到一個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫
react: ['react', 'react-dom'],
// 把項(xiàng)目需要所有的 polyfill 放到一個(gè)單獨(dú)的動(dòng)態(tài)鏈接庫
polyfill: ['core-js/fn/object/assign', 'core-js/fn/promise', 'whatwg-fetch'],
},
output: {
// 輸出的動(dòng)態(tài)鏈接庫的文件名稱,[name] 代表當(dāng)前動(dòng)態(tài)鏈接庫的名稱,
// 也就是 entry 中配置的 react 和 polyfill
filename: '[name].dll.js',
// 輸出的文件都放到 dist 目錄下
path: path.resolve(__dirname, 'dist'),
// 存放動(dòng)態(tài)鏈接庫的全局變量名稱,例如對應(yīng) react 來說就是 _dll_react
// 之所以在前面加上 _dll_ 是為了防止全局變量沖突
library: '_dll_[name]',
},
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 動(dòng)態(tài)鏈接庫的全局變量名稱,需要和 output.library 中保持一致
// 該字段的值也就是輸出的 manifest.json 文件 中 name 字段的值
// 例如 react.manifest.json 中就有 "name": "_dll_react"
name: '_dll_[name]',
// 描述動(dòng)態(tài)鏈接庫的 manifest.json 文件輸出時(shí)的文件名稱
path: path.join(__dirname, 'dist', '[name].manifest.json'),
}),
],
};
使用動(dòng)態(tài)鏈接庫文件
構(gòu)建出的動(dòng)態(tài)鏈接庫文件用于在其它地方使用,在這里也就是給執(zhí)行入口使用。
用于輸出 main.js 的主 Webpack 配置文件內(nèi)容如下:
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
plugins: [
// 告訴 Webpack 使用了哪些動(dòng)態(tài)鏈接庫
new DllReferencePlugin({
// 描述 react 動(dòng)態(tài)鏈接庫的文件內(nèi)容
manifest: require('./dist/react.manifest.json'),
}),
new DllReferencePlugin({
// 描述 polyfill 動(dòng)態(tài)鏈接庫的文件內(nèi)容
manifest: require('./dist/polyfill.manifest.json'),
}),
],
devtool: 'source-map'
};
注意:在 webpack_dll.config.js 文件中,DllPlugin 中的 name 參數(shù)必須和 output.library 中保持一致。 原因在于 DllPlugin 中的 name 參數(shù)會影響輸出的 manifest.json 文件中 name 字段的值, 而在 webpack.config.js 文件中 DllReferencePlugin 會去 manifest.json 文件讀取 name 字段的值, 把值的內(nèi)容作為在從全局變量中獲取動(dòng)態(tài)鏈接庫中內(nèi)容時(shí)的全局變量名。
執(zhí)行構(gòu)建
在修改好以上兩個(gè) Webpack 配置文件后,需要重新執(zhí)行構(gòu)建。 重新執(zhí)行構(gòu)建時(shí)要注意的是需要先把動(dòng)態(tài)鏈接庫相關(guān)的文件編譯出來,因?yàn)橹?Webpack 配置文件中定義的 DllReferencePlugin 依賴這些文件。
執(zhí)行構(gòu)建時(shí)流程如下:
如果動(dòng)態(tài)鏈接庫相關(guān)的文件還沒有編譯出來,就需要先把它們編譯出來。方法是執(zhí)行 webpack –config webpack_dll.config.js 命令。
在確保動(dòng)態(tài)鏈接庫存在的前提下,才能正常的編譯出入口執(zhí)行文件。方法是執(zhí)行 webpack 命令。這時(shí)你會發(fā)現(xiàn)構(gòu)建速度有了非常大的提升。
相信給你的項(xiàng)目加上以上優(yōu)化方法后,構(gòu)建速度會大大提高,趕快去試試把!