参考: https://zhuanlan.zhihu.com/p/337796076

JavaScript 模块化机制概览

JavaScript 常见的模块化机制主要有以下三种:

  • AMD (Asynchronous Module Definition): 在浏览器中使用,并用 define 函数定义模块;
  • CJS (CommonJS): 在 NodeJS 中使用,用 require 和 module.exports 引入和导出模块;
  • ESM (ES Modules): JavaScript 从 ES6(ES2015) 开始支持的原生模块机制,使用 import 和 export 引入和导出模块;

顺便说一下 UMD,它并不是模块机制的一种方式,而是对源代码进行打包并在运行的时候根据环境变量来判断当前到底处于何种环境

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    // AMD. Register as an anonymous module.
    define(["exports", "b"], factory);
  } else if (
    typeof exports === "object" &&
    typeof exports.nodeName !== "string"
  ) {
    // CommonJS
    factory(exports, require("b"));
  } else {
    // Browser globals
    factory((root.myModuleName = {}), root.b);
  }
})(typeof self !== "undefined" ? self : this, function (exports, b) {
  // Use b in some fashion.

  // attach properties to the exports object to define
  // the exported module properties.
  exports.action = function () {};
});

Node 对 ES Modules 支持

Node verison 13.2.0 起开始正式支持 ES Modules 特性。
注:虽然移除了 --experimental-modules 启动参数,但是由于 ESM loader 还是实验性的,所以运行 ES Modules 代码依然会有警告:
(node:47324) ExperimentalWarning: The ESM module loader is experimental.

两种方式:

  • cjs文件默认使用 CJS-loader
  • mjs 文件默认使用 ESM-loader
  • js 文件则按照 package.json 中 type 决定(type === 'module' 为 ESM-loader)
function shouldUseESMLoader(mainPath) {
  /**
   * @type {string[]} userLoaders A list of custom loaders registered by the user
   * (or an empty list when none have been registered).
   */
  const userLoaders = getOptionValue('--experimental-loader');
  /**
   * @type {string[]} userImports A list of preloaded modules registered by the user
   * (or an empty list when none have been registered).
   */
  const userImports = getOptionValue('--import');
  if (userLoaders.length > 0 || userImports.length > 0)
    return true;
  // Determine the module format of the main
  if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs'))
    return true;
  if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs'))
    return false;
  const pkg = readPackageScope(mainPath);
  return pkg && pkg.data.type === 'module';
}

两种模块间的相互引用

CommonJS 和 ES Modules 都支持 Dynamic import(),它可以支持两种模块机制的导入。

在 CommonJS 文件中导入 ES Modules 模块

由于 ES Modules 的加载、解析和执行都是异步的,而 require() 的过程是同步的、所以不能通过 require() 来引用一个 ES6 模块。
ES6 提议的 import() 函数将会返回一个 Promise,它在 ES Modules 加载后标记完成。借助于此,我们可以在 CommonJS 中使用异步的方式导入 ES Modules:

// 使用 then() 来进行模块导入后的操作
import(“es6-modules.mjs”).then((module)=>{/*…*/}).catch((err)=>{/**…*/})
// 或者使用 async 函数
(async () => {
  await import('./es6-modules.mjs');
})();

在 ES Modules 文件中导入 CommonJS 模块

在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块,因为在 ES6 模块里异步加载并非是必须的:

import { default as cjs } from 'cjs';

// The following import statement is "syntax sugar" (equivalent but sweeter)
// for `{ default as cjsSugar }` in the above import statement:
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);

Node 判断模块系统是用 ESM 还是 CJS

Node.js 12.17.0 之后的版本,按以下流程判断模块系统是用 ESM 还是 CJS:
image.png
不满足以上判断条件的会以 CJS 兜底。如果你的工程遵循 CJS 规范,并不需要特殊的文件名后缀和设置package.json type字段等额外的处理。

参考:


specialcoder
2.2k 声望171 粉丝

前端 设计 摄影 文学