webpack 打包JS 的運行原理
Webpack自動化構建實踐指南 - 掘金
一、打包原理
最近一直在學習 webpack 的相關知識,當清晰地領悟到 webpack
就是不同 loader
和 plugin
組合起來打包之後,只作為工具使用而言,算是入門了。當然,在過程中碰到數之不盡的坑,也產生了想要深入一點了解 webpack
的原理(主要是掉進坑能靠自己爬出來)。因而就從簡單的入手,先看看使用 webpack
打包後的 JS
文件是如何載入吧。
友情提示,本文簡單易懂,就算沒用過 webpack
問題都不大。如果已經了解過相關知識的朋友,不妨快速閱讀一下,算是溫故知新 。
簡單配置
既然需要用到 webpack
,還是需要簡單配置一下的,這裡就簡單貼一下代碼,首先是 webpack.config.js
:
const path = require(path);nconst webpack = require(webpack);n//用於插入html模板nconst HtmlWebpackPlugin = require(html-webpack-plugin);n//清除輸出目錄,免得每次手動刪除nconst CleanWebpackPlugin = require(clean-webpack-plugin);nnmodule.exports = {n entry: {n index: path.join(__dirname, index.js),n },n output: {n path: path.join(__dirname, /dist),n filename: js/[name].[chunkhash:4].jsn },n module: {},n plugins: [n new CleanWebpackPlugin([dist]),n new HtmlWebpackPlugin({n filename: index.html,n template: index.html,n }),n //持久化moduleId,主要是為了之後研究載入代碼好看一點。n new webpack.HashedModuleIdsPlugin(),n new webpack.optimize.CommonsChunkPlugin({n name: manifest,n })n ]n};n
這是我能想到近乎最簡單的配置,用到的兩個額外下載的插件都是十分常用的,也已經在注釋中簡單說明了。
之後是兩個簡單的 js
文件:
// test.jsnconst str = test is loaded;nmodule.exports = str;nn// index.jsnconst test = require(./src/js/test);nconsole.log(test);n
這個就不解釋了,貼一下打包後,項目的目錄結構應該是這樣的:
至此,我們的配置就完成了。
從 index.js
開始看代碼
先從打包後的 index.html
文件看看兩個 JS
文件的載入順序:
<body>nt<script type="text/javascript" src="js/manifest.2730.js"></script>nt<script type="text/javascript" src="js/index.5f4f.js"></script>n</body>n
可以看到,打包後 js
文件的載入順序是先 manifest.js
,之後才是 index.js
,按理說應該先看 manifest.js
的內容的。然而這裡先賣個關子,我們先看看 index.js
的內容是什麼,這樣可以帶著問題去了解 manifest.js
,也就是主流程的邏輯到底是怎樣的,為何能做到模塊化。
// index.jsnnwebpackJsonp([0], {n "JkW7": (function(module, exports, __webpack_require__) {n const test = __webpack_require__("zFrx");n console.log(test);n }),n "zFrx": (function(module, exports) {n const str = test is loaded;n module.exports = str;n })n}, ["JkW7"]);n
刪去各種奇怪的注釋後剩下這麼點內容,首先應該關注到的是 webpackJsonp
這個函數,可以看見是不在任何命名空間下的,也就是 manifest.js
應該定義了一個掛在 window
下的全局函數,index.js
往這個函數傳入三個參數並調用。
第一個參數是數組,現在暫時還不清楚這個數組有什麼作用。
第二個參數是一個對象,對象內都是方法,這些方法看起來至少接受兩個參數(名為 zFrx
的方法只有兩個形參)。看一眼這兩個方法的內部,其實看見了十分熟悉的東西, module.exports
,儘管看不見 require
, 但有一個樣子類似的 __webpack_require__
,這兩個應該是模塊化的關鍵,先記下這兩個函數。
第三個參數也是一個數組,也不清楚是有何作用的,但我們觀察到它的值是 JkW7
,與參數2中的某個方法的鍵是一致的,這可能存在某種邏輯關聯。
至此,index.js
的內容算是過了一遍,接下來應當帶著問題在 manifest.js
中尋找答案。
manifest.js
代碼閱讀
由於沒有配置任何壓縮 js
的選項,因此 manifest.js
的源碼大約在 150 行左右,簡化後為 28 行(已經跑過代碼,實測沒問題)。鑒於精簡後的代碼真的不多,因而先貼代碼,大家帶著剛才提出的問題,先看看能找到幾個答案:
(function(modules) {n window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {n var moduleId, result;n for (moduleId in moreModules) {n if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {n modules[moduleId] = moreModules[moduleId];n }n }n if (executeModules) {n for (i = 0; i < executeModules.length; i++) {n result = __webpack_require__(executeModules[i]);n }n }n return result;n };n var installedModules = {};nn function __webpack_require__(moduleId) {n if (installedModules[moduleId]) {n return installedModules[moduleId].exports;n }n var module = installedModules[moduleId] = {n exports: {}n };n modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);n return module.exports;n }n})([]);n
首先應該看到的是,manifest.js
內部是一個 IIFE
,就是自執行函數咯,這個函數會接受一個空數組作為參數,該數組被命名為 modules
。之後看到我們在 index.js
中的猜想,果然在 window
上掛了一個名為 webpackJsonp
的函數。它接受的三個參數,分別名為chunkIds
, moreModules
, executeModules
。對應了 index.js
中調用 webpackJsonp
時傳入的三個參數。而 webpackJsonp
內究竟是有怎樣的邏輯呢?
先不管定義的參數,webpackJsonp
先是 for in
遍歷了一次 moreModules
,將 moreModules
內的所有方法都存在 modules
, 也就是自執行函數執行時傳入的數組。
之後是一個條件判斷:
if (executeModules) {n for (i = 0; i < executeModules.length; i++) {n result = __webpack_require__(executeModules[i]);n }n}n
判斷 executeModules
, 也就是第三個參數是否存在,如存在即執行 __webpack_require__
方法。在 index.js
調用 webpackJsonp
方法時,這個參數當然是存在的,因而要看看 __webpack_require__
方法是什麼了。
__webpack_require__
接受一個名為 moduleId
的參數。方法內部首先是一個條件判斷,先不管。接下來看到賦值邏輯
var module = installedModules[moduleId] = {n exports: {}n};n
結合剛才的條件判斷,可以推測出 installedModules
是一個緩存的容器,那麼前面的代碼意思就是如果緩存中有對應的 moduleId
,那麼直接返回它的 exports
,不然就定義並賦值一個吧。接著先偷看一下 __webpack_require__
的最後的返回值,可以看到函數返回的是 module.exports
,那麼 module.exports
又是如何被賦值的呢? 看看之後的代碼:
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);n
剛才我們知道 modules[moduleId]
就是 moreModules
中的方法,此處就是將 this
指定為 module.exports
,再把module
, module.exports
, __webpack_require__
傳入去作為參數調用。這三個參數是不是很熟悉?之前我們看 index.js
裡面代碼時,有一個疑問就是模塊化是如何實現的。這裡我們已經看出了眉目。
其實 webpack
就是將每一個 js
文件封裝成一個函數,每個文件中的 require
方法對應的就是 __webpack_require__
, __webpack_require__
會根據傳入的 moduleId
再去載入對應的代碼。而當我們想導出 js
文件的值時,要麼用 module.exports
,要麼用 exports
,這就對應了module
, module.exports
兩個參數。少接觸這塊的童鞋,應該就能理解為何導出值時,直接使用 exports = xxx
會導出失敗了。簡單舉個例子:
const module = {n exports: {}n};nnfunction demo1(module) {n module.exports = 1;n}nndemo1(module);nconsole.log(module.exports); // 1nnfunction demo2(exports) {n exports = 2;n}nndemo2(module.exports);nconsole.log(module.exports); // 1n
粘貼這段代碼去瀏覽器跑一下,可以發現兩次列印出來都是1。這和 wenpack
打包邏輯是一模一樣的。
梳理一下打包後代碼執行的流程,首先 minifest.js
會定義一個 webpackJsonp
方法,待其他打包後的文件(也可稱為 chunk
)調用。當調用 chunk
時,會先將該 chunk
中所有的 moreModules
, 也就是每一個依賴的文件也可稱為 module
(如 test.js
)存起來。之後通過 executeModules
判斷這個文件是不是入口文件,決定是否執行第一次 __webpack_require__
。而 __webpack_require__
的作用,就是根據這個 module
所 require
的東西,不斷遞歸調用 __webpack_require__
,__webpack_require__
函數返回值後供 require
使用。當然,模塊是不會重複載入的,因為 installedModules
記錄著 module
調用後的 exports
的值,只要命中緩存,就返回對應的值而不會再次調用 module
。webpack
打包後的文件,就是通過一個個函數隔離 module
的作用域,以達到不互相污染的目的。
二、非同步載入
簡單配置
webpack
的配置就不貼出來了,就是確定一下入口,提取 webpack
運行時需要用到的 minifest.js
而已。這裡簡單貼一下 html
模板與需要的兩個 js
文件:
<!--index.html-->n<!doctype html>n<html lang="en">n<body>n <p class="p">Nothing yet.</p>n <button class="btn">click</button>n</body>n</html>nnn//index.jsnconst p = document.querySelector(.p);nconst btn = document.querySelector(.btn);nbtn.addEventListener(click, function() {n //只有觸發事件才回家再對應的js 也就是非同步載入 n require.ensure([], function() {n const data = require(./src/js/test);n p.innerHTML = data;n })n})nn//test.jsnconst data = success!;nmodule.exports = data;n
這樣配置示例配置就完成了。可能有小夥伴不太熟悉 require.ensure
,簡單地說,就是告訴 webpack
,請懶載入 test.js
,別一打開頁面就給我下載下來。相關的知識不妨看這裡。
打包完的目錄架構畫風是這樣的:
至此,配置就完成啦~
從 index.js
開始探索
先用瀏覽器打開 index.html
,查看資源載入情況,能發現只載入了 index.js
與 minifest.js
:
之後點擊按鈕,會再加多一個 0.7f0a.js
:
可以說明代碼是被分割了的,只要當對應的條件觸發時,瀏覽器才會去載入指定的資源。而無論之後我們點擊多少次,0.7f0a.js
文件都不會重複載入,此時小本本應記下第一個問題:如何做到不重複載入。
按照載入順序,其實是應該先砍 minifest.js
的,但不妨先看看 index.js
的代碼,帶著問題有助於尋找答案。代碼如下:
webpackJsonp([1], {n "JkW7":n (function(module, exports, __webpack_require__) {n const p = document.querySelector(.p);n const btn = document.querySelector(.btn);nn btn.addEventListener(click, function() {n __webpack_require__.e(0).then((function() {n const data = __webpack_require__("zFrx");n p.innerHTML = data;n }).bind(null, __webpack_require__)).catch(__webpack_require__.oe)n })n })n}, ["JkW7"]);n
可能有些小夥伴已經忘記了上一篇文章的內容,__webpack_require__
作用是載入對應 module
的內容。這裡提一句, module
其實就是打包前,import
或者 require
的一個個 js
文件,如test.js
與 index.js
。後文說到的 chunk
是打包後的文件,即 index.ad23.js
、manifest.473d.js
與 0.7f0a.js
文件。一個 chunk
可能包含若干 module
。
回憶起相關知識後,我們看看非同步載入到底有什麼不同。index.js
中最引入注目的應該是 __webpack_require__.e
這個方法了,傳入一個數值之後返回一個 promise
。這方法當 promise
決議成功後執行切換文本的邏輯,失敗則執行 __webpack_require__.oe
。因而小本本整理一下,算上剛才的問題,需要為這些問題找到答案:
- 如何做到不重複載入。
__webpack_require__.e
方法的邏輯。__webpack_require__.oe
方法的邏輯。
在 minifest.js
中尋找答案
我們先查看一下 __webpack_require__.e
方法,為方法查看起見,貼一下對應的代碼,大家不妨先試著自己尋找一下剛才問題的答案。
var installedChunks = {n 2: 0n};nn__webpack_require__.e = function requireEnsure(chunkId) {n var installedChunkData = installedChunks[chunkId];n if (installedChunkData === 0) {n return new Promise(function(resolve) {n resolve();n });nn }n if (installedChunkData) {n return installedChunkData[2];n }nn var promise = new Promise(function(resolve, reject) {n installedChunkData = installedChunks[chunkId] = [resolve, reject];n });n installedChunkData[2] = promise;n var head = document.getElementsByTagName(head)[0];n var script = document.createElement(script);n script.src = "js/" + chunkId + "." + {n "0": "7f0a",n "1": "ad23"n }[chunkId] + ".js";n script.onerror = script.onload = onScriptComplete;nn function onScriptComplete() {n script.onerror = script.onload = null;n var chunk = installedChunks[chunkId];n if (chunk !== 0) {n if (chunk) {n chunk[1](new Error(Loading chunk + chunkId + failed.));n }n installedChunks[chunkId] = undefined;n }n };n head.appendChild(script);n return promise;n};n
該方法中接受一個名為 chunkId
的參數,返回一個 promise
,印證了我們閱讀 index.js
時的猜想,也確認了傳入的數字是 chunkId
。之後變數 installedChunkData
被賦值為對象 installedChunks
中鍵為 chunkId
的值,可以推想出 installedChunks
對象其實就是記錄已載入 chunk
的地方。此時我們尚未載入對應模塊,理所當然是 undefined
。
之後我們想跳過兩個判斷,查看一下 __webpack_require__.e
方法返回值的 promise
是怎樣的:
var promise = new Promise(function(resolve, reject) {n installedChunkData = installedChunks[chunkId] = [resolve, reject];n});ninstalledChunkData[2] = promise;n
可以看到 installedChunkData
與 installedChunks[chunkId]
被重新賦值為一個數組,存放著返回值 promise
的 resolve
與 reject
,而令人不解的是,為何將數組的第三項賦值為這個 promise
呢?
其實此前有一個條件判斷:
if (installedChunkData) {n return installedChunkData[2];n}n
那你明白為什麼了嗎?在此例中1,假設網路很差的情況下,我們瘋狂點擊按鈕,為避免瀏覽器發出若干個請求,通過條件判斷都返回同一個 promise
,當它決議後,所有掛載在它之上的 then
方法都能得到結果運行下去,相當於構造了一個隊列,返回結果後按順序執行對應方法,此處還是十分巧妙的。
之後就是創造一個 script
標籤插入頭部,載入指定的 js
了。值得關注的是 onScriptComplete
方法中的判斷:
var chunk = installedChunks[chunkId];nif (chunk !== 0) {n ...n}n
明明 installedChunks[chunkId]
被賦值為數組,它肯定不可能為0啊,這不是鐵定失敗了么?先別急,要知道 js
文件下載成功之後,先執行內容,再執行 onload
方法的,那麼它的內容是什麼呢?
webpackJsonp([0], {n "zFrx":n (function(module, exports) {n const data = success!;n module.exports = data;n })n});n
可以看到,和 index.js
還是很像的。這個 js
文件的 chunkId
是0。它的內容很簡單,只不過是 module.exports
出去了一些東西。關鍵還是 webpackJsonp
方法,此處截取關鍵部分:
var resolves = [];nnfor (; i < chunkIds.length; i++) {n chunkId = chunkIds[i];n if (installedChunks[chunkId]) {n resolves.push(installedChunks[chunkId][0]);n }n installedChunks[chunkId] = 0;n}nnwhile (resolves.length) {n resolves.shift()();n}n
當它執行的時候,會判斷 installedChunks[chunkId]
是否存在,若存在則往數組中 push(installedChunks[chunkId][0])
並將 installedChunks[chunkId]
賦值為0; 。還得記得數組的首項是什麼嗎?是 __webpack_require__.e
返回 promise
的 resolve
!之後執行這個 resolve
。當然, webpackJsonp
方法會將下載下來文件所有的 module
存起來,當 __webpack_require__
對應 modulIde
時,返回對應的值。
讓我們目光返回 __webpack_require__.e
方法。
js
文件下載成功後,installedChunks[chunkId]
被賦值為0。文件執行完或下載失敗後都會觸發 onScriptComplete
方法,在該方法中,如若 installedChunks[chunkId] !== 0
,這是下載失敗的情況,那麼此時 installedChunks[chunkId]
的第二項是返回 promise
的 reject
,執行這個 reject
以拋出錯誤:if (chunk !== 0) {n if (chunk) {n chunk[1](new Error(Loading chunk + chunkId + failed.));n }n installedChunks[chunkId] = undefined;n}n
當再次請求同一文件時,由於對應的 module
已經被載入,因而直接返回一個成功的 promise
即可,對應的邏輯如下:
var installedChunkData = installedChunks[chunkId];nif (installedChunkData === 0) {n return new Promise(function(resolve) {n resolve();n });n}n
最後看一下 __webpack_require__.oe
方法:
__webpack_require__.oe = function(err) { console.error(err); throw err; };n
特別簡單對吧?最後整理一下流程:當非同步請求文件發起時,先判斷該 chunk
是否已被載入,是的話直接返回一個成功的 promise
,讓 then
執行的函數 require
對應的 module
即可。不然則構造一個 script
標籤載入對應的 chunk
,下載成功後掛載該 chunk
內所有的 module
。下載失敗則列印錯誤。
三、代碼打包優化
基礎配置
CommonsChunkPlugin
插件,是一個可選的用於建立一個獨立文件(又稱作 chunk)的功能,這個文件包括多個入口 chunk 的公共模塊。通過將公共模塊拆出來,最終合成的文件能夠在最開始的時候載入一次,便存起來到緩存中供後續使用。這個帶來速度上的提升,因為瀏覽器會迅速將公共的代碼從緩存中取出來,而不是每次訪問一個新頁面時,再去載入一個更大的文件。
簡單來說,這有點像封裝函數。把不變的與變化的分開,使得不變的可以高效復用,變化的靈活配置。接下來會根據這個原則優化我們的項目,現在先看看虛擬的項目長成什麼樣吧~
新建一個 index.html
模板與入口 index.js
文件,簡單配置如下:
index.html :
<!doctype html>n<html lang="en">n<head>n <meta charset="UTF-8">n <meta name="viewport"n content="width_=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">n <meta http-equiv="X-UA-Compatible" content="ie=edge">n <title>Document</title>n</head>n<body>n <div id="app">n <p>{{ vue_test }}</p>n </div>n <div class="jq_test"></div>n</body>n</html>n
index.js
:
import Vue from vue;nimport $ from jquery;nnnew Vue({n el: #app,n data: {n vue_test: vue is loaded!n }n})nn$(function() {n $(.jq_test).html(jquery is loaded!)n})n
為演示起見,代碼十分簡單,相信不用多加解釋。接下來先簡單配置一下 webpack.config.js
,代碼如下:
const path = require(path);nconst webpack = require(webpack);nconst HtmlWebpackPlugin = require(html-webpack-plugin);nconst CleanWebpackPlugin = require(clean-webpack-plugin);nconst BundleAnalyzerPlugin = require(webpack-bundle-analyzer).BundleAnalyzerPlugin;nnmodule.exports = {n entry: {n index: path.join(__dirname, index.js)n },n output: {n path: path.join(__dirname, /dist),n filename: js/[name].[chunkhash].jsn },n resolve: { alias: { vue: vue/dist/vue.js } },n plugins: [n new CleanWebpackPlugin([./dist]),n new HtmlWebpackPlugin({n filename: index.html,n template: index.html,n inject: truen }),n new BundleAnalyzerPlugin(),n ]n};n
CleanWebpackPlugin
主要用於清除 dist
目錄下的文件,這樣每次打包就不必手動清除了。HtmlWebpackPlugin
則是為了在 dist
目錄下新建 html
模板並自動插入依賴的 js
。 BundleAnalyzerPlugin
主要是為了生成打包後的 js
文件包含的依賴,如此時進行打包,則生成:
可以看到生成的 index.js
文件包含了 vue
與 jquery
。
首次優化
一般而言,我們項目中的類庫變化較少,業務代碼倒是多變的。需要想辦法把類庫抽離出來,把業務代碼單獨打包。這樣加傷 hash
後瀏覽器就能緩存類庫的 js
文件,優化用戶體驗。此時我們的主角 CommonsChunkPlugin
就正式登場了。我們在 webpack.config.js
文件的 plugins
中添加 CommonsChunkPlugin
,配置如下:
plugins: [n //...此前的代碼n new webpack.optimize.CommonsChunkPlugin({n name: vendor,n minChunks: function(module) {n return (n module.resource &&n /.js$/.test(module.resource) &&n module.resource.indexOf(n path.join(__dirname, ./node_modules)n ) === 0n )n }n }),n]n
上述配置,是通過 CommonsChunkPlugin
生成一個名為 vendor
的 js
文件,它抽取入口文件也就是 index.js
中來源於 node_modules
的依賴組成。此例中就是 vue
與 jquery
。打包出來畫風是這樣的:
此時看上去解決了我們的問題,將依賴的類庫抽取抽來獨立打包,加上緩存就能被瀏覽器緩存了。然而事情沒那麼簡單,不行你隨意改一下入口的 index.js
代碼,再次打包:
絕望地發現 vendor.js
文件的 hash
改變了。簡單說,這是因為模塊標識產生了變化所導致的,更具體的原因可以查看相關的中文文檔~修正的方法其實也挺簡單,就是再使用 CommonsChunkPlugin
抽取一次模塊,將不變的類庫沉澱下來,將變化的抽離出去。因而添如下代碼:
plugins: [n //...此前的代碼n new webpack.optimize.CommonsChunkPlugin({n name: vendor,n minChunks: function(module) {n return (n module.resource &&n /.js$/.test(module.resource) &&n module.resource.indexOf(n path.join(__dirname, ./node_modules)n ) === 0n )n }n }),n new webpack.optimize.CommonsChunkPlugin({n name: manifest,n chunks: [vendor, index]n })n]n
打包後, dist/js
目錄下多出一個名為 manifest
的 js
文件,此時你無論如何改變 index.js
的代碼,打包後的 vendor.js
的 hash
都不再會改變了。
然而稍等,當你想拍拍手收工的時候,思考一下這樣的場景:隨著項目不斷迭代,vendor
中的依賴不斷被添加與刪除,使得它的 hash
會不斷變化,這顯然不符合我們的利益,這到底如何解決呢?
再次優化
既然 CommonsChunkPlugin
是可以按照我們的需求抽取模塊,而依賴的外部模塊可能是不斷變化的,那麼為何不將基礎的依賴模塊抽取出來作為一個文件,其他的依賴如插件等作為另一個文件呢?
簡單說,如我們的項目中 vue
是基本的依賴,必須用到它,而 jquery
等則是後加的類庫,之後可能變更。那麼將 vue
獨立打包一個文件,有利於瀏覽器緩存,因為無論此後添加更多的類庫或刪去 jquery
時, vue
文件的緩存依然是生效的。因而我們可以這麼做,首先新建一個入口:
entry: {n index: path.join(__dirname, index.js),n vendor: [vue],n},n
此處主要是用於指明需要獨立打包的依賴有哪些。之後在 plugins
中做如下修改:
plugins: [n //...此前的代碼n new webpack.HashedModuleIdsPlugin(),n new webpack.optimize.CommonsChunkPlugin({n name: vendor,n minChunks: Infinity,n }),n new webpack.optimize.CommonsChunkPlugin({n name: common,n minChunks: function(module) {n return (n module.resource &&n /.js$/.test(module.resource) &&n module.resource.indexOf(n path.join(__dirname, ./node_modules)n ) === 0n )n },n chunks: [index],n }),n new webpack.optimize.CommonsChunkPlugin({n name: manifest,n chunks: [vendor, common, index]n })n]n
插件 HashedModuleIdsPlugin
,是用於保持模塊引用的 module id
不變。而 CommonsChunkPlugin
則提取入口指定的依賴獨立打包,minChunks: Infinity,
的用意是讓插件別管其他,就按照設置的數組提取文件就好。之後修改一下原來的 vendor
,重命名為 common
,指定它從入口 index.js
中抽取來自 node_modules
的依賴。最後就是抽取 webpack
運行時的函數及其模塊標識組成 manifest
。運行一下 webpack
,構建出來如圖:
可以看到 vue
與 jquery
被分開打包成了兩個文件,我們嘗試添加一下新的依賴 vuex
,打包後結果如下:
如此一來,我們的優化目的就達到了,不變的都提取出來,變化的可以動態配置~
小結
webpack
插件 CommonsChunkPlugin
就介紹到這裡了,然而優化還是有很多的,比如開啟壓縮,去除注釋等。而當項目體積逐漸增大時,CommonsChunkPlugin
就不一定是提取代碼的最優解了。在打包速度與控制構建的精細程度來說,結合 DLLPlugin
會有更好的表現。根據不同的場景組合不同的插件以達到我們的目的,本來就是 webpack
的魅力之一。
推薦閱讀:
※淺析 Webpack 插件化設計
※Webpack工程化解決方案easywebpack
※你的Tree-Shaking並沒什麼卵用
※Webpack 之 Loader 的使用
※基於 Webpack 3 的 Vue.js 工程項目腳手架
TAG:webpack |