Learn how ES Modules work in real-world environments. Master module loading in browsers, configure Node.js for ESM, and understand the practical differences between platforms.
To use ES Modules in browsers, add the type="module"
attribute to your script tag.
<!-- Basic module script -->
<script type="module" src="./main.js"></script>
<!-- Inline module code -->
<script type="module">
import { greet } from './utils.js';
greet('World');
</script>
Key Behaviors:
defer
attribute)await
at top levelDifferent types of module paths you can use:
// Relative paths (must include extension)
import { utils } from './utils.js';
import { helper } from '../helpers/helper.js';
// Absolute paths
import { api } from '/src/api/client.js';
// URLs (full path)
import { library } from 'https://cdn.example.com/lib.js';
ES Modules are subject to CORS policy. You cannot load modules from file://
protocol.
❌ Won't Work:
Opening index.html
directly in browser (file:// protocol)
✅ Use a Development Server:
# Using Python
python -m http.server 8000
# Using Node.js
npx serve .
# Using VS Code
# Install "Live Server" extension
Feature | Module Script | Classic Script |
---|---|---|
Strict Mode | Always | Optional |
Defer Loading | Default | Optional |
Scope | Module scope | Global scope |
Top-level await | Yes | No |
CORS | Required | Not required |
Node.js supports ES Modules since version 12. Two methods to enable:
Method 1: Use .mjs Extension
// utils.mjs
export function add(a, b) {
return a + b;
}
// app.mjs
import { add } from './utils.mjs';
console.log(add(2, 3));
Method 2: Set "type": "module" in package.json
{
"name": "my-app",
"version": "1.0.0",
"type": "module"
}
All .js files will be treated as ES Modules
These CommonJS globals aren't available in ESM. Use import.meta.url
instead:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log('Current file:', __filename);
console.log('Current directory:', __dirname);
Node.js built-in modules can be imported with or without node:
prefix:
// Both work, but node: prefix is recommended
import fs from 'node:fs';
import path from 'node:path';
import { readFile } from 'node:fs/promises';
// Also valid (without prefix)
import fs from 'fs';
import path from 'path';
Import JSON files with import assertions:
// Node.js 17.1+
import config from './config.json' assert { type: 'json' };
// Alternative: use fs
import { readFile } from 'node:fs/promises';
const config = JSON.parse(
await readFile('./config.json', 'utf-8')
);
Define different entry points for ESM and CommonJS in package.json:
{
"name": "my-library",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs"
}
}
}