Reading — step 1 of 7
Learn
Modern JavaScript code is split into modules — small files that explicitly export and import. ES Modules (ESM) are the official format, supported in browsers, Node.js, Deno, Bun.
Named exports
Export individual values:
Import with the same name(s):
Default exports
For modules that have one main thing to export:
Import with any name you like:
A module can have ONE default export AND multiple named exports:
Namespace import
Import everything as one object:
Useful when you want to be explicit about which module a function comes from.
Re-exports
A module can re-expose another module's exports:
Lets consumers import { add, Logger } from './index.js' instead of importing from many files.
Dynamic imports
Load a module at runtime:
Returns a Promise of the module namespace. Useful for code splitting (only load what's needed when it's needed) and conditional imports.
How modules differ from old scripts
- Always strict mode —
'use strict'is implicit - Top-level scope is the module, not global — variables don't leak
thisat top level isundefined, not the global object- Imports are hoisted —
importstatements are processed before any code runs, regardless of where they appear in the file - Imports are bindings, not copies — when the source updates a value, importers see the update
- Cyclic imports are handled — at the cost of some complexity
Browser vs Node ESM
Browser: loaded via <script type="module" src="app.js"></script>. Always relative paths or full URLs. Extension required.
Node: activated by "type": "module" in package.json, or .mjs extension. Most file paths need extensions (.js).
Common mistakes
- Mixing ESM and CommonJS — Node treats
.jsas CommonJS unless package.json says"type": "module". Mismatches cause cryptic errors. - Forgetting the file extension — Node ESM requires
.js(not just./util). - Trying to mutate imported bindings — read-only. Export functions/objects to allow mutation.
- Treating import * as math as a deep-copyable object — it's a special namespace object; can't be JSON-serialized or freely modified.
- Default + named together — the default binding comes first, then the braces:
import myDefault, { named } from '...'. (defaultitself is a keyword, not a usable name — you always give the default export a local name of your own.)
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…