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


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

    commonjs

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

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

    // src/commonjs/json/add.js
    console.log("add开始引入");
    module.exports.add = (a, b) => {
        return a + b;
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    // src/commonjs/json/test1.json
    {
        "data": "test1"
    }
    
    // src/commonjs/json/test2.json
    {
        "data": "test2"
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    module.exports = function (filename) {
        const data = require("./json/" + filename + ".json");
        return data;
    }; 
    
    • 1
    • 2
    • 3
    • 4

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

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

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

    console.log("commonjs开始执行");
    const hello = require("./hello");
    console.log(hello("test1")); 
    
    • 1
    • 2
    • 3

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

    image-20220503173736921

    看一下打包产物:

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

    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" };
            },
        }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    主要是四个模块 ./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$ ,详见下边的注释

    "./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$";
            }, 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

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

    可以再看下整体的产物:

    (() => {
        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"));
        })();
    })(); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    esmodule

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

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

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

    首先是 hello.js

    // src/esmodule/hello.js
    const hello = (filename) => {
        return import("./json/" + filename + ".json");
    };
    export default hello; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后是 index.js

    // src/esmodule/index.js
    console.log("esmodule开始执行");
    import hello from "./hello";
    hello("test1").then((data) => {
        console.log(data);
    }); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    image-20220503191214724

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

    image-20220503214502914

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

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

    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__; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

     __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;
                }
            })(); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

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

    image-20220503214502914

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

    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; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

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

      "./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;
              }, 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    2. ./src/esmodule/json lazy recursive ^\\.\\/.*\\.json$ 方法导出的是 webpackAsyncContext 方法。

      "./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; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

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

      image-20220503214502914

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

      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);
        });
      } 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    3. 详细看一下 __webpack_require__.e 方法,传入了一个参数 chunkId ,这里就是 src_esmodule_json_test1_json

      __webpack_require__.e = (chunkId) => {
        return Promise.all(
          Object.keys(__webpack_require__.f).reduce((promises, key) => {
            __webpack_require__.f[key](chunkId, promises);
            return promises;
          }, [])
        );
      }; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

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

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

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

      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;
          }
        }
      }; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48

      上边的 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 方法。

      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
          }
        }
      }; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
    5. 看一下 l 方法。

      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
      }; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44

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

      image-20220504183052534

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

      image-20220504183143512

      看一下文件内容:

      "use strict";
      (self["webpackChunkwebpack_demo"] =
          self["webpackChunkwebpack_demo"] || []).push([
          ["src_esmodule_json_test1_json"],
          {
              "./src/esmodule/json/test1.json": (module) => {
                  module.exports = { data: "test1" };
              },
          },
      ]); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

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

      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)
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

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

      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;
        }
      }; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

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

      [
          ["src_esmodule_json_test1_json"],
          {
              "./src/esmodule/json/test1.json": (module) => {
                  module.exports = { data: "test1" };
              },
          },
      ] 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

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

      1. ./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__);
        } 
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8

        __webpack_require__.m 就是 __webpack_modules__ ,保存着所有模块的键值对。

      2. installedChunks 之前保存的 promise 执行 resolve

        for (; i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            if (
              __webpack_require__.o(installedChunks, chunkId) &&
              installedChunks[chunkId]
            ) {
              installedChunks[chunkId][0](); // 数组 0 保存的就是 resolve
            }
          } 
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
      3. installedChunks 相应的对象置为 0 ,代表加载完成了,前边讲的 loadingEnded 会判断这里是不是 0

        installedChunks[chunkIds[i]] = 0; 
        
        • 1
    7. 上边一大堆完成了 JSONP ,并且成功将动态加载的模块放到了 __webpack_modules__ 中,然后我们看一下执行到哪里了:

      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);
        });
      } 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

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

      __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;
      }; 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29

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

      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;
      } 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

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

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

      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);
        }
      ); 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    以上就是 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 函数继续执行,输出导入的数据。

    可以再看下完整代码:

    (() => {
        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);
                }
            );
        })();
    })(); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384

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

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

  • 相关阅读:
    tep集成mitmproxy录制流量自动生成用例
    uview+uniapp+springboot 实现小程序上传图片并回显
    自动驾驶感知算法实战14——感知算法模型生产线
    MVCC和BufferPool缓存机制
    Git常用的命令有哪些?
    经典的网站系统架构(入门级)
    功率放大器的三种类型是什么意思
    ESP8266-Arduino编程实例-ULN2003步进电机驱动
    【PX4&Simulink&Gazebo联合仿真】在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化
    基于Python的性能优化
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/125541260