ECMAScript Modules (ESM) 是 JavaScript 的一种模块化规范,自 Node.js 12 版本开始引入支持。与传统的 require()
功能不同,它使用 import
关键字来导入模块,不允许直接通过 require()
导入 .mjs
扩展的 ESM 文件,因为这些文件是为了利用现代 JavaScript 的模块导出和加载方式设计的。
动态导入 (dynamic import()
) 可以在 CommonJS 和 ESM 中使用。这意味着即使在编写传统的 CommonJS 模块时,也可以通过 import()
动态加载 ES 模块,增加了代码的灵活性。
举个例子:
// 使用 dynamic import
async function loadModule() {
try {
const importedModule = await import('./my-esm-file.mjs');
// 使用导入的模块
console.log(await importedModule.default());
} catch (error) {
console.error('Failed to load ESM module:', error);
}
}
loadModule();
在这个例子中,我们不能直接 require('./my-esm-file.mjs')
,而是使用 import
并等待其异步加载完成。
要在Node.js中启用对ESM (ECMAScript 模块) 的支持,你可以考虑以下几个步骤:
-
更新Node.js版本:确保你使用的Node.js版本至少是14或更高,因为这些版本开始支持在顶层作用域中使用
await
语句。nvm install node --lts # 如果你使用nvm管理node版本 npm i -g node # 或者全局安装最新版本
-
确认package.json配置:检查你的
package.json
文件中的type
字段,如果它是"module"
,说明你的项目已经设置了以ESM模式运行。确保main
字段指向正确的CommonJS入口点,以便Node.js能够识别并执行它。{ "type": "commonjs", // 或者删除 type 字段,默认为 commonjs "main": "index.js" }
-
逐步引入ESM:如果你的代码中存在顶层的
await import()
,尝试将其移动到模块内部,或者创建一个专门用于导入外部库的函数。// 原始错误代码(不推荐) await import('source-map-support'); // 更改为: if (process.env.NODE_ENV !== 'production') { const sourceMapSupport = await import('source-map-support'); sourceMapSupport.install(); }
-
使用
--experimental-modules
标志:在早期版本的Node.js中,可以通过命令行参数临时启用ESM支持:node --experimental-modules yourfile.js
请注意,尽管上述步骤可以解决问题,但最好还是升级到Node.js 14以上,因为它已经完全支持ESM作为标准特性。长期来看,保持项目的ESM兼容性是最佳实践。
Index View on single page View as JSON View another version ▼
Edit on GitHub
Table of Contents
ECMAScript Modules
Introduction
Enabling
package.json "type" field
Package Scope and File Extensions
--input-type flag
Packages
Package Entry Points
Main Entry Point Export
Subpath Exports
Package Exports Fallbacks
Exports Sugar
Conditional Exports
Nested conditions
Self-referencing a package using its name
Dual CommonJS/ES Module Packages
Dual Package Hazard
Writing Dual Packages While Avoiding or Minimizing Hazards
Approach #1: Use an ES Module Wrapper
Approach #2: Isolate State
import Specifiers
Terminology
data: Imports
import.meta
Differences Between ES Modules and CommonJS
Mandatory file extensions
No NODE_PATH
No require, exports, module.exports, __filename, __dirname
No require.resolve
No require.extensions
No require.cache
URL-based paths
Interoperability with CommonJS
require
import statements
import() expressions
CommonJS, JSON, and Native Modules
Builtin modules
Experimental JSON Modules
Experimental Wasm Modules
Experimental Loaders
Hooks
resolve hook
getFormat hook
getSource hook
transformSource hook
getGlobalPreloadCode hook
dynamicInstantiate hook
Examples
HTTPS loader
Transpiler loader
Resolution Algorithm
Features
Resolver Algorithm
Customizing ESM specifier resolution algorithm
ECMAScript Modules
Stability: 1 - Experimental
Introduction
ECMAScript modules are the official standard format to package JavaScript code for reuse. Modules are defined using a variety of import and export statements.
Node.js fully supports ECMAScript modules as they are currently specified and provides limited interoperability between them and the existing module format, CommonJS.
Node.js contains support for ES Modules based upon the Node.js EP for ES Modules and the ECMAScript-modules implementation.
Expect major changes in the implementation including interoperability support, specifier resolution, and default behavior.
Enabling
The --experimental-modules flag can be used to enable support for ECMAScript modules (ES modules).
Once enabled, Node.js will treat the following as ES modules when passed to node as the initial input, or when referenced by import statements within ES module code:
Files ending in .mjs.
Files ending in .js when the nearest parent package.json file contains a top-level field "type" with a value of "module".
Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=module.
Node.js will treat as CommonJS all other forms of input, such as .js files where the nearest parent package.json file contains no top-level “type” field, or string input without the flag --input-type. This behavior is to preserve backward compatibility. However, now that Node.js supports both CommonJS and ES modules, it is best to be explicit whenever possible. Node.js will treat the following as CommonJS when passed to node as the initial input, or when referenced by import statements within ES module code:
Files ending in .cjs.
Files ending in .js when the nearest parent package.json file contains a top-level field "type" with a value of "commonjs".
Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=commonjs.
package.json “type” field
Files ending with .js will be loaded as ES modules when the nearest parent package.json file contains a top-level field “type” with a value of “module”.
The nearest parent package.json is defined as the first package.json found when searching in the current folder, that folder’s parent, and so on up until the root of the volume is reached.
// package.json
{
“type”: “module”
}
In same folder as above package.json
node --experimental-modules my-app.js # Runs as ES module
If the nearest parent package.json lacks a “type” field, or contains “type”: “commonjs”, .js files are treated as CommonJS. If the volume root is reached and no package.json is found, Node.js defers to the default, a package.json with no “type” field.
import statements of .js files are treated as ES modules if the nearest parent package.json contains “type”: “module”.
// my-app.js, part of the same example as above
import ‘./startup.js’; // Loaded as ES module because of package.json
Package authors should include the “type” field, even in packages where all sources are CommonJS. Being explicit about the type of the package will future-proof the package in case the default type of Node.js ever changes, and it will also make things easier for build tools and loaders to determine how the files in the package should be interpreted.
Regardless of the value of the “type” field, .mjs files are always treated as ES modules and .cjs files are always treated as CommonJS.
Package Scope and File Extensions
A folder containing a package.json file, and all subfolders below that folder down until the next folder containing another package.json, is considered a package scope. The “type” field defines how .js files should be treated within a particular package.json file’s package scope. Every package in a project’s node_modules folder contains its own package.json file, so each project’s dependencies have their own package scopes. A package.json lacking a “type” field is treated as if it contained “type”: “commonjs”.
The package scope applies not only to initial entry points (node --experimental-modules my-app.js) but also to files referenced by import statements and import() expressions.
// my-app.js, in an ES module package scope because there is a package.json
// file in the same folder with “type”: “module”.
import ‘./startup/init.js’;
// Loaded as ES module since ./startup contains no package.json file,
// and therefore inherits the ES module package scope from one