繼上一篇以後,咱們今天來看看如何實現 webpack 的代碼切割(code-splitting)功能,最後實現的代碼版本請參考這裏。至於什麼是 code-splitting ,爲何要使用它,請直接參考官方文檔。前端
通常說來,code-splitting 有兩種含義:node
換句話說,咱們的目標是:將原先集中到一個 output.js 中的代碼,切割成若干個 js 文件,而後分別進行加載。 也就是說:原先只加載 output.js ,如今把代碼分割到3個文件中,先加載 output.js ,而後 output.js 又會自動加載 1.output.js 和 2.output.js 。jquery
既然要將一份代碼切割成若干份代碼,總得有個切割點的標誌吧,從哪兒開始切呢?
答案:webpack 使用require.ensure
做爲切割點。webpack
然而,我用 nodeJS 也挺長時間了,怎麼不知道還有require.ensure
這種用法?而事實上 nodeJS 也是不支持的,這個問題我在CommonJS 的標準中找到了答案:雖然 CommonJS 通俗地講是一個同步模塊加載規範,可是其中是包含異步加載相關內容的。只不過這條內容只停留在 PROPOSAL (建議)階段,並未最終進入標準,因此 nodeJS 沒有實現它也就不奇怪了。只不過 webpack 剛好利用了這個做爲代碼的切割點。git
ok,如今咱們已經明白了爲何要選擇require.ensure
做爲切割點了。接下來的問題是:如何根據切割點對代碼進行切割? 下面舉個例子。github
// example.js
var a = require("a");
var b = require("b");
a();
require.ensure(["c"], function(require) {
require("b")();
var d = require("d");
var c = require('c');
c();
d();
});
require.ensure(['e'], function (require) {
require('f')();
});複製代碼
假設這個 example.js 就是項目的主入口文件,模塊 a ~ f 是簡簡單單的模塊(既沒有進一步的依賴,也不包含require.ensure
)。那麼,這裏一共有2個切割點,這份代碼將被切割爲3部分。也就說,到時候會產生3個文件:output.js ,1.output.js ,2.output.jsweb
程序如何識別require.ensure
呢?答案天然是繼續使用強大的 esprima 。關鍵代碼以下:算法
// parse.js
if (expression.callee && expression.callee.type === 'MemberExpression'
&& expression.callee.object.type === 'Identifier' && expression.callee.object.name === 'require'
&& expression.callee.property.type === 'Identifier' && expression.callee.property.name === 'ensure'
&& expression.arguments && expression.arguments.length >= 1) {
// 處理require.ensure的依賴參數部分
let param = parseStringArray(expression.arguments[0])
let newModule = {
requires: [],
namesRange: expression.arguments[0].range
};
param.forEach(module => {
newModule.requires.push({
name: module
});
});
module.asyncs = module.asyncs || [];
module.asyncs.push(newModule);
module = newModule;
// 處理require.ensure的函數體部分
if(expression.arguments.length > 1) {
walkExpression(module, expression.arguments[1]);
}
}複製代碼
觀察上面的代碼能夠看出,識別出require.ensure
以後,會將其存儲到 asyncs 數組中,且繼續遍歷其中所包含的其餘依賴。舉個例子,example.js 模塊最終解析出來的數據結構以下圖所示:
express
我在剛剛使用 webpack 的時候,是分不清這兩個概念的。如今我能夠說:「在上面的例子中,有3個 chunk,分別對應 output.js、1.output.js 、2.output.js;有7個 module,分別是 example 和 a ~ f。json
因此,module 和 chunk 之間的關係是:1個 chunk 能夠包含若干個 module。
觀察上面的例子,得出如下結論:
好了,下面進入重頭戲。
在對各個模塊進行解析以後,咱們能大概獲得如下這樣結構的 depTree。
下面咱們要作的就是:如何從8個 module 中構建出3個 chunk 出來。 這裏的代碼較長,我就不貼出來了,想看的到這裏的 buildDep.js 。
其中要重點注意是:前文說到,爲了不代碼的冗餘,須要將模塊 c 從 chunk1 中移除,具體發揮做用的就是函數removeParentsModules
,本質上無非就是改變一下標誌位。最終生成的chunks的結構以下:
經歷重重難關,咱們終於來到了最後一步:如何根據構建出來的 chunks 拼接出若干個 output.js 呢?
此處的拼接與上一篇最後提到的拼接大同小異,主要不一樣點有如下2個:
其實關於 webpack 的代碼切割還有不少值得研究的地方。好比本文咱們實現的例子僅僅是將1個文件切割成3個,並未就其加載時機進行控制。好比說,如何支持在單頁面應用切換 router 的時候再加載特定的 x.output.js?
注:更多系列文章請移步個人博客
-------- EOF -----------
本文對你有幫助?歡迎掃碼加入前端學習小組微信羣: