JavaScript - Modules (ESM)
Overview
Estimated time: 35–45 minutes
ES Modules (ESM) provide a standard way to organize code across files with explicit imports/exports. Learn named vs default exports, namespace imports, re-exports, dynamic import()
, and key differences in browsers vs Node.js.
Learning Objectives
- Export and import values using named and default exports.
- Use namespace imports and re-export from other modules.
- Load code dynamically with
import()
and understand module scope. - Be aware of browser vs Node.js ESM differences and common pitfalls.
Prerequisites
- JavaScript - async/await
- JavaScript - Promises (recommended)
Exports and imports
// math.js (module)
export const add = (a, b) => a + b;
export function mul(a, b) { return a * b; }
const PI = 3.14159;
export default PI; // default export
// app.js (module)
import PI, { add, mul } from './math.js';
console.log(add(2, 3)); // 5
console.log(mul(2, 3)); // 6
console.log(PI); // 3.14159
Namespace import and re-exports
// Namespace import
import * as math from './math.js';
math.add(1,2); math.mul(2,3); math.default; // default via .default
// Re-export
export { add, mul } from './math.js'; // re-export named
export { default as PI } from './math.js'; // re-export default under a name
Dynamic import()
import()
loads modules at runtime and returns a promise, enabling conditional and on-demand loading.
async function loadCalc() {
const mod = await import('./math.js');
console.log(mod.add(1,2));
}
Module scope and top-level await
- Each module has its own scope; variables are not global unless explicitly attached.
- Modules are singletons per URL: importing the same URL returns the same module instance.
- Top-level
await
is allowed in ESM; dependent modules wait for it to resolve.
Browser vs Node.js notes
- Browsers require valid module MIME type and often explicit file extensions in import paths.
- Node.js enables ESM via
"type": "module"
inpackage.json
or using.mjs
files. Resolution differs from browsers. - CommonJS interop: default import from a CJS module may yield the exports object; prefer
import pkg from 'cjs'
then usepkg.default || pkg
as needed. - Import maps can help resolve bare specifiers in browsers.
Common Pitfalls
- Extension and path: In browsers, use
./
and include.js
(e.g.,import './utils.js'
). - Mixed systems: Avoid mixing CommonJS
require()
with ESMimport
in the same file. - CORS/MIME: Cross-origin modules require CORS; servers must serve JS as
text/javascript
.
Try it
Run to create a module on-the-fly via Blob and import it dynamically: