带你一步步分析webpack是如何执行打包产物的

引入关系如图所示:
在这里插入图片描述
圈出来文件d是异步导入的文件。
wepback版本如图所示:
在这里插入图片描述
执行打包命令,产物如下图:
在这里插入图片描述
会生成两个js文件,一个是入口文件打包的testxx.js,还有一个是异步文件d生成的src_d_js.js。
打包后的内容如下所示,先贴代码,后面再分析:


(() => { // webpackBootstrap
  "use strict";
  var __webpack_modules__ = ({

    "./src/c.js":
      ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

        eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"a\": () => (/* binding */ a),\n/* harmony export */   \"b\": () => (/* binding */ b)\n/* harmony export */ });\nconst a = function () {\n  console.log(1);\n}\n\nconst b = function () {\n  console.log(2);\n}\n\na()\nb()\n\n__webpack_require__.e(/*! import() */ \"src_d_js\").then(__webpack_require__.bind(__webpack_require__, /*! ./d */ \"./src/d.js\")).then(res => {\n  console.log(res);\n})\n\n\n//# sourceURL=webpack://test-webpack/./src/c.js?");

      }),

    "./src/index.js":
      ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

        eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _c__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./c */ \"./src/c.js\");\n\r\n(0,_c__WEBPACK_IMPORTED_MODULE_0__.a)()\n\n//# sourceURL=webpack://test-webpack/./src/index.js?");

      })

  });
  /************************************************************************/
  // The module cache
  var __webpack_module_cache__ = {};

  // The require function
  function __webpack_require__(moduleId) {
    // Check if module is in cache
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    // Create a new module (and put it into the cache)
    var module = __webpack_module_cache__[moduleId] = {
      // no module.id needed
      // no module.loaded needed
      exports: {}
    };

    // Execute the module function
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    // Return the exports of the module
    return module.exports;
  }

  // expose the modules object (__webpack_modules__)
  __webpack_require__.m = __webpack_modules__;

  /************************************************************************/
  /* webpack/runtime/define property getters */
  (() => {
    // define getter functions for harmony exports
    __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/runtime/ensure chunk */
  (() => {
    __webpack_require__.f = {};
    // This file contains only the entry chunk.
    // The chunk loading function for additional chunks
    __webpack_require__.e = (chunkId) => {
      return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
        __webpack_require__.f[key](chunkId, promises);
        return promises;
      }, []));
    };
  })();

  /* webpack/runtime/get javascript chunk filename */
  (() => {
    // This function allow to reference async chunks
    __webpack_require__.u = (chunkId) => {
      // return url for filenames based on template
      return "" + chunkId + ".js";
    };
  })();

  /* webpack/runtime/global */
  (() => {
    __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/runtime/hasOwnProperty shorthand */
  (() => {
    __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  })();

  /* webpack/runtime/load script */
  (() => {
    var inProgress = {};
    var dataWebpackPrefix = "test-webpack:";
    // loadScript function to load a script via script tag
    __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) => {
        // avoid mem leaks in IE.
        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/runtime/make namespace object */
  (() => {
    // define __esModule on exports
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();

  /* webpack/runtime/publicPath */
  (() => {
    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
      }
    }
    // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
    // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
    if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
    scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
    __webpack_require__.p = scriptUrl;
  })();

  /* webpack/runtime/jsonp chunk loading */
  (() => {
    // no baseURI

    // object to store loaded and loading chunks
    // undefined = chunk not loaded, null = chunk preloaded/prefetched
    // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
    var installedChunks = {
      "testxx": 0
    };

    __webpack_require__.f.j = (chunkId, promises) => {
      // JSONP chunk loading for javascript
      var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
      if (installedChunkData !== 0) { // 0 means "already installed".

        // a Promise means "currently loading".
        if (installedChunkData) {
          promises.push(installedChunkData[2]);
        } else {
          if (true) { // all chunks have JS
            // setup Promise in chunk cache
            var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
            promises.push(installedChunkData[2] = promise);

            // start chunk loading
            var url = __webpack_require__.p + __webpack_require__.u(chunkId);
            // create error before stack unwound to get useful stacktrace later
            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;
        }
      }
    };

    // install a JSONP callback for chunk loading
    var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
      var [chunkIds, moreModules, runtime] = data;
      // add "moreModules" to the modules object,
      // then flag all "chunkIds" as loaded and fire callback
      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[chunkId] = 0;
      }

    }

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

  // startup
  // Load entry module and return exports
  // This entry module can't be inlined because the eval devtool is used.
  var __webpack_exports__ = __webpack_require__("./src/index.js");

})()
  ;

1.加载入口文件
在这里插入图片描述
在这里插入图片描述
index.js文件会引入c.js文件并执行a方法
在这里插入图片描述
会先执行
在这里插入图片描述
先执行__webpack_require__.r方法,定义了一些属性:
在这里插入图片描述
然后执行__webpack_require__导入c.js:
在这里插入图片描述
然后执行下面的方法:

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

继续执行下面c.js里面的方法:

 ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        __webpack_require__.r(__webpack_exports__);
         __webpack_require__.d(__webpack_exports__, { "a": () => (a), "b": () => (b)});
         const a = function () {
         	console.log(1);
         }
         const b = function () {
         	console.log(2);
         }
         a()
         b()
         __webpack_require__.e("src_d_js").then(()=>{
				return __webpack_require__.bind(__webpack_require__,"./src/d.js")
		}).then(res => { console.log(res);})
      })

该函数会先执行.d方法,该方法已定义,但是是没添加到exports上,则会添加到exports:
在这里插入图片描述
然后执行:

__webpack_require__.e(/*! import() */ "src_d_js")

在这里插入图片描述
在这个方法中主要是执行__webpack_require.f.j方法:

__webpack_require__.f.j = (chunkId, promises) => {
      // 判断是否加载过
      var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
      if (installedChunkData !== 0) { // 0 means "already installed".

        // a Promise means "currently loading".
        if (installedChunkData) {
          promises.push(installedChunkData[2]);
        } else {
          if (true) { // all chunks have JS
            // setup Promise in chunk cache
            var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
            promises.push(installedChunkData[2] = promise);

            // start chunk loading
            var url = __webpack_require__.p + __webpack_require__.u(chunkId);
            // create error before stack unwound to get useful stacktrace later
            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;
        }
      }
    };

webpack_require.f.j方法主要是将设置一个promise,并将resolve,reject和本身放入installedChunkData,promises一开始是空列表,然后放入promise。随后调用__webpack_require__.l方法,并将要加载的文件url、加载完成事件等作为参数传入:

__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);

接下来调用__webpack_require__.l方法:
在这里插入图片描述
该方法会根据url判断是否已经加载该script,如果没有就会去加载:
在这里插入图片描述
超时时间原本是120ms,本人调试就延迟了时间,这里会创建script并设置url为要加载的文件。
在这里插入图片描述
然后会监听该script的onload事件,当加载完毕会调用onScriptComplete函数。在调用该函数加载完毕之前会继续执行下图代码返回promises,该promises就是一开始installedChunks列表里放的[resolve,reject]。
在这里插入图片描述那么这个resolve什么时候执行呢?我们接着往下看,此时该文件已加载:
在这里插入图片描述

在这里插入图片描述
加载完成后就会执行onload函数的回调:onScriptComplete函数。在这里插入图片描述
如果已经超时的话就好执行setTimeout函数,相当于加载失败直接回调了:

在这里插入图片描述
此时event就是:
在这里插入图片描述
如果没有超时则会加载script的内容,此时内容如下:
在这里插入图片描述
就是d.js模块的内容:
在这里插入图片描述

"use strict";
(self["webpackChunktest_webpack"] = self["webpackChunktest_webpack"] || []).push([["src_d_js"], {

  "./src/d.js":
    ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"a\": () => (/* binding */ a),\n/* harmony export */   \"b\": () => (/* binding */ b)\n/* harmony export */ });\nconst a = function () {\n  console.log(1);\n}\n\nconst b = function () {\n  console.log(2);\n}\n\na()\nb()\n\n//# sourceURL=webpack://test-webpack/./src/d.js?");

      /***/
    })

}]);

该模块可以看出会先执行webpackChunktest_webpack方法,那么该方法又从哪里来的呢?其实在一开始就定义了该函数:
在这里插入图片描述
并且设置覆盖了push方法为webpackJsonpCallback。该函数的作用是在modules记录该模块,并且执行resolve回调。如果该模块id在installedChunks里面(执行过__webpack_require__.f.j方法就会在该数组里面,相当于import),此时就会执行第一个函数,该函数就是promise的resolve。
在这里插入图片描述
此时会执行zhen方法,于是执行__webpack_require方法:
在这里插入图片描述
于是又到了我们熟悉的内容,上面已经分析过了,此时不再赘述。
在这里插入图片描述
执行完上面的模块,就会执行第二个then方法:
在这里插入图片描述
然后还有最后一个onload回调,由于script是宏任务,所以会执行完所有的微任务,再执行onload回调。
在这里插入图片描述
onload回调会移除该script和超时定时器。然后执行fn(event),fn是loadingEnded,event是script.onload或script.onerror。正常执行完后会在webpackJsonpCallback里将installedChunks[chunkId] = 0,而installedChunkData会获取installedChunks[chunkId]的值。如果installedChunkData不是0说明没有执行webpackJsonpCallback,而webpackJsonpCallback的执行条件是已经script下载了内容才会执行。
在这里插入图片描述
全篇至此结束,还有些细节可能还没分析,有时间会再出文章解释说明,请各位读者持续关注。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Young soul2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值