參考: 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:
不滿足以上判斷條件的會以 CJS 兜底。如果你的工程遵循 CJS 規範,並不需要特殊的文件名後綴和設置package.json type字段等額外的處理。
參考:
- JavaScript 模塊的循環加載
- [ESM和CJS模塊雜談
](https://juejin.cn/post/7048276970768957477)