webpack
webpack 原理
1. webpack 模块加载原理
文件信息来源:webpack 深入理解模块加载原理
webpack 是一个模块打包器,在它看来,每一个文件都是一个模块。
1.1 CommonJS 规范
打包后的代码其实是一个立即执行函数,传入的参数是一个对象。这个对象以文件路径为 key,以文件内容为 value,它包含了所有打包后的模块。
(function(modules){
})({
path1: function1,
path2: function2
})
其中的核心就是 __webpack_require__()
函数,它接收的参数是 moduleId
,其实就是文件路径。
它的执行过程如下:
- 判断模块是否有缓存,如果有则返回缓存模块的
export
对象,即 module.exports
。 - 新建一个模块
module
,并放入缓存。 - 执行文件路径对应的模块函数。
- 将这个新建的模块标识为已加载。
- 执行完模块后,返回该模块的
exports
对象。
小结: __webpack_require__()
加载模块后,会先执行模块对应的函数,然后返回该模块的 exports
对象。而 文件的导出对象 module.exports
就是一个函数。所以入口模块能通过 __webpack_require__()
引入这个函数并执行。
1.2 ES6 module
使用 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/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?");
}),
}
源码里有几个重要的函数需要理解
-
webpack_require.d()
:给 __webpack_exports__
定义导出变量用的。它的作用相当于 __webpack_exports__["default"] = test2
。这个 “default
” 是因为你使用 export default
来导出函数,如果这样导出函数:
__webpack_require__.d(__webpack_exports__, "default", function() { return test2; });
__webpack_require__.d(__webpack_exports__, "test2", function() { return test2; });
-
webpack_require.r()
:作用是给 __webpack_exports__
添加一个 __esModule
为 true
的属性,表示这是一个 ES6 module
。
-
__webpack_require__.n()
: 分析该 export
对象是否是 ES6 module
,如果是则返回 module['default']
即 export default
对应的变量。如果不是 ES6 module
则直接返回 export
。
1.3 动态导入
按需加载,也叫异步加载、动态导入,即只在有需要的时候才去下载相应的资源文件。
在 webpack 中可以使用 import
来引入需要动态导入的代码
1.3.1 bundle.js
让我们来看看一个核心文件的执行顺序 bundle.js
- 定义了一个对象
installedChunks
,作用是缓存动态模块。 - 定义了一个辅助函数
jsonpScriptSrc()
,作用是根据模块 ID 生成 URL。 - 定义了两个新的核心函数
__webpack_require__.e()
和 webpackJsonpCallback()
。 - 定义了一个全局变量
window["webpackJsonp"] = []
,它的作用是存储需要动态导入的模块。 - 重写
window["webpackJsonp"]
数组的 push()
方法为 webpackJsonpCallback()
。也就是说 window["webpackJsonp"].push()
其实执行的是 webpackJsonpCallback()
1.3.2 0.bundle.js
0.bundle.js
文件可以发现,它正是使用 window["webpackJsonp"].push()
来放入动态模块的。动态模块数据项有两个值,第一个是它是模块的 ID ;第二个值是模块的路径名和模块内容。
1.3.3 webpack_require.e()
原来模块代码中的 import('./test2')
被翻译成了如下代码
function test() {}
test()
__webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/test2.js"))
他的处理逻辑是
- 先查看该模块 ID 对应缓存的值是否为 0,0 代表已经加载成功了,第一次取值为
undefined
。 - 如果不为 0 并且不是
undefined
代表已经是加载中的状态。然后将这个加载中的 Promise
推入 promises
数组。 - 如果不为 0 并且是
undefined
就新建一个 Promise
,用于加载需要动态导入的模块。 - 生成一个
script
标签,URL 使用 jsonpScriptSrc
(chunkId)` 生成,即需要动态导入模块的 URL。 - 为这个
script
标签设置一个 2 分钟的超时时间,并设置一个 onScriptComplete()
函数,用于处理超时错误。 - 然后添加到页面中
document.head.appendChild(7. script)
,开始加载模块。 - 返回 promises 数组。
1.3.4 小结
总的来说,动态导入的逻辑如下:
- 重写
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__()
真正开始加载代码。
2022-08-03 xieliuning
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)