美文网首页
其实webpack编译"模块化"的源码没那么难

其实webpack编译"模块化"的源码没那么难

作者: 一颗冰淇淋 | 来源:发表于2022-04-24 20:22 被阅读0次

    我们在 webpack初体验 这篇文章中演示到,浏览器不支持 CommonJS ,在特定场景下才支持 Es Module ,而 webpack 可以将这些模块化的代码解析成浏览器可识别的语法。

    那么 webpack 究竟是对模块化做了怎样的处理呢?一起来看看。

    项目结构

    demo
    ├─ src      
    │   ├─ utils
    │   │  ├─ common_math.js
    │   │  └─ esmodule_format.js  
    │   ├─ common_index.js
    │   ├─ esmodule_index.js
    │   └─ index.js
    ├─  index.html
    ├─  package.json
    └─  webpack.config.js
    

    分别以 CommonJS 和 ES Module 两种方式来进行导入导出

    // common_math.js —— 使用 CommonJS 导出
    function add(a, b) {
      return a + b;
    }
    function sub(a, b) {
      return a - b;
    }
    module.exports = {
      add,
      sub,
    };
    
    // esmodule_format.js  —— 使用 ES Module 导出
    function timeFormat() {
      return "2022-02-02";
    }
    export { timeFormat };
    

    config文件配置

    webpack.config.js 中 "mode" 默认为 "production",此时代码是经过压缩和丑化的,为了更利于阅读,我们将它设置为 "development"。

    1_development与production.png

    而 "development" 模式下 "devtool" 默认为 "eval",会给代码增加很多暂时我们不需要的内容,所以将 "devtool" 设置为 "source-map"(关于 "source-map",下一篇文章会详细介绍)

    const path = require("path");
    module.exports = {
      entry: "./src/common_index.js",
      mode: "development",
      devtool: "source-map",
      output: {
        filename: "./bundle.js",
        path: path.resolve(__dirname, "./dist"),
      },
    };
    

    CommonJS

    首先让 webpack 解析 CommonJS 语法,所以入口指定 common_index.js

    // common_index.js —— 使用 CommonJS 导入
    const { add, sub } = require("./utils/common_math.js");
    console.log(add(20, 30));
    console.log(sub(20, 30));
    

    执行 npm run build ,为了方便阅读,将生成的 将 bundle.js 中的注释、以及自执行函数的最外层全部删除。

    对于 CommonJS 的处理,主要有两个对象和一个函数

    • __webpack_modules__ 对象,用于保存文件路径和文件内容的映射关系
    • __webpack_module_cache__ 对象,用于缓存已加载过的文件,key值为文件路径,value为导出的内容
    • __webpack_require__ 函数,用于加载文件,将导出内容添加到 module.exports 和 exports 对象中

    具体webpack编译源码

    var __webpack_modules__ = {
      // 定义一个对象,对象中的 key 为文件路径,value 为函数,函数中包括文件内容
      "./src/utils/common_math.js": (module) => {
        function add(a, b) {
          return a + b;
        }
        function sub(a, b) {
          return a - b;
        }
        module.exports = {
          add,
          sub,
        };
      },
    };
    
    // 定义用于缓存已加载过文件的对象
    var __webpack_module_cache__ = {};
    function __webpack_require__(moduleId) {
      // 从缓存对象中取当前moduleId的value
      var cachedModule = __webpack_module_cache__[moduleId];
      // 如果存在直接返回 exports 对象
      if (cachedModule !== undefined) {
        return cachedModule.exports;
      }
      // 如果不存在,在缓存对象中增加 key 为 moduleId,值为 { export: {} } 的数据
      var module = (__webpack_module_cache__[moduleId] = {
        exports: {},
      });
      // 通过 moduleId,执行保存在 __webpack_modules__ 的方法,并传入参数 module 对象,
      // 执行方法后,会修改 module.exports 以及 exports 对象
      __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
      // 当前返回的就是 { add, sub } 这个对象
      return module.exports;
    }
    
    var __webpack_exports__ = {}; // 这里没有用到
    // 执行 common_math.js
    const { add, sub } = __webpack_require__("./src/utils/common_math.js");
    console.log(add(20, 30));
    console.log(sub(20, 30));
    

    通过以上方式,webpack 将浏览器不可识别的 CommonJS 代码编译成了浏览器可正常运行的语法

    ES Module

    将webpack.config.js 文件中的入口改成 esmodule_index.js。

    // esmodule_index.js —— 使用 ES Module 导入
    import { timeFormat } from "./utils/esmodule_format.js";
    console.log(timeFormat());
    

    再执行 npm run build,为了便于阅读,还是将 bundle.js 中的注释、以及自执行函数的最外层、严格模式的规定全部删除。

    ES Module 的实现与 CommonJS 相同之处在于,它也有这两个对象和一个函数

    • __webpack_modules__ 对象,用于保存文件路径和文件内容的映射关系
    • __webpack_module_cache__ 对象,用于缓存已加载过的文件,key值为文件路径,value为导出的内容
    • __webpack_require__ 函数,用于加载文件,将导出内容添加到 module.exports 和 exports 对象中

    但 ES Module 更为复杂一些,还存在这三个函数

    • __webpack_require__.d 将传入的对象属性和值遍历到 exports 对象上
    • __webpack_require__.o 判断某属性是否存在于某对象中
    • __webpack_require__.r 给使用 ES Module 实现模块化的文件中 exports 对象里增加 __esModule 属性

    具体webpack编译源码

    var __webpack_modules__ = {
      "./src/utils/esmodule_format.js": (
        __unused_webpack_module,
        __webpack_exports__,
        __webpack_require__
      ) => {
        // 给exports增加__esModule属性
        __webpack_require__.r(__webpack_exports__);
        // 将 esmodule_format.js 中导出的函数 timeFormat 添加到 exports 对象中
        __webpack_require__.d(__webpack_exports__, {
          timeFormat: () => timeFormat,
        });
        function timeFormat() {
          return "2022-02-02";
        }
      },
    };
    
    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__.d = (exports, definition) => {
      // 遍历 esmodule_format.js 中导出的对象
      for (var key in definition) {
        // 如果当前exports对象中不存在该属性,则复制到exports对象中
        if (
          __webpack_require__.o(definition, key) &&
          !__webpack_require__.o(exports, key)
        ) {
          // 定义exports对象,将函数作为get属性
          Object.defineProperty(exports, key, {
            enumerable: true,
            get: definition[key],
          });
        }
      }
    };
    
    // 判断 prop 属性是否存在于 obj 对象中
    __webpack_require__.o = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
      
    // 给 exports 对象增加 __esModule 属性
    __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__ = {};
    // __webpack_exports__ 上下两行暂时没有用到
    __webpack_require__.r(__webpack_exports__);
    
    // 使用函数将导出内容加载到 exports 和 module.exports 对象中
    var _utils_esmodule_format_js__WEBPACK_IMPORTED_MODULE_0__ =
      __webpack_require__("./src/utils/esmodule_format.js");
    
    // 以下代码和函数直接调用效果一致
    console.log(
      (0, _utils_esmodule_format_js__WEBPACK_IMPORTED_MODULE_0__.timeFormat)()
    );
    

    webpack 对于 ES Module 和 CommonJS 处理方式有些不同,CommonJS 中是直接将属性添加到 exports 对象中,而 ES Module 是通过 defineProperty 定义到该属性的 存取选择器 get 中

    ES Module 和 CommonJS 混合使用

    将webpack.config.js 文件中的入口改成 index.js。

    // index.js
    //  ES Module 导出的内容 CommonJS 导入
    const { timeFormat } = require( "./utils/esmodule_format.js");
    // CommonJS 导出的内容 ES Module 导入
    import { add, sub } from ("./utils/common_math.js");
    console.log(timeFormat());
    console.log(add(20, 30));
    console.log(sub(20, 30));
    

    再执行 npm run build,为了便于阅读,还是将 bundle.js 中的注释、以及自执行函数的最外层、严格模式的规定全部删除。

    模块化相互引用的方式复用了 ES Module 和 CommonJS 都有的两个对象和一个函数

    • __webpack_modules__ 对象,用于保存文件路径和文件内容的映射关系
    • __webpack_module_cache__ 对象,用于缓存已加载过的文件,key值为文件路径,value为导出的内容
    • __webpack_require__ 函数,用于加载文件,将导出内容添加到 module.exports 和 exports 对象中

    同时也保留了 ES Module 独有的这三个函数

    • __webpack_require__.d 将传入的对象属性和值遍历到 exports 对象上
    • __webpack_require__.o 判断某属性是否存在于某对象中
    • __webpack_require__.r 给使用 ES Module 实现模块化的文件中 exports 对象里增加 __esModule 属性

    再增加了一个函数

    • __webpack_require__.n 定义遍历赋值为函数,并在该变量上添加a属性,值为函数
    var __webpack_modules__ = {
      "./src/utils/common_math.js": (module) => {
        function add(a, b) {
          return a + b;
        }
        function sub(a, b) {
          return a - b;
        }
        module.exports = {
          add,
          sub,
        };
      },
      "./src/utils/esmodule_format.js": (
        __unused_webpack_module,
        __webpack_exports__,
        __webpack_require__
      ) => {
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, {
          timeFormat: () => timeFormat,
        });
        function timeFormat() {
          return "2022-02-02";
        }
      },
    };
    
    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__.n = (module) => {
      var getter =
        module && module.__esModule ? () => module["default"] : () => module;
      __webpack_require__.d(getter, { a: getter });
      return getter;
    };
    
    __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 = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
      
    __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__ = {};
    __webpack_require__.r(__webpack_exports__);
    var _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
      "./src/utils/common_math.js"
    );
    
    // 暂时没有用到
    var _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0___default =
    __webpack_require__.n(_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__);
    
    const { timeFormat } = __webpack_require__("./src/utils/esmodule_format.js");
    console.log(timeFormat());
    console.log(
      (0, _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__.add)(20, 30)
    );
    console.log(
      (0, _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__.sub)(20, 30)
    );
    

    ES Module 和 CommonJS 混合使用的方式是被支持的,webpack 的处理方式就是将两者单独处理合并在一起。

    以上就是webpack编译模块化文件的源码的内容,更多有关webpack的内容可以参考我其它的博文,持续更新中~

    相关文章

      网友评论

          本文标题:其实webpack编译"模块化"的源码没那么难

          本文链接:https://www.haomeiwen.com/subject/heqxyrtx.html