Webpack是如何实现js模块化的

400 阅读2分钟

我们先写个简单demo,然后webpack build后观察产出bundle方式来了解webpack是怎么实现的js模块化的

测试环境webpack5

写个简单demo

//index.js
import * as d from "./demo/demo";
import * as d2 from "./demo/demo2";

console.log(d.a);
console.log(d2.a);
//demo.js
import * as d from "./demo2";

export let a = d;
//demo2.js
export let a = { a: 1 };

webpack build

webpack build生成了bundle.js截了部分代码如下

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

/***/ "./src/demo/demo.ts":
/*!**************************!*\
  !*** ./src/demo/demo.ts ***!
  \**************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "a": () => (/* binding */ a)
/* harmony export */ });
/* harmony import */ var _demo2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo2 */ "./src/demo/demo2.ts");


let a = _demo2__WEBPACK_IMPORTED_MODULE_0__;

/*  */
/***/ }),

/***/ "./src/demo/demo2.ts":
/*!***************************!*\
  !*** ./src/demo/demo2.ts ***!
  \***************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "a": () => (/* binding */ a)
/* harmony export */ });
let a = { a: 1 };

...

注释太多影响可读性,写个正则表达式去掉注释,正则表达式如下

/.*?\*/|//.*|/\*(.|\r\n|\n)*?\*/

继续我们格式化代码,现在代码如下

(() => {
  "use strict";
  var __webpack_modules__ = {
    "./src/demo/demo.ts": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        a: () => a,
      });
      var _demo2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
        "./src/demo/demo2.ts"
      );

      let a = _demo2__WEBPACK_IMPORTED_MODULE_0__;
    },

    "./src/demo/demo2.ts": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        a: () => a,
      });
      let a = { a: 1 };
    },
  };

  e;
  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) => {
      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 _demo_demo__WEBPACK_IMPORTED_MODULE_0__ =
      __webpack_require__("./src/demo/demo.ts");
    var _demo_demo2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
      "./src/demo/demo2.ts"
    );

    console.log(_demo_demo__WEBPACK_IMPORTED_MODULE_0__.a);
    console.log(_demo_demo2__WEBPACK_IMPORTED_MODULE_1__.a);
  })();
})();

webpack模块实现讲解

如果分析上面代码,bundle里面代码功能主要分三块功能,分别是

  • __webpack_modules__变量
  • __webpack_require__函数
  • index代码执行

我们按照webpack_require,webpack_modules,index代码执行顺序讲解代码

webpack_require

我们来看__webpack_require__.r(),传了export对象,然后标记为模块

(() => {
    __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__.o(),只是hasOwnProperty的封装

 (() => {
    __webpack_require__.o = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
  })();

我们来看__webpack_require__.d(),defintion对象浅拷贝到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__(), 传进来的id缓存过,那么从缓存取exports,没有则从__webpack_modules__取模块并且初始化,然后存到__webpack_modules__

  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_modules

__webpack_modules__对象分别存了demo.js跟demo2.js文件,索引为文件名,后面是函数。 这个函数是在模块初始化的时候执行

先看代码逻辑

  • 代码逻辑是声明__webpack_exports__为模块
  • 拷贝代码里的export的字段到__webpack_exports__
  • 执行文件里的代码逻辑
  var __webpack_modules__ = {
    "./src/demo/demo.ts": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        a: () => a,
      });
      var _demo2__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
        "./src/demo/demo2.ts"
      );

      let a = _demo2__WEBPACK_IMPORTED_MODULE_0__;
    },

    "./src/demo/demo2.ts": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        a: () => a,
      });
      let a = { a: 1 };
    },
  };

来看参数

参数来自于__webpack_require__,可以翻看下面代码

  //__webpack_require__
  var module = (__webpack_module_cache__[moduleId] = {
      exports: {},
    });

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

index代码执行

我们理解了__webpack_modules__跟__webpack_require__不难看出初始化的代码逻辑。故只列出源代码,跳过讲解

  var __webpack_exports__ = {};

  (() => {
    __webpack_require__.r(__webpack_exports__);
    var _demo_demo__WEBPACK_IMPORTED_MODULE_0__ =
      __webpack_require__("./src/demo/demo.ts");
    var _demo_demo2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
      "./src/demo/demo2.ts"
    );

    console.log(_demo_demo__WEBPACK_IMPORTED_MODULE_0__.a);
    console.log(_demo_demo2__WEBPACK_IMPORTED_MODULE_1__.a);
  })();

总结

webpack实现js模块化使用的原理是通过把各个文件里的代码函数闭包化来实现的。每个js文件闭包化之后,以key-value形式存到__webpack_modules__对象里面通过__webpack_require__读取exports,从而做到避免变量影响全局作用域