What is it?
ES modules (ESM) are the standard way JavaScript files reference each
other. You export values from one file and import them into another.
The browser, Node, Deno, and Bun all run ESM natively — bundlers (Vite,
esbuild, Turbopack) just stitch many modules into fewer files for
production.
Why it matters
Modules shape how code is organized and how much ships to the user. Knowing the difference between a default and named export, or how tree-shaking works, is the difference between a 50KB bundle and a 500KB one. Hiring teams check for this in pull-request comments.
What to learn
- Named exports vs default exports — and when each is right
import { x } from './foo'vsimport x from './foo'- Side-effect imports:
import './styles.css' - Re-exports:
export * from './module' - Static vs dynamic imports (
import()returns a promise) - Tree-shaking: how bundlers drop unused exports
Common pitfall
Default-exporting everything. A default export is renameable on import,
which means consumers can call your Button Btn in their file and the
codebase loses searchability. Named exports are searchable, refactorable,
and play better with tooling.
Resources
Primary (free):
- MDN — JavaScript modules · docs
- JavaScript.info — Modules · docs
- web.dev — Module workers · article
Practice
Take a single-file vanilla JS project (50+ lines). Split it into modules
by responsibility — utilities, DOM helpers, the main entry. Use named
exports. Check the bundle size with vite build before and after. Done
when the file structure tells someone what each piece does.
Outcomes
- Choose named over default exports by default, with a real reason.
- Split a single-file project into modules without circular imports.
- Use dynamic
import()to lazy-load a non-critical chunk. - Check whether tree-shaking actually dropped a function (build size diff).