webpack 是一个模块打包器,在它看来,每一个文件都是一个模块。

无论你开发应用的是 CommonJS 标准还是 ES6 模块标准,打包后的文件都对立应用 webpack 自定义的模块标准来治理、加载模块。本文将从一个简略的示例开始,来解说 webpack 模块加载原理。

CommonJS 标准

假如当初有如下两个文件:

// index.jsconst test2 = require('./test2')function test() {}test()test2()
// test2.jsfunction 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})

再看一下这个立刻函数做了什么:

  1. 定义了一个模块缓存对象 installedModules,作用是缓存曾经加载过的模块。
  2. 定义了一个模块加载函数 __webpack_require__()
  3. ... 省略一些其余代码。
  4. 应用 __webpack_require__() 加载入口模块。

其中的外围就是 __webpack_require__() 函数,它接管的参数是 moduleId,其实就是文件门路。

它的执行过程如下:

  1. 判断模块是否有缓存,如果有则返回缓存模块的 export 对象,即 module.exports
  2. 新建一个模块 module,并放入缓存。
  3. 执行文件门路对应的模块函数。
  4. 将这个新建的模块标识为已加载。
  5. 执行完模块后,返回该模块的 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;}

从上述代码能够看到,在执行模块函数时传入了三个参数,别离为 modulemodule.exports__webpack_require__

其中 modulemodule.exports 的作用和 CommonJS 中的 modulemodule.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.jsimport test2 from './test2'function test() {}test()test2()
// test2.jsexport 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__ 增加一个 __esModuletrue 的属性,示意这是一个 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;};

先来剖析一下入口模块的解决逻辑:

  1. __webpack_exports__ 导出对象标识为 ES6 module。
  2. 加载 test2.js 模块,并将该模块的导出对象作为参数传入 __webpack_require__.n() 函数。
  3. __webpack_require__.n 剖析该 export 对象是否是 ES6 module,如果是则返回 module['default']export default 对应的变量。如果不是 ES6 module 则间接返回 export

按需加载

按需加载,也叫异步加载、动静导入,即只在有须要的时候才去下载相应的资源文件。

在 webpack 中能够应用 importrequire.ensure 来引入须要动静导入的代码,例如上面这个示例:

// index.jsfunction test() {}test()import('./test2')
// test2.jsexport 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 模块标准,它有哪些不同:

  1. 定义了一个对象 installedChunks,作用是缓存动静模块。
  2. 定义了一个辅助函数 jsonpScriptSrc(),作用是依据模块 ID 生成 URL。
  3. 定义了两个新的外围函数 __webpack_require__.e()webpackJsonpCallback()
  4. 定义了一个全局变量 window["webpackJsonp"] = [],它的作用是存储须要动静导入的模块。
  5. 重写 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);};

它的解决逻辑如下:

  1. 先查看该模块 ID 对应缓存的值是否为 0,0 代表曾经加载胜利了,第一次取值为 undefined
  2. 如果不为 0 并且不是 undefined 代表曾经是加载中的状态。而后将这个加载中的 Promise 推入 promises 数组。
  3. 如果不为 0 并且是 undefined 就新建一个 Promise,用于加载须要动静导入的模块。
  4. 生成一个 script 标签,URL 应用 jsonpScriptSrc(chunkId) 生成,即须要动静导入模块的 URL。
  5. 为这个 script 标签设置一个 2 分钟的超时工夫,并设置一个 onScriptComplete() 函数,用于解决超时谬误。
  6. 而后增加到页面中 document.head.appendChild(script),开始加载模块。
  7. 返回 promises 数组。

当 JS 文件下载实现后,会主动执行文件内容。也就是说下载完 0.bundle.js 后,会执行 window["webpackJsonp"].push()

因为 window["webpackJsonp"].push() 已被重置为 webpackJsonpCallback() 函数。所以这一操作就是执行 webpackJsonpCallback() ,接下来咱们看看 webpackJsonpCallback() 做了哪些事件。

webpackJsonpCallback()

对这个模块 ID 对应的 Promise 执行 resolve(),同时将缓存对象中的值置为 0,示意曾经加载实现了。相比于 __webpack_require__.e(),这个函数还是挺好了解的。

小结

总的来说,动静导入的逻辑如下:

  1. 重写 window["webpackJsonp"].push() 办法。
  2. 入口模块应用 __webpack_require__.e() 下载动静资源。
  3. 资源下载实现后执行 window["webpackJsonp"].push(),即 webpackJsonpCallback()
  4. 将资源标识为 0,代表曾经加载实现。因为加载模块应用的是 Promise,所以要执行 resolve()
  5. 再看一下入口模块的加载代码 __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js")),下载实现后执行 then() 办法,调用 __webpack_require__() 真正开始加载代码,__webpack_require__() 在上文曾经解说过,如果不理解,倡议再浏览一遍。

更多文章,敬请关注