What is ES Module in Node.js?
ES Modules (ECMAScript Modules), often referred to as ESM, are the standardized module system for JavaScript. Node.js has progressively adopted ESM, offering a modern, standardized way to organize and reuse code, aligning its module system more closely with browsers.
What are ES Modules?
ES Modules use import and export statements to share code between files. This syntax is standardized across the JavaScript ecosystem, including browsers, and provides several benefits over Node.js's traditional CommonJS (CJS) module system, such as static analysis capabilities, better tree-shaking potential, and explicit dependency management.
Prior to ESM adoption, Node.js primarily used CommonJS with require() to import modules and module.exports or exports to expose them. While CommonJS is still widely used and supported, ESM represents the future of JavaScript module development.
Key Features and Benefits
- Standardized Syntax: Uses the official
importandexportsyntax, making code more portable between Node.js and browsers. - Static Analysis: Module dependencies can be determined at parse time, which enables tools to perform optimizations like tree-shaking (removing unused code).
- Asynchronous Loading: ESM supports top-level
await, allowing modules to resolve asynchronous dependencies at the top level without wrapping them in anasyncfunction. - Clearer Dependencies: Explicitly declares what a module exports and what it imports, leading to better-structured codebases.
- Scoped Variables: Each module has its own scope, preventing global variable pollution.
How to Use ES Modules in Node.js
Node.js offers two primary ways to signal that a file should be treated as an ES Module:
- Using the
.mjsextension: Files with a.mjsextension are always treated as ES Modules. - Using
"type": "module"inpackage.json: If yourpackage.jsonfile contains"type": "module", all.jsfiles within that package (unless explicitly overridden by"type": "commonjs"in a sub-package.json or a.cjsextension) will be treated as ES Modules.
Here's an example of an ES Module:
// utils.mjs or utils.js (if "type": "module" is set)
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
And how to import it:
// app.mjs or app.js (if "type": "module" is set)
import { add, PI } from './utils.js';
console.log(add(5, 3)); // Output: 8
console.log(PI); // Output: 3.14159
Interoperability with CommonJS
Node.js provides mechanisms for interoperability between ESM and CJS modules:
- ESM can import CJS: An ES Module can
importa CommonJS module. Thedefaultimport will be themodule.exportsobject from the CJS module. - CJS cannot
require()ESM (directly): A CommonJS module cannot directlyrequire()an ES Module. However, dynamicimport()can be used within a CJS module to load an ESM module asynchronously.
Key Differences and Considerations
- No
__filenameor__dirname: These CommonJS globals are not available in ESM. Useimport.meta.urlto get the current module's URL, and then use the Node.jsurlandpathmodules to construct file paths. - No
requireglobal: Therequirefunction is not available in ESM. Useimportfor modules andcreateRequirefrom themodulemodule if you absolutely need to dynamicallyrequirea CJS module from ESM. - JSON Modules: Direct
importof JSON files in ESM might not work as intuitively asrequire()in CJS; it often requires a specificimportassertion in newer Node.js versions or a custom loader. Consider importing them as text or usingfs.readFileandJSON.parse. - File Extensions: Always use full file extensions (e.g.,
.js,.mjs,.json) when importing modules in ESM. Node.js does not perform extension resolution or directory index resolution like it does for CommonJS.