webpack模块加载(二):不同模式的模块之间的加载

278 阅读3分钟

目前在使用 webpack 开发的时候,经常会出现 CommonJs 模块和 ESModule 模块混用的情况。由于这两种的模块是基于不同的规范定义的,那么它们之间是如何相互加载的呢?

我们来看一下以下几种情况:

1. CommentJs 加载 CommentJs

  • 定义一个 CommonJs 模块
// module.js
module.exports = {
    name: "CommonJs module",
    desc: "module description"
}
  • 使用 CommentJs 的方法 require 加载该模块
// index.js
const module = require("./module.js")
console.log('module.name :>> ', module.name);
console.log('module.desc :>> ', module.desc);
  • webpack 打包结果
(() => {

  // module.js 模块代码
  var webpackModules = {
    "./src/module.js": (module, exports, require) => {
      module.exports = {
        name: "CommonJs module",
        desc: "module description"
      };
    }
  };

  // webpack 定义的模块加载的方法
  var webpackModuleCache = {};
  function webpackRequire(moduleId) {

    // 判断该模块是否加载过,若加载过直接取缓存
    var cachedModule = webpackModuleCache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }

    // 定义一个空的 module 对象,用于存储当前加载模块的导出 `exports`
    var module = webpackModuleCache[moduleId] = {
      exports: {}
    };

    // 执行要加载的模块代码
    webpackModules[moduleId](module, module.exports, webpackRequire);

    // 返回模块导出
    return module.exports;
  }

  var webpackExports = {};

  // 入口文件 index.js
  (() => {
    // 通过 webpackRequire 加载模块
    const module = webpackRequire("./src/module.js");
    console.log('module.name :>> ', module.name);
    console.log('module.desc :>> ', module.desc);
  })();
})();
  • 模块文件
    • 首先 CommonJs 的模块会被包装成一个函数,并传入 module, exports, require 参数。模块的源代码没有改变。以模块路径作为 moduleId 存储在 webpackModules 对象中。
    • webpack 会定义一个 webpackRequire 方法用来加载模块。
      • 判断该模块是否加载过,若加载过直接取缓存
      • 定义一个空的 module 对象,用于存储当前加载模块的导出 exports
      • 执行要加载的模块代码
      • 返回模块导出
  • 入口文件
    • 通过 webpackRequire 方法来加载。

2. CommentJs 加载 ESModule

  • 定义一个ESModule
// module.js
export default 'ESModule default';
export const temp = "Templates"
  • 使用 CommentJs 的方法 require 加载该 ESModule 模块。
const module = require("./module.js")
console.log('module :>> ', module);
console.log('module.dafault :>> ', module.default);
console.log('module.temp :>> ', module.temp);
  • webpack打包后的代码
(() => {
  var webpackModules = {
    "./src/module.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
      "use strict";
      // 定义该模块为 ESModule
      webpackRequire.r(webpackExports);

      // 重写ESModule的导出对象
      webpackRequire.d(webpackExports, {
        "default": () => webpackDefaultExport,
        "temp": () => temp
      });
      const webpackDefaultExport = 'ESModule';
      const temp = "Templates";
    }
  };

  // webpackRequire
  var webpackModuleCache = {};
  function webpackRequire(moduleId) {
    var cachedModule = webpackModuleCache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = webpackModuleCache[moduleId] = {
      exports: {}
    };
    webpackModules[moduleId](module, module.exports, webpackRequire);
    return module.exports;
  }

  (() => {
    // 定义对象属性的getter
    webpackRequire.d = (exports, definition) => {
      for (var key in definition) {
        if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
          Object.defineProperty(exports, key, {
            enumerable: true,
            get: definition[key]
          });
        }
      }
    };
  })();

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

  (() => {
    // 定义该模块为一个ESModule的标志
    webpackRequire.r = exports => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
          value: 'Module'
        });
      }
      Object.defineProperty(exports, 'esmodule', {
        value: true
      });
    };
  })();

  var webpackExports = {};
  (() => {
    const module = webpackRequire("./src/module.js");
    console.log('module :>> ', module);
    console.log('module.default :>> ', module.default);
    console.log('module.desc :>> ', module.temp);
  })();
})();
  • 模块文件
    • webpack 会对 ESModule 进行一个简单的修改.
    • 首先也是同样将模块包装成了一个箭头函数,并传入 module, exports, require 参数。
    • 通过 webpackRequire.r 标志为一个 ESModule 模块。
    • 通过 webpackRequire.d 将要导出的属性设置一个 getter 方法。这样也保证了导出的是值的引用
    • 重写了导出的 默认导出 以及 变量导出
  • 入口文件
    • 通过 webpackRequire 方法来加载。
    • 通过module.default来获取默认导出

3. ES 加载 ES

export default "name"
export const age = 27;
import name, {age} from "./module"
console.log('name :>> ', name);
console.log('age :>> ', age);
(() => {
  "use strict";
  var webpackModules = {
    "./src/module.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
      webpackRequire.r(webpackExports);
      webpackRequire.d(webpackExports, {
        "age": () => age,
        "default": () => webpackDefaultExport
      });
      const webpackDefaultExport = "name";
      const age = 27;
    }
  };
  var webpackModuleCache = {};
  function webpackRequire(moduleId) {
    var cachedModule = webpackModuleCache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = webpackModuleCache[moduleId] = {
      exports: {}
    };
    webpackModules[moduleId](module, module.exports, webpackRequire);
    return module.exports;
  }
  (() => {
    webpackRequire.d = (exports, definition) => {
      for (var key in definition) {
        if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
          Object.defineProperty(exports, key, {
            enumerable: true,
            get: definition[key]
          });
        }
      }
    };
  })();
  (() => {
    webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
  })();
  (() => {
    webpackRequire.r = exports => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
          value: 'Module'
        });
      }
      Object.defineProperty(exports, 'esmodule', {
        value: true
      });
    };
  })();
  var webpackExports = {};
  (() => {
    webpackRequire.r(webpackExports);
    var _modulewebpackImportedModule0 = webpackRequire("./src/module.js");
    console.log('name :>> ', _modulewebpackImportedModule0["default"]);
    console.log('age :>> ', _modulewebpackImportedModule0.age);
  })();
})();
  • 模块文件
    • ESModule 的模块还是通过同样的方式进行封装
  • 入口文件
    • 首先定义一个空对象{},并将该对象标记为一个 ESModule模块。
    • 通过webpackRequire方法加载模块代码
    • 通过module.default来获取默认导出

4. ES 加载 CommentJs

  • 定义CommentJs模块
// module.js
module.exports = {
    name: "common_module",
    desc: "A common module"
}
  • 通过ESModule的引入方式引入
import module from "./module"
console.log('name :>> ', module.name);
console.log('desc :>> ', module.desc);
  • 导出结果
(() => {
  var webpackModules = {
    "./src/module.js": module => {
      module.exports = {
        name: "common_module",
        desc: "A common module"
      };
    }
  };
  var webpackModuleCache = {};
  function webpackRequire(moduleId) {
    var cachedModule = webpackModuleCache[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = webpackModuleCache[moduleId] = {
      exports: {}
    };
    webpackModules[moduleId](module, module.exports, webpackRequire);
    return module.exports;
  }
  (() => {
    webpackRequire.n = module => {
      var getter = module && module.esmodule ? () => module['default'] : () => module;
      webpackRequire.d(getter, {
        a: getter
      });
      return getter;
    };
  })();
  (() => {
    webpackRequire.d = (exports, definition) => {
      for (var key in definition) {
        if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
          Object.defineProperty(exports, key, {
            enumerable: true,
            get: definition[key]
          });
        }
      }
    };
  })();
  (() => {
    webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
  })();
  (() => {
    webpackRequire.r = exports => {
      if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
          value: 'Module'
        });
      }
      Object.defineProperty(exports, 'esmodule', {
        value: true
      });
    };
  })();
  var webpackExports = {};
  (() => {
    "use strict";
    webpackRequire.r(webpackExports);
    var _modulewebpackImportedModule0 = webpackRequire("./src/module.js");
    var _modulewebpackImportedModule0_Default = webpackRequire.n(_modulewebpackImportedModule0);
    console.log('name :>> ', _modulewebpackImportedModule0_Default().name);
    console.log('desc :>> ', _modulewebpackImportedModule0_Default().desc);
  })();
})();