IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> webpack 异步import生成代码解析 -> 正文阅读

[JavaScript知识库]webpack 异步import生成代码解析

原文件内容

// index.js原文件
import("./a").then(console.log);

// a.js
export default "async a";

文件目录

  • 打包前

在这里插入图片描述

  • 打包后

在这里插入图片描述

入口文件生成代码

  // webpackBootstrap
  var __webpack_modules__ = {
    "./src/index.js": (
      __unused_webpack_module,
      __unused_webpack_exports,
      __webpack_require__
    ) => {
      eval(
        '__webpack_require__.e(/*! import() */ "src_a_js").then(__webpack_require__.bind(__webpack_require__, /*! ./a */ "./src/a.js")).then(console.log);\n\n\n//# sourceURL=webpack://webpack5_demo/./src/index.js?'
      );

      /***/
    },
  };

var __webpack_exports__ = __webpack_require__("./src/index.js");
  • __webpack_modules__是文件id(moduleId)和引入文件内容的方法的映射对象,通过__webpack_require__根据chunkId实现内容的导入
  • 可以看到异步文件是通过__webpack_require__.e引入的
    • “src_a_js"是打包后的异步文件名称(chunkId),”./src/a.js"是异步文件未打包前的名称,作为异步文件的id(moduleId)
  • 当异步chunk下载完成后,通过__webpack_require__引入

生成的一些辅助方法

  • webpack_require.m

// 保存__webpack_modules__的引用
__webpack_require__.m = __webpack_modules__;
  • webpack_require.d

    • 通过Object.defineProperty定义对象属性
  /* 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_require.o

  /* webpack/runtime/hasOwnProperty shorthand */
  // 判断对象是否有某个属性
  (() => {
    __webpack_require__.o = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
  })();
  • webpack_require.u

 /* webpack/runtime/get javascript chunk filename */
  //指向打包后的异步chunk的文件名,用于之后请求异步文件路径拼接
  (() => {
    // This function allow to reference async chunks
    __webpack_require__.u = (chunkId) => {
      // return url for filenames based on template
      // chunkId:src_a_js
      return "" + chunkId + ".bundle.js";
    };
  })();
  • webpack_require.g

  /* webpack/runtime/global */
  // 返回对应环境的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_require.r

  (() => {
    // define __esModule on exports
    // 标识是esm导入
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
          value: "Module",
        });
      }
      Object.defineProperty(exports, "__esModule", { value: true });
    };
  })();

导入文件通用方法

  • webpack_require

// The require function
function __webpack_require__(moduleId) {
  // Check if module is in cache
  // 首先从缓存中,根据moduleId获取("./src/index.js"、./src/index.js)
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  // Create a new module (and put it into the cache)
  // 缓存中没有则创建,exports对象会在调用moduleId对应的方法内部进行赋值
  var module = (__webpack_module_cache__[moduleId] = {
    // no module.id needed
    // no module.loaded needed
    exports: {},
  });

  // Execute the module function
  // 调用moduleId对应的方法引入文件内容
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

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

异步文件引入

  • 获取下载文件的公共地址__webpack_require__.p

    • 通过获取已有script标签的src属性,来获取下载地址
/* webpack/runtime/publicPath */
// 获取入口文件js脚本的公共路径,本例为' http://127.0.0.1:5500/dist/'
(() => {
  var scriptUrl;
  // importScripts用于在一个js里面包含其他的js文件。相当于C里面的#include。
  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; //'http://127.0.0.1:5500/dist/bundle.js'
    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(/\/[^\/]+$/, "/");

  //替换后结果为: http://127.0.0.1:5500/dist/
  __webpack_require__.p = scriptUrl;
})();
  • 异步导入__webpack_require__.e

    • 通过Promise.all进行阻塞,当文件下载完毕后再通过__webpack_require__导入
/* webpack/runtime/ensure chunk */
(() => {	
  //保存具体导入方法的对象,本案例保存__webpack_require__.f.j方法
  __webpack_require__.f = {};
  // This file contains only the entry chunk.
  // The chunk loading function for additional chunks
  // chunkId:src_a_js
  __webpack_require__.e = (chunkId) => {
    let res = Promise.all(
      Object.keys(__webpack_require__.f).reduce((promises, key) => {
        //这里实际上调用了:__webpack_require__.f.j(chunkId, promises)
        // promises是一个数组,用来保存每个下载的chunk的下载状态,具体chunk状态查看__webpack_require__.f.j
        __webpack_require__.f[key](chunkId, promises);
        return promises;
      }, [])
    );
    return res;
  };
})();
  • 包装异步文件__webpack_require__.f.j

    • 将初次未下载的异步chunk包裹成promise,再通过__webpack_require__.l发出实际请求
/* webpack/runtime/jsonp chunk loading */
// 异步文件的引入,__webpack_require__.e实际调用的是__webpack_require__.f.j,__webpack_require__.f.j实际调用的是__webpack_require__.l
(() => {
 // 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 = {
   main: 0,
 };

//首次传入的promises
 __webpack_require__.f.j = (chunkId, promises) => {
   // JSONP chunk loading for javascript
   // 根据chunkId从缓存中获取文件下载状态,首次为undefined
   var installedChunkData = __webpack_require__.o(installedChunks, chunkId)
     ? installedChunks[chunkId]
     : undefined;
     
   if (installedChunkData !== 0) {
     // 0 means "already installed".

     // a Promise means "currently loading".
     // 正在加载中,继续将文件对应的promise状态放入promises数组中
     if (installedChunkData) { //只有[resolve, reject, Promise]才能进入该分支
       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)); //注意这里的数据结构:[[resolve,reject,promise]]

         // start chunk loading
         // 异步chunk的路径: http://127.0.0.1:5500/dist/ + src_a_js.bundle.js
         var url = __webpack_require__.p + __webpack_require__.u(chunkId);
         // create error before stack unwound to get useful stacktrace later
         var error = new Error();
         //下载完成回调,主要处理下载失败的情况,作为script的onload回调
         var loadingEnded = (event) => {
           if (__webpack_require__.o(installedChunks, chunkId)) {
             installedChunkData = installedChunks[chunkId];
             // 本案例为0,表示已加载完毕,下面分支为处理加载失败的异常情况
             if (installedChunkData !== 0)
               installedChunks[chunkId] = undefined;
             if (installedChunkData) { //[resolve,reject,promise]会进入该分支
               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); //调用reject失败回调
             }
           }
         };
		 // 实际发出下载的请求方法
         __webpack_require__.l(
           url,
           loadingEnded,
           "chunk-" + chunkId,
           chunkId
         );
       } else installedChunks[chunkId] = 0;
     }
   }
 };

 // no prefetching

 // no preloaded

 // no HMR

 // no HMR manifest

 // no on chunks loaded

 // install a JSONP callback for chunk loading
 var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
	// ...
 };
 
// ...

})();
  • 下载文件请求方法__webpack_require__.l

    • 通过动态创建script标签去下载异步脚本
// 通过 script 实现异步 chunk 内容的加载与执行,实际在__webpack_require__.f.j中调用
/* webpack/runtime/load script */
(() => {
  var inProgress = {};
  var dataWebpackPrefix = "webpack5_demo:";
  // loadScript function to load a script via script tag
  // 通过 script 实现异步 chunk 内容的加载与执行。

  // url: 异步chunk的请求url,本案例是:http://127.0.0.1:5500/dist/src_a_js.bundle.js'
  // key: 异步模块打包后未加bundle.js的名称,本案例是'src_a_js'
  // done: __webpack_require__.f.j中的loadingEnded
  __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;
    }
    // 将文件对应的下载完成回调保存起来,及loadingEnded
    inProgress[url] = [done];
    var onScriptComplete = (prev, event) => {
      console.log("script end");
      // avoid mem leaks in IE.
      script.onerror = script.onload = null;
      clearTimeout(timeout);
      // 去除文件对应的完成回调
      var doneFns = inProgress[url];
      delete inProgress[url];
      // 异步文件下载完成后删除script标签
      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);
    //onload:js文件被加载完成并执行完成后才会触发
    script.onload = onScriptComplete.bind(null, script.onload);
    needAttach && document.head.appendChild(script);
  };
})();
  • 监听文件下载完成的黑魔法webpackJsonpCallback

/* webpack/runtime/jsonp chunk loading */
// 异步文件的引入,__webpack_require__.e实际调用的是__webpack_require__.f.j,__webpack_require__.f.j实际调用的是__webpack_require__.l
(() => {
  // 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 = {
    main: 0,
  };

  __webpack_require__.f.j = (chunkId, promises) => {
	// ...
  };

  // no prefetching

  // no preloaded

  // no HMR

  // no HMR manifest

  // no on chunks loaded

  // install a JSONP callback for chunk loading
  var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
	//data就是异步文件打包后的内容,及src_a_js.boundle.js
	//chunkIds: ['src_a_js']
	//moreModules:{'./src/a.js':加载异步文件内容的函数}
    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,即__webpack_modules__
          __webpack_require__.m[moduleId] = moreModules[moduleId];
        }
      }
      if (runtime) var result = runtime(__webpack_require__);
    }
    //parentChunkLoadingFunction即chunkLoadingGlobal.push方法自身
    if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
    // 处理异步chunk加载成功
    for (; i < chunkIds.length; i++) {
      chunkId = chunkIds[i];
      if (
        __webpack_require__.o(installedChunks, chunkId) &&
        installedChunks[chunkId]
      ) {
        installedChunks[chunkId][0](); //将之前异步文件的promise状态变成resolved
      }
      installedChunks[chunkId] = 0; // 将异步文件对应的状态变成0,表示文件已下载完毕
    }
  };

  // 本案例中:self是window,self["webpackChunkwebpack5_demo"]存储的是异步chunck及其内容,本案例对应src_a_js.bundle.js内容
  var chunkLoadingGlobal = (self["webpackChunkwebpack5_demo"] =
    self["webpackChunkwebpack5_demo"] || []);

  //本行其实没看出有什么作用。。,没有这行也不影响结果
  chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
  //这里是黑魔法监听文件下载完毕
  //修改chunkLoadingGlobal数组的push方法,变成webpackJsonpCallback
  //当异步文件下载完成执行时,会调用chunkLoadingGlobal.push方法,从而执行webpackJsonpCallback,改变文件状态
  //从而将__webpack_require__.e中的Promise.all返回的promise状态改变,进而通过__webpack_require__方法引入下载成功后保存到__webpack_modules__上的方法
  //最终实现异步模块的导入
  chunkLoadingGlobal.push = webpackJsonpCallback.bind(
    null,
    //这里还有个小魔法,先通过chunkLoadingGlobal.push.bind(chunkLoadingGlobal)返回原本的push方法
    //方法里调用push其实还是操作chunkLoadingGlobal本身,不受改变后的push影响
    //即既改变了push方法,又保留了原对象的操作方法
    chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
  );
  console.log("www3123", chunkLoadingGlobal);
})();
  • 修改chunkLoadingGlobal数组的push方法,变成webpackJsonpCallback
  • 当异步文件下载完成执行时,会调用chunkLoadingGlobal.push方法,从而执行webpackJsonpCallback,改变文件状态
  • 从而将__webpack_require__.e中的Promise.all返回的promise状态改变,进而能够再运行__webpack_require__方法引入下载成功后保存到__webpack_modules__上的方法
  • 最终实现异步模块的导入
  • 其中chunkLoadingGlobal的push改变和添加bind
    • 既改变了push方法,又保留了原对象的操作方法,也十分巧妙

打包后的异步文件

"use strict";
/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
(self["webpackChunkwebpack5_demo"] = self["webpackChunkwebpack5_demo"] || []).push([["src_a_js"],{

/***/ "./src/a.js":
/*!******************!*\
  !*** ./src/a.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 */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (\"async a\");\n\n\n//# sourceURL=webpack://webpack5_demo/./src/a.js?");

/***/ })

}]);
  • 可以知道当下载完成,执行脚本内容的时候就会执行self[“webpackChunkwebpack5_demo”]的push方法,进而调用webpackJsonpCallback通知下载完毕
  • 再来看看index.js打包后的代码,__webpack_require__就会执行上面"./src/a.js"对应的方法
      eval(
        '__webpack_require__.e(/*! import() */ "src_a_js").then(__webpack_require__.bind(__webpack_require__, /*! ./a */ "./src/a.js")).then(console.log);\n\n\n//# sourceURL=webpack://webpack5_demo/./src/index.js?'
      );
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 11:46:33  更:2022-10-31 11:50:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 20:14:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码