webpack的模块化原理

7/23/2021 webpack

# Mode 配置

此处只是简单的提一下 Mode 配置选项

  • 默认值:production(什么都不设置的情况下)
  • 可选值: 'none' | 'development' | 'production'

三种选项值的区别

选项 描述
development 会将DefinePluginprocess.env.NODE_ENV的值设置为development。为模块和 chunk 启用有效的名。
production 会将DefinePluginprocess.env.NODE_ENV的值设置为production。为模块和 chunk 启用确定性的混淆名称。FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginTerserPlugin
none 不使用任何默认优化选项

使用

module.exports = {
  mode: 'development',
};
1
2
3

# Webpack 的模块化

Webpack 打包的代码,允许我们使用各种各样的模块化,但是最常用的是CommonJSES Module

先写一个ESModule规范和一个CommonJS规范的模块

// format.js  CommonJS
const dateFormat = data => {
    return "2021-07-04"
}

const priceFormat = price => {
    return "100.00"
}

module.exports = { dateFormat, priceFormat }
1
2
3
4
5
6
7
8
9
10
// math.js  ES Module
export const sum = (num1, num2) => {
    return num1 + num2
}

export const mul = (num1, num2) => {
    return num1 * num2
}
1
2
3
4
5
6
7
8

# Webpack 对于 CommonJS 模块化实现原理分析

编译前代码

const { dateFormat, priceFormat } = require("./js/format")

console.log(dateFormat("abc"))
console.log(priceFormat("abc"))
1
2
3
4

编译后代码

// 定义了一个对象
// 模块的路径(key): 函数(value)
var __webpack_modules__ = {

  "./src/js/format.js":
    (function (module) {

      const dateFormat = data => {
        return "2021-07-04"
      }

      const priceFormat = price => {
        return "100.00"
      }

      // * 将我们要导出的变量,放入到module对象中的exports对象中,同时也放入了__webpack_module_cache__缓存中
      module.exports = { dateFormat, priceFormat }

    })

};

// 定义一个对象,作为加载模块的缓存
var __webpack_module_cache__ = {};

// 是一个函数当我们加载一个模块时,都会通过这个函数来加载
function __webpack_require__(moduleId) {
  // * 1. 获取参数对应的缓存值,判断缓存中是否已经加载过
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    // * 如果缓存过就直接从缓存取
    return cachedModule.exports;
  }

  // * 2. 给module遍历和__webpack_module_cache__[moduleId]赋值了同一个对象,以后就会共享变化(因为地址一样)
  var module = __webpack_module_cache__[moduleId] = { exports: {} };

  // * 加载执行模块
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

  // * 4. 导出module.exports {dateFormat, priceFormat }
  return module.exports;
}

var __webpack_exports__ = {};

// * 加!立即执行函数的一种写法,1. (function () {})()  2. (function () {}()) 3. !function() {}()
// * 默认情况下定的的function无法直接运行,采用取反将其变成表达式后,就可以使用立即执行
// todo 此处开始执行,前面在定义
!function () {
  // * 加载./src/js/format
  // todo 从__webpack_require__获取到返回值({dateFormat, priceFormat })然后将其解构
  const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js")

  // todo 然后调用上面解构的函数
  console.log(dateFormat("abc"))
  console.log(priceFormat("abc"))
}();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# Webpack 对于 ES Module 模块化实现原理分析

编译前代码

import { sum, mul } from "./js/math"

console.log(sum(20, 30))
console.log(mul(20, 30))
1
2
3
4

编译后代码

// 1. 定义了一个对象,对象里面放的是我们的模块映射
var __webpack_modules__ = ({

  "./src/js/math.js":
    (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {

      // * 调用r,记录是一个__esModule -> true
      __webpack_require__.r(__webpack_exports__);
      // * exports对象本身是没有对应的函数
      __webpack_require__.d(__webpack_exports__, {
        "sum": function () { return sum; },
        "mul": function () { return mul; }
      });
      const sum = (num1, num2) => {
        return num1 + num2
      }

      const mul = (num1, num2) => {
        return num1 * num2
      }

    })

});

// * 模块的缓存
var __webpack_module_cache__ = {};

// * 3. require函数的实现(加载模块)
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;
}

// * 立即执行函数
!function () {
  // * 函数本身也是一个对象,给__webpack_require__这个函数添加一个d属性,它的值是一个function
  __webpack_require__.d = function (exports, definition) {
    // * 遍历传入的对象
    for (var key in definition) {
      // * 调用o判断definition和exports是否有key这个属性
      if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
        // * definition有且exports没有这个属性
        // * 就给exports定义key这个属性,设置为可枚举的并重写get方法
        // * 该get方法相当于做了一个代理,当我们exports[key]调用时,实际回去访问definition[key]
        Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
      }
    }
  };
}();

!function () {
  // * 给__webpack_require__这个函数添加一个o属性,它的值是一个function
  // * 用于判断obj中是否有prop这个属性
  __webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
}();

!function () {
  // * 给__webpack_require__这个函数添加一个r属性,它的值是一个function
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      // * 如果支持Symbol的话
      // * 给exports添加一个Symbol.toStringTag属性,告诉其是一个模块
      Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
    // * 给exports添加一个__esModule属性,标记加载的是一个ES Module
    Object.defineProperty(exports, '__esModule', { value: true });
  };
}();


var __webpack_exports__ = {};
!function () {
  // * 调用r,记录是一个__esModule -> true
  __webpack_require__.r(__webpack_exports__);
  // * 本质就是调用__webpack_modules__的./src/js/math.js属性,它是一个函数
  // * _js_math__WEBPACK_IMPORTED_MODULE_0__就等于exports
  var _js_math__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/math.js");

  // * 下面的语法相当于func(20, 30)
  console.log((0, _js_math__WEBPACK_IMPORTED_MODULE_0__.sum)(20, 30))
  console.log((0, _js_math__WEBPACK_IMPORTED_MODULE_0__.mul)(20, 30))
}();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

注意:在Es Module的实现中,exports中实际上只是获取了一个代理,只是在访问时调用get去获取definition中对应的函数

# CommonJS和ESModule混合使用

// es module导出内容,commonJs导入内容
const { sum, mul } = require("./js/math")

// CommonJS导出内容,es module导入内容
import { dateFormat, priceFormat } from "./js/format"

console.log(sum(20, 30))
console.log(mul(20, 30))

console.log(dateFormat("aaa"))
console.log(priceFormat("bbb"))
1
2
3
4
5
6
7
8
9
10
11

var __webpack_modules__ = ({

    "./src/js/format.js":
      (function (module) {
        const dateFormat = data => {
          return "2021-07-04"
        }
        const priceFormat = price => {
          return "100.00"
        }
        module.exports = { dateFormat, priceFormat }
      }),
  
    "./src/js/math.js":
      (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
  
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, {
          "sum": function () { return sum; },
          "mul": function () { return mul; }
        });
        const sum = (num1, num2) => {
          return num1 + num2
        }
  
        const mul = (num1, num2) => {
          return num1 * num2
        }
  
      })
  
  });
  
  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;
  }
  
  !function () {
    // * 给__webpack_require__这个函数添加一个n属性,是一个函数
    // * 判断当前这个module是不是Es Module, 如果是就加给module加一个default再返回,如果不是就直接返回
    __webpack_require__.n = function (module) {
      var getter = module && module.__esModule ?
        function () { return module['default']; } :
        function () { return module; };
      // * 调用d给getter代理一下
      __webpack_require__.d(getter, { a: getter });
      return getter;
    };
  }();
  
  !function () {
    __webpack_require__.d = function (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] });
        }
      }
    };
  }();
  
  !function () {
    __webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
  }();
  
  !function () {
    __webpack_require__.r = function (exports) {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  }();
  
  
  var __webpack_exports__ = {};
  !function () {
    "use strict";
    // * 调用r,告诉当前是一个Es Module
    __webpack_require__.r(__webpack_exports__);
    // * 加载format
    // CommonJS导出内容,es module导入内容
    var _js_format__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/format.js");
    var _js_format__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_js_format__WEBPACK_IMPORTED_MODULE_0__);
  
    // ES module导出内容,CommonJS导入内容
    const { sum, mul } = __webpack_require__("./src/js/math.js");
  
    console.log(sum(20, 30))
    console.log(mul(20, 30))
  
    console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().dateFormat("aaa"))
    console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().priceFormat("bbb"))
  }();
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

分析

  • 可以看到在webpack中无论ES Module还是CommonJS都是公用同一个调用函数__webpack_require__
  • 当我们直接解构获取导出的内容(使用CommonJS语法导入)时,编译出来也是使用解构的
  • 而使用ESModule导入的内容就算使用“解构语法”(实际不是解构),编译出来的结果也不是解构的而是使用对象.获取,个人分析是因为ESModule导入时使用了代理(此处代理是指重写getter,并没给其赋值,而是访问的时候调用getter去获取别的地方的值)的方式,所以不能解构
  • 但是CommonJS导入,ES Module导出时好像也有一次代理(疑问。。)
Last Updated: 1/21/2025, 10:16:53 AM