webpack 模块加载原理

  • Post author:
  • Post category:其他




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__

    添加一个

    __esModule



    true

    的属性,表示这是一个

    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



版权声明:本文为weixin_51175605原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。