在 wepback 中是可以使用 CommonJS 模块化,也可以使用 ES6 模块化的,而且 CommonJS 和 ES6 两种模块化可以混用,即 ES6 模块化导出使用 CommonJS 模块化导入, CommonJS 模块化导出使用 ES6 模块化导入。

CommonJS 模块化

文件结构

基于下面的文件结构然后查看打包后的源码:

  1. // common.js
  2. function increase(num1, num2) {
  3. return num1 + num2;
  4. }
  5. function multiply(num1, num2) {
  6. return num1 + num2;
  7. }
  8. module.exports = {
  9. increase,
  10. multiply,
  11. }
  1. // 入口: index.js
  2. const common = require('./common');
  3. console.log(common.increase(1, 2));

打包后的源码解析

入口为 index.js 文件,在 index.js 文件中引入了 common.js 文件,webpack 打包后的源码中会有一个自执行函数,这个自执行函数就是入口 index.js 文件中的内容:

  1. // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
  2. (() => {
  3. /********************!*\
  4. !*** ./src/index.js ***!
  5. \**********************/
  6. const common = __webpack_require__( /*! ./common */ "./src/common.js");
  7. console.log(common.increase(1, 2));
  8. })();

在上面的代码中可以看到打包后的源码将 require 函数替换成了__webpack_require__ 调用并传递了文件路径。
__webpack_require__ 源码如下:

  1. /************************************************************************/
  2. /******/ // The module cache
  3. /******/
  4. var __webpack_module_cache__ = {}; // 缓存对象
  5. /******/
  6. /******/ // The require function
  7. /******/
  8. function __webpack_require__(moduleId) {
  9. /******/ // Check if module is in cache
  10. /******/
  11. var cachedModule = __webpack_module_cache__[moduleId];
  12. // 检查缓存对象中是否存在 moduleId 对应的数据, 有则返回
  13. if (cachedModule !== undefined) {
  14. /******/
  15. return cachedModule.exports;
  16. /******/
  17. }
  18. /******/ // Create a new module (and put it into the cache)
  19. // 如果缓存对象中没有, 则创建一个对象复赋值给缓存对象[moduleId]属性和一个新的 module 变量
  20. var module = __webpack_module_cache__[moduleId] = {
  21. /******/ // no module.id needed
  22. /******/ // no module.loaded needed
  23. /******/
  24. exports: {}
  25. /******/
  26. };
  27. /******/
  28. /******/ // Execute the module function
  29. // 在 __webpack_modules__ 调用对应 moduleId 的函数
  30. __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  31. /******/
  32. /******/ // Return the exports of the module
  33. // 返回数据
  34. return module.exports;
  35. /******/
  36. }

__webpack_require__ 函数中接受一个 moduleId 的参数,这个 moduleId 就是调用时传递的 "./src/common.js",首先会在 __webpack_module_cache__ 这个缓存对象中查找是否有对应 moduleId 的数据,有的话就直接返回了,没有会创建下面这样的对象:

  1. {
  2. exports: {},
  3. }

这个对象会同时赋值给一个新的 module 属性和 __webpack_module_cache__[moduleId] 属性,然后的调用了 __webpack_modules__[moduleId] 下面的一个函数,并将 module 这个新对象和 module 下面的 exports 属性再加上自身传递了过去,最后返回了 module.exports。

  1. var __webpack_modules__ = ({
  2. "./src/common.js":
  3. /*!***********************!*\
  4. !*** ./src/common.js ***!
  5. \***********************/
  6. /***/
  7. ((module) => {
  8. function increase(num1, num2) {
  9. return num1 + num2;
  10. }
  11. function multiply(num1, num2) {
  12. return num1 + num2;
  13. }
  14. module.exports = {
  15. increase,
  16. multiply,
  17. }
  18. })
  19. });

__weboack_modules 是一个对象,这个对象中就保存着所有模块,对象中的 key 就是模块对应的路径,值是一个函数,函数中是模块中的内容,在__webpack_require__函数中调用__webpack_modules__[moduleId] 就是找到了地址对应的模块函数,执行并把模块中需要导出的数据挂到了 module.exports 中,这时在 __webpack`require_ 中创建的 module 属性和webpack_module_cache[moduleId]` 内容如下:

  1. {
  2. exports: {
  3. increase,
  4. multiply,
  5. },
  6. }

也就是说 __webpack_require__ 导出的内容就是上面的 exports 对象,这样就实现了 commonJS 的模块化。

ES6 模块化

文件结构

  1. // es.js
  2. function increase(num1, num2) {
  3. return num1 + num2;
  4. }
  5. function multiply(num1, num2) {
  6. return num1 + num2;
  7. }
  8. export default {
  9. increase,
  10. multiply,
  11. }
  1. // 入口:index.js
  2. import es from './es';
  3. console.log(es.multiply(2, 2));

打包后的源码解析

基于上面的代码结构,webpack 打包出来的内容和 commonJS 模块化打包处理的内容相差不是很多,也有一个自执行函数代表入口文件:

  1. // 这里不同的是 多了一个 __webpack_exports__ 对象
  2. var __webpack_exports__ = {};
  3. // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
  4. (() => {
  5. /*!**********************!*\
  6. !*** ./src/index.js ***!
  7. \**********************/
  8. // 执行了 __webpack_require__.r 函数, 函数的作用是打一个标记
  9. __webpack_require__.r(__webpack_exports__);
  10. /* harmony import */
  11. var _es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./es */ "./src/es.js");
  12. console.log(_es__WEBPACK_IMPORTED_MODULE_0__.default.multiply(2, 2));
  13. })();

在 ES6 打包出来的源码中会多几个功能函数如下:

  1. __webpack_require__.r = (exports) => {
  2. // 判断是否支持 Symbol 和 Symbol.toStringTag 是否存在
  3. if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  4. // 存在的话则在 exports 对象上添加一个 key 为 Symbol.toStringTag, 值为 Module 的属性
  5. // 这个操作会在 exports.toString() 时返回 "[object Module]", 自定义了对象类型标签。
  6. Object.defineProperty(exports, Symbol.toStringTag, {
  7. value: 'Module'
  8. });
  9. }
  10. // 在 exports 对象上添加 __esModule 属性, 值为 true
  11. Object.defineProperty(exports, '__esModule', {
  12. value: true
  13. });
  14. };
  1. // 判断 prop 是不是在 obj 对象中
  2. __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop));
  1. __webpack_require__.d = (exports, definition) => {
  2. // 遍历 definition
  3. for (var key in definition) {
  4. // 调用 o 函数找出 definition 实例属性,并且 exports 对象中没有相同 key 的属性时进行下一步
  5. if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
  6. // 将 definition 对象的属性通过属性描述符的方式定义在了 exports 上,
  7. // exports 通过对应的 key 就可以找到 definition 上的属性值
  8. Object.defineProperty(exports, key, {
  9. enumerable: true,
  10. // 注意: get 是一个函数, 所以说 definition[key] 就是一个函数, exports[key] 访问时,
  11. // 返回的内容就是 definition[key] 函数调用后返回的内容
  12. get: definition[key]
  13. });
  14. }
  15. }
  16. };

基于上面的三个功能函数看一下自执行函数中调用的 _webpack_require 函数:

  1. var __webpack_module_cache__ = {};
  2. function __webpack_require__(moduleId) {
  3. var cachedModule = __webpack_module_cache__[moduleId];
  4. return cachedModule.exports;
  5. }
  6. var module = __webpack_module_cache__[moduleId] = {
  7. exports: {}
  8. };
  9. __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  10. return module.exports;
  11. }

可以看到 webpackrequire 函数函数中并没有用到上面的三个功能函数,它的内容和 commonJS 生成的一样,接下来看下 webpack modules_ 对象:

  1. var __webpack_modules__ = ({
  2. "./src/es.js":
  3. /*!*******************!*\
  4. !*** ./src/es.js ***!
  5. \*******************/
  6. /***/
  7. ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
  8. // 调用 r 函数, 给 exports 对象打上标记
  9. __webpack_require__.r(__webpack_exports__);
  10. // 调用 d 函数, 使用 defineProperty 的方式, 让 exports 可以访问到第二个参数对象
  11. __webpack_require__.d(__webpack_exports__, {
  12. // 这个 default 函数返回的对象就是当前模块中导出的对象
  13. "default": () => (__WEBPACK_DEFAULT_EXPORT__)
  14. });
  15. function increase(num1, num2) {
  16. return num1 + num2;
  17. }
  18. function multiply(num1, num2) {
  19. return num1 + num2;
  20. }
  21. // 当前模块导出的内容挂载到这个对象中,exports 也能访问到
  22. const __WEBPACK_DEFAULT_EXPORT__ = ({
  23. increase,
  24. multiply,
  25. });
  26. })
  27. });

这个时候返回的对象结构如下:

{
  default: { // get
      increase,
    multiply,
  },
  __esModule: true,
  [Symbol.toStringTag]: 'Module'
}

在代码中通过 es.multiply调用,打包后的代码转成了 es_WEBPACK_IMPORTED_MODULE_0.default.multiply 调用。

ES6 和 CommonJS 模块化混用

文件结构

其他文件结构和上面一样,入口 index 文件内容如下:

// 入口:index
const es = require('./es');

console.log(es.multiply(2, 2));

import { increase } from './common';

console.log(increase(2, 2));

打包后的源码分析

(() => {
  // 通过 import 引入 module.exports 导出
  var _common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./common */ "./src/common.js");
  // 通过 require 引入 expoet.default 导出
  const es = __webpack_require__( /*! ./es */ "./src/es.js");
  console.log(es.multiply(2, 2));
  console.log((0, _common__WEBPACK_IMPORTED_MODULE_0__.increase)(2, 2));
})();

除去这个自执行函数外,其他的代码与上面 common、es 源码相同,测试后发现报错,原因是: 通过 require 引入 expoet.default 导出的内容时 webpack _require 这个函数函数的其实是一个带有 default 的对象,所有模块都出内容都在这个 default 对象上,而代码直接使用了 es.multiply 函数所以报错了,改成 es.default.multiply 就可以正常执行了,而 import 方式引入 module.exports 导出时是正常的。

改变文件结构

将 es 文件中的默认导出 export.default 改为 export :

export function increase(num1, num2) {
  return num1 + num2;
}

export function multiply(num1, num2) {
  return num1 + num2;
}

将 ES6 的默认导出改为 export 导出后代码就能正常运行了,这时因为去掉了 default 这个属性,直接导出了对应的属性,不再需要 module.defalut.xxx 这样的方式调用了:

var __webpack_modules__ = ({
    "./src/es.js":
  ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, {
      // 这里导出的就不是 default 属性,而是创建了多个 get 函数
      "increase": () => ( /* binding */ increase),
      "multiply": () => ( /* binding */ multiply)
    });

    function increase(num1, num2) {
      return num1 + num2;
    }

    function multiply(num1, num2) {
      return num1 + num2;
    }
  })
})

所以当 ES6 模块化和 CommonJS 模块化混用时,不要通过 require 的方式去导入 export default 导出的内容!