webpack 模块加载原理

webpack

webpack 原理

1. webpack 模块加载原理

文件信息来源:webpack 深入理解模块加载原理

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

1.1 CommonJS 规范

打包后的代码其实是一个立即执行函数,传入的参数是一个对象。这个对象以文件路径为 key,以文件内容为 value,它包含了所有打包后的模块。

// 简化后的代码
(function(modules){
  /*
    1. 定义了一个模块缓存对象 `installedModules` ,作用是缓存已经加载过的模块。
    2. 定义了一个模块加载函数 `__webpack_require__()`。
    3. ... 省略一些其他代码。
    4. 使用 `__webpack_require__()` 加载入口模块。
  */
})({
  path1: function1,
  path2: function2
})

其中的核心就是 __webpack_require__() 函数,它接收的参数是 moduleId ,其实就是文件路径。

它的执行过程如下:

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

小结: __webpack_require__() 加载模块后,会先执行模块对应的函数,然后返回该模块的 exports 对象。而 文件的导出对象 module.exports 就是一个函数。所以入口模块能通过 __webpack_require__() 引入这个函数并执行。

1.2 ES6 module

使用 ES6 module 规范打包后的代码和使用 CommonJS 规范打包后的代码绝大部分都是一样的。

一样的地方是 webpack 自定义模块规范的代码一样,唯一不同的是上面两个文件打包后的代码不同,它们的入参不一样。

// ES6 module
{
  "./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?");
 }),
}
// CommonJS
  "./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?");
  }),
}

源码里有几个重要的函数需要理解

  1. 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; });
    
  2. webpack_require.r():作用是给 __webpack_exports__ 添加一个 __esModuletrue 的属性,表示这是一个 ES6 module

  3. __webpack_require__.n(): 分析该 export 对象是否是 ES6 module,如果是则返回 module['default']export default 对应的变量。如果不是 ES6 module 则直接返回 export

1.3 动态导入

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

在 webpack 中可以使用 import 来引入需要动态导入的代码

1.3.1 bundle.js

让我们来看看一个核心文件的执行顺序 bundle.js

  1. 定义了一个对象 installedChunks ,作用是缓存动态模块。
  2. 定义了一个辅助函数 jsonpScriptSrc() ,作用是根据模块 ID 生成 URL。
  3. 定义了两个新的核心函数 __webpack_require__.e()webpackJsonpCallback()
  4. 定义了一个全局变量 window["webpackJsonp"] = [],它的作用是存储需要动态导入的模块。
  5. 重写 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"))
//# sourceURL=webpack:///./src/index.js?

他的处理逻辑是

  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(7. script),开始加载模块。
  7. 返回 promises 数组。
1.3.4 小结

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

  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__() 真正开始加载代码。

2022-08-03 xieliuning

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值