likes
comments
collection
share

原来webpack是这样解析commonJs与EsModule的

作者站长头像
站长
· 阅读数 15

本文将对webpack打包后的代码进行分析,理解为什么能在webpack创建的项目中同时使用esModule,commonJs,使得可以在各种浏览器中顺利执行。

创建项目

  • 创建项目&下载依赖
npm init -y

npm install webpack webpack-cli
  • 创建esModule格式的util文件
// src/util/math.js
export const add = (a, b) => {
  return a + b
}
  • 创建commonJs格式的util文件
// src/util/format.js
const dateFormat = (date) => {
  return "2023-03-13"
}

module.exports = {
  dateFormat
}
  • 创建两个入口文件
// src/main.common.js
const { dateFromat } = require("./util/format");

console.log(dateFromat("abc"))
// src/main.es.js
import { add } from "./util/math";

console.log(add(1, 2))
  • 项目根目录新建webpack.config.js文件
const path = require("path");

module.exports = {
  mode: "development",
  devtool: "source-map",
    
  entry: {
    common: "./src/main.common.js",
    es: "./src/main.es.js",
  },
  output: {
    filename: "[name].[hash:4].js",
    path: path.resolve(__dirname, "./build")
  }
}

main.common.js文件使用require导入commonjs格式的format.js文件,并打包生成common.hash.js

main.common.js文件使用import/from导入esModule格式的math.js文件,并打包生成es.hash.js

解析EsModule模块

打开打包后的文件es.hash.js,删除注释内容,并去掉外层的自执行函数,留下主要代码进行分析

// 根据模块路径获取模块内容,并填充传递过来的module空对象
var __webpack_modules__ = {
  "./src/util/format.js":
    ((module) => {
      const dateFormat = (date) => {
        return "2023-03-13"
      }
      module.exports = {
        dateFormat
      }
    })

};
// 缓存模块
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;
}

var __webpack_exports__ = {};
const { dateFromat } = __webpack_require__("./src/util/format.js");

console.log(dateFromat("abc"))

__webpack_modules__定义了一个对象,key为模块的路径,value为一个返回了模块内容的箭头函数。

__webpack_module_cache__定义了一个模块缓存对象,当某个模块被使用时,会自动添加到改对象中,后续再使用到该模块时,不需要重新读取了。

__webpack_require__是一个函数,他的参数为模块的路径,返回值为模块的内容,可以理解为模块加载函数。

我们翻到最下面可以看到:

const { dateFromat } = __webpack_require__("./src/util/format.js");

__webpack_require__函数接收到模块的路径后,先去__webpack_module_cache__中查找,如果有,直接返回模块中的函数、变量等。如果没有找到,则创建一个空对象:{export: {}},并赋值给缓存对象__webpack_module_cache__,再根据模块路径与__webpack_modules__,得到该路径对应的箭头函数。箭头函数根据传递过来的空对象,将模块内容赋值给空对象。最后由__webpack_require__返回取到的模块函数。最终实现了模块的解析。

解析CommonJs模块

打开打包后的文件es.hash.js,删除注释内容,并去掉外层的自执行函数,留下主要代码进行分析

var __webpack_modules__ = {
  "./src/util/math.js":
  ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
      // 8,为该模块添加esModule的标志
      __webpack_require__.r(__webpack_exports__);
      // 9,为"__webpack_exports__"对象添加属性:add,当值为一个返回了add函数的函数。
      __webpack_require__.d(__webpack_exports__, {
        "add": () => (add)
      });
      const add = (a, b) => {
        return a + b
      }
    })

};
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: {}
  };
  // 7,根据模块id,从模块map:"__webpack_modules__"中获取模块
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  return module.exports;
}
// 1,遍历definition的属性,如果该属性存在于definition中,却不存在exports中,当访问exports的该属性时,返回definition上的该属性值。
__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] });
    }
  }
};
// 2,判断obj上是否含有prop属性
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
// 3,如果当前js环境支持Symbol,就为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 });
};
// 4,定义了一个模块缓存对象
var __webpack_exports__ = {};
// 5,为缓存模块添加esModule标志
__webpack_require__.r(__webpack_exports__);
// 6,加载"./src/util/math.js"模块,
// 10,拿到了包含函数add的模块:_util_math__WEBPACK_IMPORTED_MODULE_0__
var _util_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/util/math.js");
// 11,执行函数add
console.log((0, _util_math__WEBPACK_IMPORTED_MODULE_0__.add)(1, 2))

在打包后的代码中打印_util_math__WEBPACK_IMPORTED_MODULE_0__,并复制到浏览器控制台,发现他为一个es模块对象:

原来webpack是这样解析commonJs与EsModule的

原来webpack是这样解析commonJs与EsModule的