ES modules are the official standard format for JavaScript modules. They use import and export statements.
Introduction
ECMAScript modules (ESM) are the standard for packaging JavaScript code for reuse. They are supported natively in Node.js and modern browsers.
Basic Example
// math.mjs
export function add ( a , b ) {
return a + b ;
}
export const PI = 3.14159 ;
// app.mjs
import { add , PI } from './math.mjs' ;
console . log ( add ( 2 , 3 )); // 5
console . log ( PI ); // 3.14159
Enabling ES Modules
Node.js treats files as ES modules when:
Files have .mjs extension
Files have .js extension with "type": "module" in nearest package.json
String input with --input-type=module flag
Using .mjs Extension
// module.mjs - treated as ES module
import fs from 'node:fs' ;
Using package.json
// package.json
{
"type" : "module"
}
// module.js - treated as ES module when package.json has "type": "module"
import fs from 'node:fs' ;
Import Syntax
Named Imports
import { readFile , writeFile } from 'node:fs/promises' ;
import { join } from 'node:path' ;
Default Import
import fs from 'node:fs' ;
import express from 'express' ;
Namespace Import
import * as fs from 'node:fs' ;
import * as path from 'node:path' ;
console . log ( fs . readFileSync );
console . log ( path . join );
Dynamic Import
// Works in both CommonJS and ES modules
const module = await import ( './my-module.mjs' );
// Conditional loading
if ( condition ) {
const { feature } = await import ( './feature.mjs' );
}
Export Syntax
Named Exports
export const name = 'John' ;
export function greet () {
return 'Hello!' ;
}
export class User {}
Default Export
export default class Application {
constructor () {
this . name = 'MyApp' ;
}
}
Export List
const a = 1 ;
const b = 2 ;
function c () {}
export { a , b , c };
Re-exporting
export { something } from './other.mjs' ;
export * from './utilities.mjs' ;
Import Specifiers
Relative Specifiers
Must include file extension:
import './startup.js' ;
import config from './config.json' with { type : 'json' };
import utils from '../lib/utils.mjs' ;
Bare Specifiers
Package names or built-in modules:
import express from 'express' ;
import { EventEmitter } from 'node:events' ;
Absolute Specifiers
import config from 'file:///opt/app/config.js' ;
Import Attributes
Provide metadata about imports:
JSON Modules
import data from './data.json' with { type : 'json' };
console . log ( data );
The with { type: 'json' } attribute is required for importing JSON files.
Metadata about the current module:
console . log ( import . meta . url );
// file:///home/user/project/module.mjs
console . log ( import . meta . filename );
// /home/user/project/module.mjs
console . log ( import . meta . dirname );
// /home/user/project
Resolve module specifiers:
const resolved = import . meta . resolve ( './lib/feature.js' );
console . log ( resolved );
// file:///home/user/project/lib/feature.js
const pkgPath = import . meta . resolve ( 'express' );
// file:///home/user/project/node_modules/express/index.js
import.meta.main
Check if module is the entry point:
export function greet () {
console . log ( 'Hello!' );
}
if ( import . meta . main ) {
// Run only when this is the entry point
greet ();
}
Loading CommonJS from ES Modules
Import CommonJS
// CommonJS module: circle.cjs
exports . area = ( r ) => Math . PI * r ** 2 ;
// ES module
import pkg from './circle.cjs' ;
console . log ( pkg . area ( 4 ));
// Named imports (if detected)
import { area } from './circle.cjs' ;
console . log ( area ( 4 ));
Top-level await
Use await at the top level of ES modules:
const response = await fetch ( 'https://api.example.com/data' );
const data = await response . json ();
export { data };
import { data } from './fetchData.mjs' ;
console . log ( data ); // Already resolved
Built-in Modules
Import Node.js built-in modules:
import fs from 'node:fs/promises' ;
import path from 'node:path' ;
import { EventEmitter } from 'node:events' ;
const content = await fs . readFile ( './file.txt' , 'utf-8' );
Default and Named Exports
// Default export
import fs from 'node:fs' ;
// Named exports
import { readFile } from 'node:fs/promises' ;
// Both
import fs , { readFileSync } from 'node:fs' ;
Data URLs
Import from data URLs:
import 'data:text/javascript,console.log("hello");' ;
import data from 'data:application/json,"world"' with { type : 'json' };
console . log ( data ); // "world"
WebAssembly Modules
Import WebAssembly modules:
// Import WebAssembly module instance
import * as wasm from './module.wasm' ;
wasm . exportedFunction ();
// Source phase import (get the WebAssembly.Module)
import source wasmModule from './module.wasm' ;
const instance = await WebAssembly . instantiate ( wasmModule , imports );
Differences from CommonJS
No require()
Use import instead of require():
// CommonJS
const fs = require ( 'fs' );
// ES modules
import fs from 'node:fs' ;
// Or create require() if needed
import { createRequire } from 'node:module' ;
const require = createRequire ( import . meta . url );
const pkg = require ( './package.json' );
No __dirname or __filename
Use import.meta:
// CommonJS
console . log ( __dirname );
console . log ( __filename );
// ES modules
console . log ( import . meta . dirname );
console . log ( import . meta . filename );
No module.exports
Use export statements:
// CommonJS
module . exports = { hello: 'world' };
// ES modules
export default { hello: 'world' } ;
// or
export const hello = 'world' ;
Module Resolution
File Extensions Required
// ❌ This won't work
import utils from './utils' ;
// ✅ This works
import utils from './utils.js' ;
No Directory Indexes
// ❌ This won't work
import app from './lib' ;
// ✅ Must specify the full path
import app from './lib/index.js' ;
Package Exports
Use package.json exports field:
{
"name" : "my-package" ,
"exports" : {
"." : "./index.js" ,
"./utils" : "./lib/utils.js"
}
}
import pkg from 'my-package' ;
import { helper } from 'my-package/utils' ;
Conditional Exports
Provide different entry points based on environment:
{
"exports" : {
"import" : "./index.mjs" ,
"require" : "./index.cjs"
}
}
Customization Hooks
Customize module loading with hooks:
import { registerHooks } from 'node:module' ;
registerHooks ({
resolve ( specifier , context , nextResolve ) {
// Custom resolution logic
return nextResolve ( specifier , context );
},
load ( url , context , nextLoad ) {
// Custom loading logic
return nextLoad ( url , context );
}
});
CommonJS Modules Legacy CommonJS module system with require()
node:module API Module utilities and customization hooks
Packages Package.json configuration and exports