在邂逅webpack时就有关于提到webpack的模块化,这里前情回顾一下:
webpack默认支持各种模块化开发,比如ES Mouele、CommonJS、AMD等。(新版本的浏览器默认只支持ES Module,而旧版本是不支持所有模块化的。有了webpack我们就可以自行选择想要的模块化方案,无需担心浏览器支不支持。)

而且更牛的是,webpack还让我们的commonJs可以使用ES Module,EsModule也可以使用commonJS。它们是可以混着用的。那这些是怎么实现的呢?
这里我们将尝试看一下打包出来后的js源码,就可以最直观地感受到webpack是怎样实现模块化的了。

Webpack实现CommonJS原理

首先我们写一段使用了commonJS的语法的代码,然后使用webpack打包出来,就可以看到webpack实现commonJS模块化后转化的代码了。(webpack配置打包mode模式为development,默认的production模式打包会改变我们写的变量名,不利于阅读)

  1. //使用CommonJS
  2. const { dateFormat, priceFormat } = require('./js/format');
  3. console.log(dateFormat("abc"));
  4. console.log(priceFormat("abc"));

我们可以看到打包出来的源码,是存在很多模板注释以及其他一些杂七杂八的东西影响我们阅读。我们接下来将删掉这些东西,然后看一下我们整理好的代码。

wps2.jpg

整理好都的代码:

  1. //每个依赖的模块都在这个对象里存储:{模块的路径(key): 函数(value)}
  2. var __webpack_modules__ = {
  3. //这里的key:value,其实就是我们require要导入的文件路径与我们写的代码
  4. "./src/js/format.js": function (module) {
  5. const dateFormat = (date) => {
  6. return "2022-5-7";
  7. };
  8. const priceFormat = (price) => {
  9. return "100.00";
  10. };
  11. // 将我们要导出的变量, 放入到module对象中的exports对象
  12. module.exports = {
  13. dateFormat,
  14. priceFormat,
  15. };
  16. },
  17. };
  18. // 定义一个对象, 作为加载模块的缓存
  19. var __webpack_module_cache__ = {};
  20. // 这个函数就是webpack为我们生成的,用来替代commonJs的require
  21. function __webpack_require__(moduleId) {
  22. // 1.判断缓存中是否已经加载过
  23. if (__webpack_module_cache__[moduleId]) {
  24. return __webpack_module_cache__[moduleId].exports;
  25. }
  26. // 2.给新建的module变量和__webpack_module_cache__[moduleId]缓存对象,一起赋值了同一个对象
  27. var module = (__webpack_module_cache__[moduleId] = { exports: {} });
  28. // 3.在保存路径名与代码的对象里,根据路径名拿到我们需要用到的代码
  29. __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  30. // 4.我们要require的东西已经保存到了module.exports里,return
  31. return module.exports;
  32. }
  33. // 立即执行函数:开始执行代码逻辑!!!
  34. //其实这里就是我们写的代码,但是涉及commonJs模块化的地方进行了处理
  35. !(function () {
  36. // 1.使用webpack生成的 __webpack_require__函数 替代commonJS的require
  37. const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js");
  38. console.log(dateFormat("abc"));
  39. console.log(priceFormat("abc"));
  40. })();

现在我们就清楚webpack是怎样实现commonJS的模块化了。就是去重写commonJS的导入与导出,写成浏览器看得懂的样子。
并且还贴心地附送了缓存功能,下次进行导入导出直接在缓存里拿到就可以了,无需重复逻辑。

Webpack实现ES Module原理

老样子,写一段使用了ES Module 的代码,然后打包看看打包出来的代码。

  1. //使用ES Module
  2. import { sum, mul } from "./js/math";
  3. console.log(mul(20, 30));
  4. console.log(sum(20, 30));

打包后的代码如下:

  1. // 1.定义了一个对象, 对象里面放的是我们的模块映射
  2. //每个依赖的模块都在这个对象里存储:{模块的路径(key): 函数(value)}
  3. var __webpack_modules__ = {
  4. "./src/es_index.js": function (
  5. __unused_webpack_module,
  6. __webpack_exports__,
  7. __webpack_require__
  8. ) {
  9. // 调用r的目的:记录下__webpack_exports__是一个ES Modue
  10. __webpack_require__.r(__webpack_exports__);
  11. var _js_math__WEBPACK_IMPORTED_MODULE_0__ =
  12. __webpack_require__("./src/js/math.js");
  13. console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.mul(20, 30));
  14. console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.sum(20, 30));
  15. },
  16. "./src/js/math.js": function (
  17. __unused_webpack_module,
  18. __webpack_exports__,
  19. __webpack_require__
  20. ) {
  21. __webpack_require__.r(__webpack_exports__);
  22. // 调用了d函数: 给exports设置了一个代理definition(我们现在传入的第二个参数就是definition)
  23. // exports对象中本身是没有对应的函数,所以我们通过exports拿到对应函数,其实是到代理definition里拿到的
  24. __webpack_require__.d(__webpack_exports__, {
  25. sum: function () {
  26. return sum;
  27. },
  28. mul: function () {
  29. return mul;
  30. },
  31. });
  32. const sum = (num1, num2) => {
  33. return num1 + num2;
  34. };
  35. const mul = (num1, num2) => {
  36. return num1 * num2;
  37. };
  38. },
  39. };
  40. // 2.模块的缓存
  41. var __webpack_module_cache__ = {};
  42. // 3.require函数的实现(加载模块)
  43. function __webpack_require__(moduleId) {
  44. // 1.判断缓存中是否已经加载过
  45. if (__webpack_module_cache__[moduleId]) {
  46. return __webpack_module_cache__[moduleId].exports;
  47. }
  48. // 2.给新建的module变量和__webpack_module_cache__[moduleId]缓存对象,一起赋值了同一个对象
  49. var module = (__webpack_module_cache__[moduleId] = {
  50. exports: {},
  51. });
  52. // 3.在保存路径名与代码的对象里,根据路径名module拿到我们需要用到的代码
  53. __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  54. // 4.我们要require的东西已经保存到了module.exports里,return
  55. return module.exports;
  56. }
  57. //写了三个立即执行函数,为我们的__webpack_require__添加三个属性,分别是d、o、r,它们分别是一个函数,有各自的作用
  58. !(function () {
  59. // __webpack_require__.d属性:exports没有definition的值时,就修改exports的个体,保证exports可以拿到definition的值
  60. __webpack_require__.d = function (exports, definition) {
  61. //遍历definition
  62. for (var key in definition) {
  63. //如果exports里没有我们的key,就去修改get,这样每次get都可以从definition上拿到我们要的key
  64. if (
  65. __webpack_require__.o(definition, key) &&
  66. !__webpack_require__.o(exports, key)
  67. ) {
  68. Object.defineProperty(exports, key, {
  69. enumerable: true,
  70. get: definition[key],
  71. });
  72. }
  73. }
  74. };
  75. })();
  76. !(function () {
  77. __webpack_require__.o = function (obj, prop) {
  78. return Object.prototype.hasOwnProperty.call(obj, prop);
  79. };
  80. })();
  81. //r的作用是用来记录当前使用的模块化是ES Module类型
  82. !(function () {
  83. __webpack_require__.r = function (exports) {
  84. //如果支持Symbol,就会加这个东西:记录下exports是一个Module模块(exports就是用来存放我们使用模块化ES Moduel导入的东西)
  85. if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
  86. Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
  87. }
  88. // 记录下exports是一个ES Module
  89. Object.defineProperty(exports, "__esModule", { value: true });
  90. };
  91. })();
  92. //入口进入
  93. __webpack_require__("./src/es_index.js");