前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Webpack 打包 commonjs 和 esmodule 动态引入模块的产物对比

Webpack 打包 commonjs 和 esmodule 动态引入模块的产物对比

作者头像
windliang
发布2022-09-23 13:17:44
7180
发布2022-09-23 13:17:44
举报
文章被收录于专栏:windliang的博客windliang的博客

Webpack 打包 commonjs 和 esmodule 模块的产物对比 我们来继续分析。这篇文章主要来看一下动态引入,允许我们引入的模块名包含变量。

⚠️超长代码预警,需要几个小时的时间去啃,但读懂以后应该会很开心。

commonjs

新建一个 json 文件夹,包含几个 json 文件,和一个 add 方法。

其中 add.js 就是一个简单的加法模块。

代码语言:javascript
复制
// src/commonjs/json/add.js
console.log("add开始引入");
module.exports.add = (a, b) => {
    return a + b;
};

test1.jsontest2.json 都是一个 json 对象。

代码语言:javascript
复制
// src/commonjs/json/test1.json
{
    "data": "test1"
}

// src/commonjs/json/test2.json
{
    "data": "test2"
}

然后我们提供一个 hello 模块,可以根据用户传入的参数,来引入不同的 json 文件返回给用户。

代码语言:javascript
复制
module.exports = function (filename) {
    const data = require("./json/" + filename + ".json");
    return data;
};

需要注意的上边 require 传入的模块名一定不能是一个纯变量,比如 require(filename) ,不然 webpack 就不知道该打包哪些文件了。

上边我们限定了目录位置 ./json 和文件名后缀 .json 。这样 Webpack 就会把 json 文件夹下所有的 .json 文件进行打包。

主函数 index.js 来调用 hello 方法。

代码语言:javascript
复制
console.log("commonjs开始执行");
const hello = require("./hello");
console.log(hello("test1"));

可以看一下控制台是正常输出:

image-20220503173736921

看一下打包产物:

主要看一下保存所有模块的 __webpack_modules__ 变量,其它的可以看一下上篇 Webpack 打包 commonjs 和 esmodule 模块的产物对比

代码语言:javascript
复制
var __webpack_modules__ = {
        "./src/commonjs/hello.js": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            module.exports = function (filename) {
                const data = __webpack_require__(
                    "./src/commonjs/json sync recursive ^\\.\\/.*\\.json$"
                )("./" + filename + ".json");
                return data;
            };
        },

        "./src/commonjs/json sync recursive ^\\.\\/.*\\.json$": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            ...
        },

        "./src/commonjs/json/test1.json": (module) => {
            "use strict";
            module.exports = { data: "test1" };
        },

        "./src/commonjs/json/test2.json": (module) => {
            "use strict";
            module.exports = { data: "test2" };
        },
    };

主要是四个模块 ./src/commonjs/hello.js./src/commonjs/json sync recursive ^\\.\\/.*\\.json$./src/commonjs/json/test1.json./src/commonjs/json/test2.json

./src/commonjs/json/test1.json./src/commonjs/json/test2.json 这两个模块就是把我们的 json 文件用 module.exports 来导出。

./src/commonjs/hello.js 模块中先调用 ./src/commonjs/json sync recursive ^\\.\\/.*\\.json$ 模块的方法,再进行传参。

此外将我们原本的 "./json/" + filename + ".json" 参数转为了 "./" + filename + ".json"

重点来看下 ./src/commonjs/json sync recursive ^\\.\\/.*\\.json$ ,详见下边的注释

代码语言:javascript
复制
"./src/commonjs/json sync recursive ^\\.\\/.*\\.json$": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
       // 映射 key
            var map = {
                "./test1.json": "./src/commonjs/json/test1.json",
                "./test2.json": "./src/commonjs/json/test2.json",
            };

            function webpackContext(req) {
                var id = webpackContextResolve(req); // 得到映射后的 key
                return __webpack_require__(id); // 通过 __webpack_require__ 导入文件
            }
       // 返回映射后的 key
            function webpackContextResolve(req) {
                if (!__webpack_require__.o(map, req)) {
                    var e = new Error("Cannot find module '" + req + "'");
                    e.code = "MODULE_NOT_FOUND";
                    throw e;
                }
                return map[req];
            }
            webpackContext.keys = function webpackContextKeys() {
                return Object.keys(map);
            };
            webpackContext.resolve = webpackContextResolve;
            module.exports = webpackContext;
            webpackContext.id =
                "./src/commonjs/json sync recursive ^\\.\\/.*\\.json$";
        },

commonjs 模块整体上就是把匹配 "./json/" + filename + ".json" 这个格式的文件 test1.jsontest2.json 都进行了打包,并且略过了 add.js 文件。

可以再看下整体的产物:

代码语言:javascript
复制
(() => {
    var __webpack_modules__ = {
        "./src/commonjs/hello.js": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            module.exports = function (filename) {
                const data = __webpack_require__(
                    "./src/commonjs/json sync recursive ^\\.\\/.*\\.json$"
                )("./" + filename + ".json");
                return data;
            };
        },

        "./src/commonjs/json sync recursive ^\\.\\/.*\\.json$": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            var map = {
                "./test1.json": "./src/commonjs/json/test1.json",
                "./test2.json": "./src/commonjs/json/test2.json",
            };

            function webpackContext(req) {
                var id = webpackContextResolve(req);
                return __webpack_require__(id);
            }
            function webpackContextResolve(req) {
                if (!__webpack_require__.o(map, req)) {
                    var e = new Error("Cannot find module '" + req + "'");
                    e.code = "MODULE_NOT_FOUND";
                    throw e;
                }
                return map[req];
            }
            webpackContext.keys = function webpackContextKeys() {
                return Object.keys(map);
            };
            webpackContext.resolve = webpackContextResolve;
            module.exports = webpackContext;
            webpackContext.id =
                "./src/commonjs/json sync recursive ^\\.\\/.*\\.json$";
        },

        "./src/commonjs/json/test1.json": (module) => {
            "use strict";
            module.exports = { data: "test1" };
        },

        "./src/commonjs/json/test2.json": (module) => {
            "use strict";
            module.exports = { data: "test2" };
        },
    };

    var __webpack_module_cache__ = {};

    function __webpack_require__(moduleId) {
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {
            return cachedModule.exports;
        }

        var module = (__webpack_module_cache__[moduleId] = {
            exports: {},
        });

        __webpack_modules__[moduleId](
            module,
            module.exports,
            __webpack_require__
        );

        return module.exports;
    }

    (() => {
        __webpack_require__.o = (obj, prop) =>
            Object.prototype.hasOwnProperty.call(obj, prop);
    })();

    var __webpack_exports__ = {};

    (() => {
        console.log("commonjs开始执行");
        const hello = __webpack_require__("./src/commonjs/hello.js");
        console.log(hello("test1"));
    })();
})();

esmodule

esmodule 提供了 import() 方法进行动态引入,会返回一个 Promise 对象。

★The ES2015 Loader spec defines import() as method to load ES2015 modules dynamically on runtime. ”

我们来用 esmodule 的形式改写下上边 commonjs 的代码。

首先是 hello.js

代码语言:javascript
复制
// src/esmodule/hello.js
const hello = (filename) => {
    return import("./json/" + filename + ".json");
};
export default hello;

然后是 index.js

代码语言:javascript
复制
// src/esmodule/index.js
console.log("esmodule开始执行");
import hello from "./hello";
hello("test1").then((data) => {
    console.log(data);
});

不同于 commonjs ,除了输出 test1.json 原本的数据,还多了一个 default 属性。

image-20220503191214724

打包文件中除了 main.js ,把两个 json 文件也单拎了出来,如下图:

image-20220503214502914

打包产物中除了 Webpack 打包 commonjs 和 esmodule 模块的产物对比 介绍的 d、o、r 方法,又多了很多奇奇怪怪的方法。

m 属性指向 __webpack_modules__,保存了导出的所有模块。

代码语言:javascript
复制
var __webpack_modules__ = {
        "./src/esmodule/hello.js": (
            __unused_webpack_module,
            __webpack_exports__,
            __webpack_require__
        ) => {
            "use strict";
            __webpack_require__.r(__webpack_exports__);
            __webpack_require__.d(__webpack_exports__, {
                default: () => __WEBPACK_DEFAULT_EXPORT__,
            });
            const hello = (filename) => {
                return __webpack_require__(
                    "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$"
                )("./" + filename + ".json");
            };
            const __WEBPACK_DEFAULT_EXPORT__ = hello;
        },

        "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            ...
        },
    };
__webpack_require__.m = __webpack_modules__;

g 属性指向全局对象,浏览器中的话就会返回 window

代码语言:javascript
复制
 __webpack_require__.g = (function () {
            if (typeof globalThis === "object") return globalThis; // 这句就直接返回
            try {
                return this || new Function("return this")();
            } catch (e) {
                if (typeof window === "object") return window;
            }
        })();

u 方法是将 chunkId 末尾加上 .main.js ,主要是为了和打包出来的文件名吻合。

代码语言:javascript
复制
__webpack_require__.u = (chunkId) => {
            return "" + chunkId + ".main.js";
        };

p 属性主要是为了拿到域名,开始执行的时候浏览器会加载我们的 main.js

image-20220503214502914

当前请求的地址是 http://127.0.0.1:5501/dist/main.js ,通过这个地址,我们要拿到 http://127.0.0.1:5501/dist/ ,详见下边的代码:

代码语言:javascript
复制
var scriptUrl;
        if (__webpack_require__.g.importScripts) // 这里不执行
            scriptUrl = __webpack_require__.g.location + "";
        var document = __webpack_require__.g.document; // 这里拿到 window.document
        if (!scriptUrl && document) {
            if (document.currentScript) scriptUrl = document.currentScript.src; // 这里得到 http://127.0.0.1:5501/dist/main.js
            if (!scriptUrl) { // 这里不执行
                var scripts = document.getElementsByTagName("script");
                if (scripts.length) scriptUrl = scripts[scripts.length - 1].src;
            }
        }

        if (!scriptUrl)
            throw new Error(
                "Automatic publicPath is not supported in this browser"
            );
        scriptUrl = scriptUrl
            .replace(/#.*$/, "")
            .replace(/\?.*$/, "")
            .replace(/\/[^\/]+$/, "/"); // 这里得到 http://127.0.0.1:5501/dist/
        __webpack_require__.p = scriptUrl;

接下来会比较复杂,会分成 8 个步骤来看一下 esmodule 异步加载的主流程。整体思路是通过 JSONP 的形式发送请求加载我们的 JSON 文件,同时把整个的加载过程会包装为一个 Promise ,加载完成将内容保存到 __webpack_modules__ 中。

hello 方法通过 __webpack_require__ 调用 "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$" 方法。

代码语言:javascript
复制
"./src/esmodule/hello.js": (
            __unused_webpack_module,
            __webpack_exports__,
            __webpack_require__
        ) => {
            "use strict";
            __webpack_require__.r(__webpack_exports__);
            __webpack_require__.d(__webpack_exports__, {
                default: () => __WEBPACK_DEFAULT_EXPORT__,
            });
            const hello = (filename) => {
                return __webpack_require__(
                    "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$"
                )("./" + filename + ".json");
            };
            const __WEBPACK_DEFAULT_EXPORT__ = hello;
        },

./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$ 方法导出的是 webpackAsyncContext 方法。

代码语言:javascript
复制
"./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            var map = {
                "./test1.json": [
                    "./src/esmodule/json/test1.json",
                    "src_esmodule_json_test1_json",
                ],
                "./test2.json": [
                    "./src/esmodule/json/test2.json",
                    "src_esmodule_json_test2_json",
                ],
            };
            function webpackAsyncContext(req) {
                if (!__webpack_require__.o(map, req)) {
                    return Promise.resolve().then(() => {
                        var e = new Error("Cannot find module '" + req + "'");
                        e.code = "MODULE_NOT_FOUND";
                        throw e;
                    });
                }
                debugger;
                var ids = map[req],
                    id = ids[0];
                return __webpack_require__.e(ids[1]).then(() => {
                    return __webpack_require__.t(id, 3 | 16);
                });
            }
            webpackAsyncContext.keys = () => Object.keys(map);
            webpackAsyncContext.id =
                "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$";
            module.exports = webpackAsyncContext;

map 中定义了 json 文件的映射,"./src/esmodule/json/test1.json" 是原本的文件位置,会作为模块的 key"src_esmodule_json_test1_json" 对应打包后的文件名。

image-20220503214502914

看一下 webpackAsyncContext 方法,先调用 __webpack_require__.e 方法来发送请求加载文件并且返回一个 Promise__webpack_require__.t 方法会将返回的数据加一个 default 属性,也就是开头说的一个不同之处。

代码语言:javascript
复制
function webpackAsyncContext(req) {
  if (!__webpack_require__.o(map, req)) {
    return Promise.resolve().then(() => {
      var e = new Error("Cannot find module '" + req + "'");
      e.code = "MODULE_NOT_FOUND";
      throw e;
    });
  }
  var ids = map[req], // ids[0] 是原本路径, id[1] 是打包后的文件名字
      id = ids[0];
  return __webpack_require__.e(ids[1]).then(() => {
    return __webpack_require__.t(id, 3 | 16);
  });
}

详细看一下 __webpack_require__.e 方法,传入了一个参数 chunkId ,这里就是 src_esmodule_json_test1_json

代码语言:javascript
复制
__webpack_require__.e = (chunkId) => {
  return Promise.all(
    Object.keys(__webpack_require__.f).reduce((promises, key) => {
      __webpack_require__.f[key](chunkId, promises);
      return promises;
    }, [])
  );
};

主要就是执行 f 对象的所有属性函数,f 的属性函数会在传入的 promises 中添加当前的 Promise

看一下 f 对象的属性函数的定义。

f 对象当前场景下只有一个 j 属性函数,所以在上边的 e 方法中会执行下边的 j 函数。

代码语言:javascript
复制
var installedChunks = { // 记录加载的文件
  main: 0,
};

__webpack_require__.f.j = (chunkId, promises) => {
  var installedChunkData = __webpack_require__.o( // o 方法是判断当前对象是否有该属性
    installedChunks,
    chunkId
  )
  ? installedChunks[chunkId]
  : undefined;
  if (installedChunkData !== 0) {
    if (installedChunkData) {
      promises.push(installedChunkData[2]);
    } else {
      if (true) {
        // 第一次加载文件会走到这里
        var promise = new Promise(
          (resolve, reject) =>
          (installedChunkData = installedChunks[chunkId] =
           [resolve, reject]) // 将 resolve 和 reject 保存
        );
        promises.push((installedChunkData[2] = promise)); // 把当前 promise 塞入到传入的 promises 数组

        var url =
            __webpack_require__.p +
            __webpack_require__.u(chunkId); // url 拼成了 http://127.0.0.1:5501/dist/src_esmodule_json_test1_json.main.js

        var error = new Error();
        var loadingEnded = (event) => {
          if (
            __webpack_require__.o(installedChunks, chunkId)
          ) {
              ...
            }
          }
        };
        
        __webpack_require__.l(
          url,
          loadingEnded,
          "chunk-" + chunkId,
          chunkId
        );
      } else installedChunks[chunkId] = 0;
    }
  }
};

上边的 j 函数执行完后,会在 installedChunks 对象中增加一个 src_esmodule_json_test1_jsonkey ,值是一个数组,数组的 0promiseresolve1promisereject2 是当前 promise ,如下图所示。

image-20220504181410631

最后执行 l 方法,就是我们的主角,通过 JSONP 的形式,塞一个 script 去加载 http://127.0.0.1:5501/dist/src_esmodule_json_test1_json.main.js 文件。

加载完成或者加载错误会执行上边的 loadingEnded 方法。

代码语言:javascript
复制
var error = new Error();
var loadingEnded = (event) => {
  if (
    __webpack_require__.o(installedChunks, chunkId)
  ) {
    installedChunkData = installedChunks[chunkId];
    if (installedChunkData !== 0)
      installedChunks[chunkId] = undefined;
    if (installedChunkData) { // 走到这里 installedChunkData 应该已经是 0 了(后边会讲到哪里置的 0),不然的话就抛出错误
      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;
      installedChunkData[1](error); // installedChunkData[1] 是之前保存的 reject
    }
  }
};

看一下 l 方法。

代码语言:javascript
复制
var inProgress = {};
var dataWebpackPrefix = "webpack-demo:";

__webpack_require__.l = (url, done, key, chunkId) => {
  if (inProgress[url]) {
    inProgress[url].push(done);
    return;
  }
  var script, needAttach;
  ...
  // 设置 script
  if (!script) {
    needAttach = true;
    script = document.createElement("script");

    script.charset = "utf-8";
    script.timeout = 120;
    if (__webpack_require__.nc) {
      script.setAttribute("nonce", __webpack_require__.nc);
    }
    script.setAttribute("data-webpack", dataWebpackPrefix + key);
    script.src = url;
  }
  inProgress[url] = [done];
  var onScriptComplete = (prev, event) => {
    script.onerror = script.onload = null;
    clearTimeout(timeout);
    var doneFns = inProgress[url];
    delete inProgress[url];
    script.parentNode && script.parentNode.removeChild(script);
    doneFns && doneFns.forEach((fn) => fn(event));
    if (prev) return prev(event);
  };
  var timeout = setTimeout(
    onScriptComplete.bind(null, undefined, {
      type: "timeout",
      target: script,
    }),
    120000
  );
  script.onerror = onScriptComplete.bind(null, script.onerror);
  script.onload = onScriptComplete.bind(null, script.onload);
  needAttach && document.head.appendChild(script); // 插入当前 script
};

主要就是 scrpit 加载完毕后的回调,然后将当前 script 插入到 head 标签中。

image-20220504183052534

接着浏览器就会发送请求加载我们之前打包后的 js 文件。

image-20220504183143512

看一下文件内容:

代码语言:javascript
复制
"use strict";
(self["webpackChunkwebpack_demo"] =
    self["webpackChunkwebpack_demo"] || []).push([
    ["src_esmodule_json_test1_json"],
    {
        "./src/esmodule/json/test1.json": (module) => {
            module.exports = { data: "test1" };
        },
    },
]);

加载完毕后会执行上边的代码,self["webpackChunkwebpack_demo"]push 方法之前已经重定义好了,也就是下边的代码。

代码语言:javascript
复制
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
  ...
};

var chunkLoadingGlobal = (self["webpackChunkwebpack_demo"] =
                          self["webpackChunkwebpack_demo"] || []);
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind( // 定义 push 方法
  null,
  chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
);

执行 self["webpackChunkwebpack_demo"] || []).push 相当于执行 webpackJsonpCallback 方法。

代码语言:javascript
复制
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
  var [chunkIds, moreModules, runtime] = data;

  var moduleId,
      chunkId,
      i = 0;
  if (chunkIds.some((id) => installedChunks[id] !== 0)) {
    for (moduleId in moreModules) {
      if (__webpack_require__.o(moreModules, moduleId)) {
        __webpack_require__.m[moduleId] = moreModules[moduleId];
      }
    }
    if (runtime) var result = runtime(__webpack_require__);
  }
  if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
  for (; i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    if (
      __webpack_require__.o(installedChunks, chunkId) &&
      installedChunks[chunkId]
    ) {
      installedChunks[chunkId][0]();
    }
    installedChunks[chunkIds[i]] = 0;
  }
};

传入的 data 参数就是加载的文件内容时候传入的,也就是下边的样子。

代码语言:javascript
复制
[
    ["src_esmodule_json_test1_json"],
    {
        "./src/esmodule/json/test1.json": (module) => {
            module.exports = { data: "test1" };
        },
    },
]

webpackJsonpCallback 拿到上边的 data 后主要做了三件事情:

  • ./src/esmodule/json/test1.json 模块保存到 __webpack_modules__ if (chunkIds.some((id) => installedChunks[id] !== 0)) { for (moduleId in moreModules) { if (__webpack_require__.o(moreModules, moduleId)) { __webpack_require__.m[moduleId] = moreModules[moduleId]; } } if (runtime) var result = runtime(__webpack_require__); } __webpack_require__.m 就是 __webpack_modules__ ,保存着所有模块的键值对。
  • installedChunks 之前保存的 promise 执行 resolvefor (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if ( __webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId] ) { installedChunks[chunkId][0](); // 数组 0 保存的就是 resolve } }
  • installedChunks 相应的对象置为 0 ,代表加载完成了,前边讲的 loadingEnded 会判断这里是不是 0installedChunks[chunkIds[i]] = 0;

上边一大堆完成了 JSONP ,并且成功将动态加载的模块放到了 __webpack_modules__ 中,然后我们看一下执行到哪里了:

代码语言:javascript
复制
function webpackAsyncContext(req) {
  if (!__webpack_require__.o(map, req)) {
    return Promise.resolve().then(() => {
      var e = new Error("Cannot find module '" + req + "'");
      e.code = "MODULE_NOT_FOUND";
      throw e;
    });
  }
  var ids = map[req], // ids[0] 是原本路径, id[1] 是打包后的文件名字
      id = ids[0];
  return __webpack_require__.e(ids[1]).then(() => {
    return __webpack_require__.t(id, 3 | 16);
  });
}

执行完 e 方法,接下执行 t 方法,会有很多不同的 mode 进入不同的分支,这里就不细究了,只需要知道最终结果是把数据加了 default 属性然后返回。

代码语言:javascript
复制
__webpack_require__.t = function (value, mode) {
  if (mode & 1) value = this(value);
  if (mode & 8) return value;
  if (typeof value === "object" && value) {
    if (mode & 4 && value.__esModule) return value;
    if (mode & 16 && typeof value.then === "function") return value;
  }
  var ns = Object.create(null);
  __webpack_require__.r(ns);
  var def = {};
  leafPrototypes = leafPrototypes || [
    null,
    getProto({}),
    getProto([]),
    getProto(getProto),
  ];
  for (
    var current = mode & 2 && value;
    typeof current == "object" && !~leafPrototypes.indexOf(current);
    current = getProto(current)
  ) {
    Object.getOwnPropertyNames(current).forEach(
      (key) => (def[key] = () => value[key])
    );
  }
  def["default"] = () => value;
  __webpack_require__.d(ns, def);
  return ns;
};

拿数据的话就是第一行代码,if (mode & 1) value = this(value); ,这里的 this 就是 webpack_require 函数,相当于执行 __webpack_require__('./src/esmodule/json/test1.json')。关于 this 指向可以参考 JavaScript中this指向详细分析(译)

代码语言:javascript
复制
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }

  var module = (__webpack_module_cache__[moduleId] = {
    exports: {},
  });

  __webpack_modules__[moduleId](
    module,
    module.exports,
    __webpack_require__
  );

  return module.exports;
}

'./src/esmodule/json/test1.json' 之前已经保存到了 __webpack_modules__ 中,所以就把之前加载的内容返回给了 value

上边讲了 hello 方法的执行,最后返回了一个包含数据的 promise ,最终回到了我们的 index 函数中。

代码语言:javascript
复制
var _hello__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
  "./src/esmodule/hello.js"
);
console.log("esmodule开始执行");
(0, _hello__WEBPACK_IMPORTED_MODULE_0__["default"])("test1").then(
  (data) => {
    console.log(data);
  }
);

以上就是 esmodule 异步加载模块的全过程了,稍微有些复杂,整体流程如下:

定义 JSOP 的回调函数((self["webpackChunkwebpack_demo"].push) ->

进入 index 函数 -> 进入 hello 函数 -> 进入 webpackAsyncContext 函数 ->

进入 __webpack_require__.e 函数 ->

执行 __webpack_require__.f.j 函数,保存 promise ,生成要下载的文件 url ->

进入 __webpack_require__.l 函数,运用 JSONP,动态插入 script ->

加载 script 文件,执行回调函数 (self["webpackChunkwebpack_demo"].push ,将数据保存到 __webpack_modules__ ->

执行 __webpack_require__.t 方法,将数据加上 default 返回 ->

hello 函数执行完毕 ->

回到 index 函数继续执行,输出导入的数据。

可以再看下完整代码:

代码语言:javascript
复制
(() => {
    var __webpack_modules__ = {
        "./src/esmodule/hello.js": (
            __unused_webpack_module,
            __webpack_exports__,
            __webpack_require__
        ) => {
            "use strict";
            __webpack_require__.r(__webpack_exports__);
            __webpack_require__.d(__webpack_exports__, {
                default: () => __WEBPACK_DEFAULT_EXPORT__,
            });
            const hello = (filename) => {
                return __webpack_require__(
                    "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$"
                )("./" + filename + ".json");
            };
            const __WEBPACK_DEFAULT_EXPORT__ = hello;
        },

        "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$": (
            module,
            __unused_webpack_exports,
            __webpack_require__
        ) => {
            var map = {
                "./test1.json": [
                    "./src/esmodule/json/test1.json",
                    "src_esmodule_json_test1_json",
                ],
                "./test2.json": [
                    "./src/esmodule/json/test2.json",
                    "src_esmodule_json_test2_json",
                ],
            };
            function webpackAsyncContext(req) {
                if (!__webpack_require__.o(map, req)) {
                    return Promise.resolve().then(() => {
                        var e = new Error("Cannot find module '" + req + "'");
                        e.code = "MODULE_NOT_FOUND";
                        throw e;
                    });
                }
                debugger;
                var ids = map[req],
                    id = ids[0];
                return __webpack_require__.e(ids[1]).then(() => {
                    return __webpack_require__.t(id, 3 | 16);
                });
            }
            webpackAsyncContext.keys = () => Object.keys(map);
            webpackAsyncContext.id =
                "./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$";
            module.exports = webpackAsyncContext;
        },
    };

    var __webpack_module_cache__ = {};

    function __webpack_require__(moduleId) {
        var cachedModule = __webpack_module_cache__[moduleId];
        if (cachedModule !== undefined) {
            return cachedModule.exports;
        }

        var module = (__webpack_module_cache__[moduleId] = {
            exports: {},
        });

        __webpack_modules__[moduleId](
            module,
            module.exports,
            __webpack_require__
        );

        return module.exports;
    }

    __webpack_require__.m = __webpack_modules__;

    (() => {
        var getProto = Object.getPrototypeOf
            ? (obj) => Object.getPrototypeOf(obj)
            : (obj) => obj.__proto__;
        var leafPrototypes;

        __webpack_require__.t = function (value, mode) {
            if (mode & 1) value = this(value);
            if (mode & 8) return value;
            if (typeof value === "object" && value) {
                if (mode & 4 && value.__esModule) return value;
                if (mode & 16 && typeof value.then === "function") return value;
            }
            var ns = Object.create(null);
            __webpack_require__.r(ns);
            var def = {};
            leafPrototypes = leafPrototypes || [
                null,
                getProto({}),
                getProto([]),
                getProto(getProto),
            ];
            for (
                var current = mode & 2 && value;
                typeof current == "object" && !~leafPrototypes.indexOf(current);
                current = getProto(current)
            ) {
                Object.getOwnPropertyNames(current).forEach(
                    (key) => (def[key] = () => value[key])
                );
            }
            def["default"] = () => value;
            __webpack_require__.d(ns, def);
            return ns;
        };
    })();

    (() => {
        __webpack_require__.d = (exports, definition) => {
            for (var key in definition) {
                if (
                    __webpack_require__.o(definition, key) &&
                    !__webpack_require__.o(exports, key)
                ) {
                    Object.defineProperty(exports, key, {
                        enumerable: true,
                        get: definition[key],
                    });
                }
            }
        };
    })();

    (() => {
        __webpack_require__.f = {};

        __webpack_require__.e = (chunkId) => {
            return Promise.all(
                Object.keys(__webpack_require__.f).reduce((promises, key) => {
                    __webpack_require__.f[key](chunkId, promises);
                    return promises;
                }, [])
            );
        };
    })();

    (() => {
        __webpack_require__.u = (chunkId) => {
            return "" + chunkId + ".main.js";
        };
    })();

    (() => {
        __webpack_require__.g = (function () {
            if (typeof globalThis === "object") return globalThis;
            try {
                return this || new Function("return this")();
            } catch (e) {
                if (typeof window === "object") return window;
            }
        })();
    })();

    (() => {
        __webpack_require__.o = (obj, prop) =>
            Object.prototype.hasOwnProperty.call(obj, prop);
    })();

    (() => {
        var inProgress = {};
        var dataWebpackPrefix = "webpack-demo:";

        __webpack_require__.l = (url, done, key, chunkId) => {
            if (inProgress[url]) {
                inProgress[url].push(done);
                return;
            }
            var script, needAttach;
            if (key !== undefined) {
                var scripts = document.getElementsByTagName("script");
                for (var i = 0; i < scripts.length; i++) {
                    var s = scripts[i];
                    if (
                        s.getAttribute("src") == url ||
                        s.getAttribute("data-webpack") ==
                            dataWebpackPrefix + key
                    ) {
                        script = s;
                        break;
                    }
                }
            }
            if (!script) {
                needAttach = true;
                script = document.createElement("script");

                script.charset = "utf-8";
                script.timeout = 120;
                if (__webpack_require__.nc) {
                    script.setAttribute("nonce", __webpack_require__.nc);
                }
                script.setAttribute("data-webpack", dataWebpackPrefix + key);
                script.src = url;
            }
            inProgress[url] = [done];
            var onScriptComplete = (prev, event) => {
                script.onerror = script.onload = null;
                clearTimeout(timeout);
                var doneFns = inProgress[url];
                delete inProgress[url];
                script.parentNode && script.parentNode.removeChild(script);
                doneFns && doneFns.forEach((fn) => fn(event));
                if (prev) return prev(event);
            };
            var timeout = setTimeout(
                onScriptComplete.bind(null, undefined, {
                    type: "timeout",
                    target: script,
                }),
                120000
            );
            script.onerror = onScriptComplete.bind(null, script.onerror);
            script.onload = onScriptComplete.bind(null, script.onload);
            needAttach && document.head.appendChild(script);
        };
    })();

    (() => {
        __webpack_require__.r = (exports) => {
            if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
                Object.defineProperty(exports, Symbol.toStringTag, {
                    value: "Module",
                });
            }
            Object.defineProperty(exports, "__esModule", { value: true });
        };
    })();

    (() => {
        var scriptUrl;
        if (__webpack_require__.g.importScripts)
            scriptUrl = __webpack_require__.g.location + "";
        var document = __webpack_require__.g.document;
        if (!scriptUrl && document) {
            if (document.currentScript) scriptUrl = document.currentScript.src;
            if (!scriptUrl) {
                var scripts = document.getElementsByTagName("script");
                if (scripts.length) scriptUrl = scripts[scripts.length - 1].src;
            }
        }

        if (!scriptUrl)
            throw new Error(
                "Automatic publicPath is not supported in this browser"
            );
        scriptUrl = scriptUrl
            .replace(/#.*$/, "")
            .replace(/\?.*$/, "")
            .replace(/\/[^\/]+$/, "/");
        __webpack_require__.p = scriptUrl;
    })();

    (() => {
        var installedChunks = {
            main: 0,
        };

        __webpack_require__.f.j = (chunkId, promises) => {
            var installedChunkData = __webpack_require__.o(
                installedChunks,
                chunkId
            )
                ? installedChunks[chunkId]
                : undefined;
            if (installedChunkData !== 0) {
                if (installedChunkData) {
                    promises.push(installedChunkData[2]);
                } else {
                    if (true) {
                        var promise = new Promise(
                            (resolve, reject) =>
                                (installedChunkData = installedChunks[chunkId] =
                                    [resolve, reject])
                        );
                        promises.push((installedChunkData[2] = promise));

                        var url =
                            __webpack_require__.p +
                            __webpack_require__.u(chunkId);

                        var error = new Error();
                        var loadingEnded = (event) => {
                            if (
                                __webpack_require__.o(installedChunks, chunkId)
                            ) {
                                installedChunkData = installedChunks[chunkId];
                                if (installedChunkData !== 0)
                                    installedChunks[chunkId] = undefined;
                                if (installedChunkData) {
                                    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;
                                    installedChunkData[1](error);
                                }
                            }
                        };
                        __webpack_require__.l(
                            url,
                            loadingEnded,
                            "chunk-" + chunkId,
                            chunkId
                        );
                    } else installedChunks[chunkId] = 0;
                }
            }
        };

        var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
            var [chunkIds, moreModules, runtime] = data;

            var moduleId,
                chunkId,
                i = 0;
            if (chunkIds.some((id) => installedChunks[id] !== 0)) {
                for (moduleId in moreModules) {
                    if (__webpack_require__.o(moreModules, moduleId)) {
                        __webpack_require__.m[moduleId] = moreModules[moduleId];
                    }
                }
                if (runtime) var result = runtime(__webpack_require__);
            }
            if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
            for (; i < chunkIds.length; i++) {
                chunkId = chunkIds[i];
                if (
                    __webpack_require__.o(installedChunks, chunkId) &&
                    installedChunks[chunkId]
                ) {
                    installedChunks[chunkId][0]();
                }
                installedChunks[chunkIds[i]] = 0;
            }
        };

        var chunkLoadingGlobal = (self["webpackChunkwebpack_demo"] =
            self["webpackChunkwebpack_demo"] || []);
        chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
        chunkLoadingGlobal.push = webpackJsonpCallback.bind(
            null,
            chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
        );
    })();

    var __webpack_exports__ = {};

    (() => {
        "use strict";

        __webpack_require__.r(__webpack_exports__);
        var _hello__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
            "./src/esmodule/hello.js"
        );
        console.log("esmodule开始执行");

        (0, _hello__WEBPACK_IMPORTED_MODULE_0__["default"])("test1").then(
            (data) => {
                console.log(data);
            }
        );
    })();
})();

require 引入模块是同步的,因此打包的时候就将数据保存起来了,打包产物也比较简单。

import() 是异步的,需要异步加载的文件提前单独生成文件,然后通过 JSONP 的形式进行加载,加载完毕后通过回调将数据添加到 __webpack_modules__ 对象中,方便后续使用。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 windliang 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • commonjs
  • esmodule
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档