深入瞭解 webpack 模組載入原理
webpack 是一個模組打包器,在它看來,每一個檔案都是一個模組。
無論你開發使用的是 CommonJS 規範還是 ES6 模組規範,打包後的檔案都統一使用 webpack 自定義的模組規範來管理、載入模組。本文將從一個簡單的示例開始,來講解 webpack 模組載入原理。
CommonJS 規範
假設現在有如下兩個檔案:
// index.js
const test2 = require('./test2')
function test() {}
test()
test2()
// test2.js
function test2() {}
module.exports = test2
以上兩個檔案使用 CommonJS 規範來匯入匯出檔案,打包後的程式碼如下(已經刪除了不必要的註釋):
(function(modules) { // webpackBootstrap // The module cache // 模組快取物件 var installedModules = {}; // The require function // webpack 實現的 require() 函式 function __webpack_require__(moduleId) { // Check if module is in cache // 如果模組已經載入過,直接返回快取 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) // 建立一個新模組,並放入快取 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function // 執行模組函式 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded // 將模組標識為已載入 module.l = true; // Return the exports of the module return module.exports; } // expose the modules object (__webpack_modules__) // 將所有的模組掛載到 require() 函式上 __webpack_require__.m = modules; // expose the module cache // 將快取物件掛載到 require() 函式上 __webpack_require__.c = installedModules; // define getter function for harmony exports __webpack_require__.d = function(exports, name, getter) { if(!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { enumerable: true, get: getter }); } }; // define __esModule on exports __webpack_require__.r = function(exports) { if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); } Object.defineProperty(exports, '__esModule', { value: true }); }; // create a fake namespace object // mode & 1: value is a module id, require it // mode & 2: merge all properties of value into the ns // mode & 4: return value when already ns object // mode & 8|1: behave like require __webpack_require__.t = function(value, mode) { if(mode & 1) value = __webpack_require__(value); if(mode & 8) return value; if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; var ns = Object.create(null); __webpack_require__.r(ns); Object.defineProperty(ns, 'default', { enumerable: true, value: value }); if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); return ns; }; // getDefaultExport function for compatibility with non-harmony modules __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; }; // Object.prototype.hasOwnProperty.call __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // __webpack_public_path__ __webpack_require__.p = ""; // Load entry module and return exports // 載入入口模組,並返回模組物件 return __webpack_require__(__webpack_require__.s = "./src/index.js"); })({ "./src/index.js": (function(module, exports, __webpack_require__) { eval("const test2 = __webpack_require__(/*! ./test2 */ \"./src/test2.js\")\r\n\r\nfunction test() {}\r\n\r\ntest()\r\ntest2()\n\n//# sourceURL=webpack:///./src/index.js?"); }), "./src/test2.js": (function(module, exports) { eval("function test2() {}\r\n\r\nmodule.exports = test2\n\n//# sourceURL=webpack:///./src/test2.js?"); }) });
可以看到 webpack 實現的模組載入系統非常簡單,僅僅只有一百行程式碼。
打包後的程式碼其實是一個立即執行函式,傳入的引數是一個物件。這個物件以檔案路徑為 key,以檔案內容為 value,它包含了所有打包後的模組。
{ "./src/index.js": (function(module, exports, __webpack_require__) { eval("const test2 = __webpack_require__(/*! ./test2 */ \"./src/test2.js\")\r\n\r\nfunction test() {}\r\n\r\ntest()\r\ntest2()\n\n//# sourceURL=webpack:///./src/index.js?"); }), "./src/test2.js": (function(module, exports) { eval("function test2() {}\r\n\r\nmodule.exports = test2\n\n//# sourceURL=webpack:///./src/test2.js?"); }) }
將這個立即函式化簡一下,相當於:
(function(modules){
// ...
})({
path1: function1,
path2: function2
})
再看一下這個立即函式做了什麼:
- 定義了一個模組快取物件
installedModules
,作用是快取已經載入過的模組。 - 定義了一個模組載入函式
__webpack_require__()
。 - ... 省略一些其他程式碼。
- 使用
__webpack_require__()
載入入口模組。
其中的核心就是 __webpack_require__()
函式,它接收的引數是 moduleId
,其實就是檔案路徑。
它的執行過程如下:
- 判斷模組是否有快取,如果有則返回快取模組的
export
物件,即module.exports
。 - 新建一個模組
module
,並放入快取。 - 執行檔案路徑對應的模組函式。
- 將這個新建的模組標識為已載入。
- 執行完模組後,返回該模組的
exports
物件。
// The require function
// webpack 實現的 require() 函式
function __webpack_require__(moduleId) {
// Check if module is in cache
// 如果模組已經載入過,直接返回快取
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
// 建立一個新模組,並放入快取
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
// 執行模組函式
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
// 將模組標識為已載入
module.l = true;
// Return the exports of the module
return module.exports;
}
從上述程式碼可以看到,在執行模組函式時傳入了三個引數,分別為 module
、module.exports
、__webpack_require__
。
其中 module
、module.exports
的作用和 CommonJS 中的 module
、module.exports
的作用是一樣的,而 __webpack_require__
相當於 CommonJS 中的 require
。
在立即函式的最後,使用了 __webpack_require__()
載入入口模組。並傳入了入口模組的路徑 ./src/index.js
。
__webpack_require__(__webpack_require__.s = "./src/index.js");
我們再來分析一下入口模組的內容。
(function(module, exports, __webpack_require__) {
eval("const test2 = __webpack_require__(/*! ./test2 */ \"./src/test2.js\")\r\n\r\nfunction test() {}\r\n\r\ntest()\r\ntest2()\n\n//# sourceURL=webpack:///./src/index.js?");
})
入口模組函式的引數正好是剛才所說的那三個引數,而 eval 函式的內容美化一下後和下面內容一樣:
const test2 = __webpack_require__("./src/test2.js")
function test() {}
test()
test2()
//# sourceURL=webpack:///./src/index.js?
將打包後的模組程式碼和原模組的程式碼進行對比,可以發現僅有一個地方發生了變化,那就是 require
變成了 __webpack_require__
。
再看一下 test2.js
的程式碼:
function test2() {}
module.exports = test2
//# sourceURL=webpack:///./src/test2.js?
從剛才的分析可知,__webpack_require__()
載入模組後,會先執行模組對應的函式,然後返回該模組的 exports
物件。而 test2.js
的匯出物件 module.exports
就是 test2()
函式。所以入口模組能通過 __webpack_require__()
引入 test2()
函式並執行。
到目前為止可以發現 webpack 自定義的模組規範完美適配 CommonJS 規範。
ES6 module
將剛才用 CommonJS 規範編寫的兩個檔案換成用 ES6 module 規範來寫,再執行打包。
// index.js
import test2 from './test2'
function test() {}
test()
test2()
// test2.js
export default function test2() {}
使用 ES6 module 規範打包後的程式碼和使用 CommonJS 規範打包後的程式碼絕大部分都是一樣的。
一樣的地方是指 webpack 自定義模組規範的程式碼一樣,唯一不同的是上面兩個檔案打包後的程式碼不同。
{
"./src/index.js":(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _test2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test2 */ \"./src/test2.js\");\n\r\n\r\nfunction test() {}\r\n\r\ntest()\r\nObject(_test2__WEBPACK_IMPORTED_MODULE_0__[\"default\"])()\n\n//# sourceURL=webpack:///./src/index.js?");
}),
"./src/test2.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return test2; });\nfunction test2() {}\n\n//# sourceURL=webpack:///./src/test2.js?");
})
}
可以看到傳入的第二個引數是 __webpack_exports__
,而 CommonJS 規範對應的第二個引數是 exports
。將這兩個模組程式碼的內容美化一下:
// index.js
__webpack_require__.r(__webpack_exports__);
var _test2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/test2.js");
function test() {}
test()
Object(_test2__WEBPACK_IMPORTED_MODULE_0__["default"])()
//# sourceURL=webpack:///./src/index.js?
// test2.js
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "default", function() { return test2; });
function test2() {}
//# sourceURL=webpack:///./src/test2.js?
可以發現,在每個模組的開頭都執行了一個 __webpack_require__.r(__webpack_exports__)
語句。並且 test2.js
還多了一個 __webpack_require__.d()
函式。
我們先來看看 __webpack_require__.r()
和 __webpack_require__.d()
是什麼。
webpack_require.d()
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
原來 __webpack_require__.d()
是給 __webpack_exports__
定義匯出變數用的。例如下面這行程式碼:
__webpack_require__.d(__webpack_exports__, "default", function() { return test2; });
它的作用相當於 __webpack_exports__["default"] = test2
。這個 "default"
是因為你使用 export default
來匯出函式,如果這樣匯出函式:
export function test2() {}
它就會變成 __webpack_require__.d(__webpack_exports__, "test2", function() { return test2; });
webpack_require.r()
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
__webpack_require__.r()
函式的作用是給 __webpack_exports__
新增一個 __esModule
為 true
的屬性,表示這是一個 ES6 module。
新增這個屬性有什麼用呢?
主要是為了處理混合使用 ES6 module 和 CommonJS 的情況。
例如匯出使用 CommonJS module.export = test2
匯出函式,匯入使用 ES6 module import test2 from './test2
。
打包後的程式碼如下:
// index.js
__webpack_require__.r(__webpack_exports__);
var _test2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/test2.js");
var _test2__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_test2__WEBPACK_IMPORTED_MODULE_0__);
function test() {}
test()
_test2__WEBPACK_IMPORTED_MODULE_0___default()()
//# sourceURL=webpack:///./src/index.js?
// test2.js
function test2() {}
module.exports = test2
//# sourceURL=webpack:///./src/test2.js?
從上述程式碼可以發現,又多了一個 __webpack_require__.n()
函式:
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
先來分析一下入口模組的處理邏輯:
- 將
__webpack_exports__
匯出物件標識為 ES6 module。 - 載入
test2.js
模組,並將該模組的匯出物件作為引數傳入__webpack_require__.n()
函式。 __webpack_require__.n
分析該export
物件是否是 ES6 module,如果是則返回module['default']
即export default
對應的變數。如果不是 ES6 module 則直接返回export
。
按需載入
按需載入,也叫非同步載入、動態匯入,即只在有需要的時候才去下載相應的資原始檔。
在 webpack 中可以使用 import
和 require.ensure
來引入需要動態匯入的程式碼,例如下面這個示例:
// index.js
function test() {}
test()
import('./test2')
// test2.js
export default function test2() {}
其中使用 import
匯入的 test2.js
檔案在打包時會被單獨打包成一個檔案,而不是和 index.js
一起打包到 bundle.js
。
這個 0.bundle.js
對應的程式碼就是動態匯入的 test2.js
的程式碼。
接下來看看這兩個打包檔案的內容:
// bundle.js
(function(modules) { // webpackBootstrap
// install a JSONP callback for chunk loading
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()();
}
};
// The module cache
var installedModules = {};
// object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
"main": 0
};
// script path function
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + "" + chunkId + ".bundle.js"
}
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// This file contains only the entry chunk.
// The chunk loading function for additional chunks
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
// expose the modules object (__webpack_modules__)
__webpack_require__.m = modules;
// expose the module cache
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
__webpack_require__.t = function(value, mode) {
if(mode & 1) value = __webpack_require__(value);
if(mode & 8) return value;
if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
return ns;
};
// getDefaultExport function for compatibility with non-harmony modules
__webpack_require__.n = function(module) {
var getter = module && module.__esModule ?
function getDefault() { return module['default']; } :
function getModuleExports() { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// __webpack_public_path__
__webpack_require__.p = "";
// on error function for async loading
__webpack_require__.oe = function(err) { console.error(err); throw err; };
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
"./src/index.js":(function(module, exports, __webpack_require__) {
eval("function test() {}\r\n\r\ntest()\r\n__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./test2 */ \"./src/test2.js\"))\n\n//# sourceURL=webpack:///./src/index.js?");
})
});
// 0.bundle.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push(
[
[0],
{
"./src/test2.js":(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return test2; });\nfunction test2() {}\n\n//# sourceURL=webpack:///./src/test2.js?");
})
}
]
);
這次打包的程式碼量有點膨脹,bundle.js
程式碼居然有 200 行。我們來看看相比於同步載入的 webpack 模組規範,它有哪些不同:
- 定義了一個物件
installedChunks
,作用是快取動態模組。 - 定義了一個輔助函式
jsonpScriptSrc()
,作用是根據模組 ID 生成 URL。 - 定義了兩個新的核心函式
__webpack_require__.e()
和webpackJsonpCallback()
。 - 定義了一個全域性變數
window["webpackJsonp"] = []
,它的作用是儲存需要動態匯入的模組。 - 重寫
window["webpackJsonp"]
陣列的push()
方法為webpackJsonpCallback()
。也就是說window["webpackJsonp"].push()
其實執行的是webpackJsonpCallback()
。
而從 0.bundle.js
檔案可以發現,它正是使用 window["webpackJsonp"].push()
來放入動態模組的。動態模組資料項有兩個值,第一個是 [0]
,它是模組的 ID;第二個值是模組的路徑名和模組內容。
然後我們再看一下打包後的入口模組的程式碼,經過美化後:
function test() {}
test()
__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
//# sourceURL=webpack:///./src/index.js?
原來模組程式碼中的 import('./test2')
被翻譯成了 __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
。
那 __webpack_require__.e()
的作用是什麼呢?
webpack_require.e()
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
它的處理邏輯如下:
- 先檢視該模組 ID 對應快取的值是否為 0,0 代表已經載入成功了,第一次取值為
undefined
。 - 如果不為 0 並且不是
undefined
代表已經是載入中的狀態。然後將這個載入中的 Promise 推入promises
陣列。 - 如果不為 0 並且是
undefined
就新建一個 Promise,用於載入需要動態匯入的模組。 - 生成一個
script
標籤,URL 使用jsonpScriptSrc(chunkId)
生成,即需要動態匯入模組的 URL。 - 為這個
script
標籤設定一個 2 分鐘的超時時間,並設定一個onScriptComplete()
函式,用於處理超時錯誤。 - 然後新增到頁面中
document.head.appendChild(script)
,開始載入模組。 - 返回
promises
陣列。
當 JS 檔案下載完成後,會自動執行檔案內容。也就是說下載完 0.bundle.js
後,會執行 window["webpackJsonp"].push()
。
由於 window["webpackJsonp"].push()
已被重置為 webpackJsonpCallback()
函式。所以這一操作就是執行 webpackJsonpCallback()
,接下來我們看看 webpackJsonpCallback()
做了哪些事情。
webpackJsonpCallback()
對這個模組 ID 對應的 Promise 執行 resolve()
,同時將快取物件中的值置為 0,表示已經載入完成了。相比於 __webpack_require__.e()
,這個函式還是挺好理解的。
小結
總的來說,動態匯入的邏輯如下:
- 重寫
window["webpackJsonp"].push()
方法。 - 入口模組使用
__webpack_require__.e()
下載動態資源。 - 資源下載完成後執行
window["webpackJsonp"].push()
,即webpackJsonpCallback()
。 - 將資源標識為 0,代表已經載入完成。由於載入模組使用的是 Promise,所以要執行
resolve()
。 - 再看一下入口模組的載入程式碼
__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
,下載完成後執行then()
方法,呼叫__webpack_require__()
真正開始載入程式碼,__webpack_require__()
在上文已經講解過,如果不瞭解,建議再閱讀一遍。