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__()
在上文已經講解過,如果不了解,建議再閱讀一遍。