webpack编译源码分析

44 阅读4分钟

1.png

写在前面

2023年前端行业的内卷加剧,29岁似乎已经提前退休在家待业,为了能够重新返岗就业,不得不去卷下源码,虽然很枯燥但是收获还是有的。第一次写技术文章为了记录下自己的理解。大家且看且轻喷。

工程准备

  • 新建了一个工程化的项目如下结构 (关于怎么去建立这些东西就不用说了,文章重点是在于编译后的源码分析)
    | - node_modules
    | - dist
        |- main.js
        |- index.html
    | - src
        | - utils.js
        | - index.js
    | - package.json
    | - webpack.config.js
    | - yarn.lock
    
 

  // utils.js
     const printHello = () => {
        console.log("hello");
      };
     const key = "this is a webpack build result analysis";
     export { printHello };
     export default key;
  // index.js
    import key, { printHello } from "./utils";
    function component() {
    const element = document.createElement("div");
    printHello();
    element.innerHTML = key;
    return element;
     }
    document.body.appendChild(component());

编译后的结果

(() => {
 // webpackBootstrap
 "use strict";
 var __webpack_modules__ = {
  "./src/utils.js": (
     __unused_webpack_module,
     __webpack_exports__,
     __webpack_require__
   ) => {
     __webpack_require__.r(__webpack_exports__);
     __webpack_require__.d(__webpack_exports__, {
       default: () => __WEBPACK_DEFAULT_EXPORT__,
       printHello: () =>  printHello,
     });
     const printHello = () => {
       console.log("hello");
     };
         const key = "this is a webpack build result analysis";
         const __WEBPACK_DEFAULT_EXPORT__ = key;
   },
 };

 // 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;
 }
  // 第一个IIFE
 (() => {
   // 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/hasOwnProperty shorthand */
  // 第二个IIFE
 (() => {
   __webpack_require__.o = (obj, prop) =>
     Object.prototype.hasOwnProperty.call(obj, prop);
 })();

 /* webpack/runtime/make namespace object */
   // 第三个IIFE
 (() => {
   // 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 });
   };
 })();
 var __webpack_exports__ = {};
 // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
 // 第四个IIFE
 (() => {
   __webpack_require__.r(__webpack_exports__);
   var _utils__WEBPACK_IMPORTED_MODULE_0__ =
     __webpack_require__("./src/utils.js");
   function component() {
     const element = document.createElement("div");
     (0, _utils__WEBPACK_IMPORTED_MODULE_0__.printHello)();
     element.innerHTML = _utils__WEBPACK_IMPORTED_MODULE_0__["default"];
     return element;
   }
   document.body.appendChild(component());
 })();
})();

编译结果分析

  • 通过 webpack 编译后会用一个 IIFE 包裹住 包裹的原因是为了防止变量的全局污染,创造了一个函数的作用域
  • 首先可以看到 webpack 定义一个 webpack__modules 对象,对象的 key 值是模块文件的相对路径,value 值是一些带有参数模块的方法,该方法的中调用 webpack__require.d 方法传入的是我们自己定义的模块具名导出和默认导出的方法函数。
  • 我们再来看看 webpack_require 这个方法,该方法传入了一个 moduleId,从编译结果的注释中可以看到第四个IIFE,中的代码可以看出 moduleId 就是模块引入的相对路径,也就是 import printHello from "./src/utils.js" 在 webpack 看来就是用 webpack_require 实现了自己的模块化导入。
  • 再深入到 webpack_require 的方法中,该方法中的基本操作就是定义一个缓存的对象,先去判断这个对象中有没有之前存过的 moduleId 它的值是一个对象 {exports:{}},如果没有就重新定义一个变量来保存。所以我们可以想到是这个 exports 对象里面的值是后面通过添加进来的
  • webpack_require 中的 webpack_modules[moduleId](module, module.exports, webpack_require) 是在匹配 webpack__modules 中的 key 返回的方法然后执行,传入的是刚刚定义过的 exports 空对象,进入到返回的函数体中执行了 webpack_require.d 这个方法,这个方法是定义在 webpack_require 方法上的方法(挂载在__webpack_require__的构造函数上的)
  • 再来看看 webpack_require.d 做了什么事情,也就是第一个IIFE,参数是一个 exports和 definition,这个 definition 是什么呢,其实是一个对象我们定义的具名导出和默认导出的方法对象, 这个IIFE执行结果是在 exports 上挂载具名导出和默认导出的对象方法,该方法在definition上做了一个循环,做了一个判断用到了 webpack_require.o 这个方法,这个方法的作用是判断是不是原型对象上的key 。
  • if(webpack_require.o(definition, key) && !webpack_require.o(exports, key)) 这个意思是只遍历definition对象上的键值然后exports 不存在相应的键值 才会添加到 exports 上,所以我们可以想到的是 module.exports = { default: () => WEBPACK_DEFAULT_EXPORT, printHello: () => printHello, } 应该是这个对象。
  • var utils__WEBPACK_IMPORTED_MODULE_0_ 这个变量拿到的就应该是 module.exports这个对象
  • 然后通过对象的方式去调用这个方法。

总结

  • webpack 是通过 webpack_require 和_ webpack_modules 实现了自己的模块化,通过对象调用的方式执行对应的方法。

写在后面

通过读源码知道了 webpack 是怎么样去组织模块化的。自己用于记录学习过程,同时也希望能够帮助到你们。😊