深入了解 webpack 模块加载原理
2021-01-02 05:27
标签:test 异步加载 like exports 阅读 string 技术 javascrip 加载 webpack 是一个模块打包器,在它看来,每一个文件都是一个模块。 无论你开发使用的是 CommonJS 规范还是 ES6 模块规范,打包后的文件都统一使用 webpack 自定义的模块规范来管理、加载模块。本文将从一个简单的示例开始,来讲解 webpack 模块加载原理。 假设现在有如下两个文件: 以上两个文件使用 CommonJS 规范来导入导出文件,打包后的代码如下(已经删除了不必要的注释): 可以看到 webpack 实现的模块加载系统非常简单,仅仅只有一百行代码。 打包后的代码其实是一个立即执行函数,传入的参数是一个对象。这个对象以文件路径为 key,以文件内容为 value,它包含了所有打包后的模块。 将这个立即函数化简一下,相当于: 再看一下这个立即函数做了什么: 其中的核心就是 它的执行过程如下: 从上述代码可以看到,在执行模块函数时传入了三个参数,分别为 其中 在立即函数的最后,使用了 我们再来分析一下入口模块的内容。 入口模块函数的参数正好是刚才所说的那三个参数,而 eval 函数的内容美化一下后和下面内容一样: 将打包后的模块代码和原模块的代码进行对比,可以发现仅有一个地方发生了变化,那就是 再看一下 从刚才的分析可知, 到目前为止可以发现 webpack 自定义的模块规范完美适配 CommonJS 规范。 将刚才用 CommonJS 规范编写的两个文件换成用 ES6 module 规范来写,再执行打包。 使用 ES6 module 规范打包后的代码和使用 CommonJS 规范打包后的代码绝大部分都是一样的。 一样的地方是指 webpack 自定义模块规范的代码一样,唯一不同的是上面两个文件打包后的代码不同。 可以看到传入的第二个参数是 可以发现,在每个模块的开头都执行了一个 我们先来看看 原来 它的作用相当于 它就会变成 添加这个属性有什么用呢? 主要是为了处理混合使用 ES6 module 和 CommonJS 的情况。 例如导出使用 CommonJS 打包后的代码如下: 从上述代码可以发现,又多了一个 先来分析一下入口模块的处理逻辑: 按需加载,也叫异步加载、动态导入,即只在有需要的时候才去下载相应的资源文件。 在 webpack 中可以使用 其中使用 接下来看看这两个打包文件的内容: 这次打包的代码量有点膨胀, 而从 然后我们再看一下打包后的入口模块的代码,经过美化后: 原来模块代码中的 那 它的处理逻辑如下: 当 JS 文件下载完成后,会自动执行文件内容。也就是说下载完 由于 对这个模块 ID 对应的 Promise 执行 总的来说,动态导入的逻辑如下: 深入了解 webpack 模块加载原理 标签:test 异步加载 like exports 阅读 string 技术 javascrip 加载 原文地址:https://www.cnblogs.com/woai3c/p/13669933.htmlCommonJS 规范
// index.js
const test2 = require(‘./test2‘)
function test() {}
test()
test2()
// test2.js
function test2() {}
module.exports = test2
(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?");
})
});
{
"./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?");
})
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()
函数并执行。ES6 module
// index.js
import test2 from ‘./test2‘
function test() {}
test()
test2()
// test2.js
export default function test2() {}
{
"./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。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
。按需加载
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
// 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);
};
undefined
。undefined
代表已经是加载中的状态。然后将这个加载中的 Promise 推入 promises
数组。undefined
就新建一个 Promise,用于加载需要动态导入的模块。script
标签,URL 使用 jsonpScriptSrc(chunkId)
生成,即需要动态导入模块的 URL。script
标签设置一个 2 分钟的超时时间,并设置一个 onScriptComplete()
函数,用于处理超时错误。document.head.appendChild(script)
,开始加载模块。promises
数组。0.bundle.js
后,会执行 window["webpackJsonp"].push()
。window["webpackJsonp"].push()
已被重置为 webpackJsonpCallback()
函数。所以这一操作就是执行 webpackJsonpCallback()
,接下来我们看看 webpackJsonpCallback()
做了哪些事情。webpackJsonpCallback()
resolve()
,同时将缓存对象中的值置为 0,表示已经加载完成了。相比于 __webpack_require__.e()
,这个函数还是挺好理解的。小结
window["webpackJsonp"].push()
方法。__webpack_require__.e()
下载动态资源。window["webpackJsonp"].push()
,即 webpackJsonpCallback()
。resolve()
。__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
,下载完成后执行 then()
方法,调用 __webpack_require__()
真正开始加载代码,__webpack_require__()
在上文已经讲解过,如果不了解,建议再阅读一遍。更多文章,敬请关注
文章标题:深入了解 webpack 模块加载原理
文章链接:http://soscw.com/index.php/essay/39505.html