Navigate ESM and CommonJS, interop between them, and structure imports so bundlers can tree-shake. Use when hitting module-resolution errors, mixing ESM and CJS, or shrinking a bundle.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Amey-Thakur/AI-SKILLS --skill js-modules --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Js Modules?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/amey-thakur-js-modules)More formats (shields.io, HTML) on the badges page.
---
name: js-modules
description: Navigate ESM and CommonJS, interop between them, and structure imports so bundlers can tree-shake. Use when hitting module-resolution errors, mixing ESM and CJS, or shrinking a bundle.
---
# JS modules
The JavaScript module story is two systems (ESM and CommonJS) that mostly
interoperate and occasionally do not, and most "cannot use import outside
a module" and "require is not defined" errors come from a mismatch nobody
configured on purpose. Get the model straight and the errors resolve.
## Method
1. **Default to ESM (`import`/`export`).** It is the standard, statically
analyzable (which enables tree-shaking), and what the ecosystem is
converging on. Signal it with `"type": "module"` in package.json (or
`.mjs` files). Reserve CommonJS (`require`/`module.exports`) for
legacy code and tools that still need it.
2. **Understand the interop asymmetry.** ESM can import CommonJS (the
`module.exports` becomes the default import), but CommonJS cannot
`require` an ESM module synchronously (it needs dynamic `import()`).
This one-way friction is behind most dual-system pain; know which
direction you are crossing (see tsconfig-mastery for making the
compiler agree).
3. **Match module settings across the toolchain.** package.json `type`,
tsconfig `module`/`moduleResolution`, and the bundler must agree. A
`.ts` file compiled as CJS but run in an ESM package, or `import`s
without extensions under `NodeNext`, produce the classic resolution
errors. Set them once, consistently, for your runtime.
4. **Structure imports for tree-shaking.** Import named exports from
modules that have no side effects at import time, and mark your package
`"sideEffects": false` (or list the files that do) so bundlers can drop
unused code (see bundle-size). Deep-import from large libraries
(`import x from "lib/x"`) or use ones with proper ESM exports; a
barrel file that re-exports everything can defeat tree-shaking and slow
cold starts.
5. **Use dynamic `import()` for code-splitting and lazy loading.**
`await import("./heavy")` loads a module on demand, splitting it into
its own chunk: the mechanism behind route-level and feature-level lazy
loading (see lazy-loading, bundle-size). It also bridges CJS
to ESM where needed.
6. **Publish libraries dual-format deliberately.** If others consume your
package, provide both ESM and CJS builds via the `exports` map
(conditional `import`/`require` entries) so both kinds of consumer
work, and pin the types entry (see npm-publishing). Getting the
`exports` map wrong is the top reason a published package "cannot be
imported".
## Boundaries
- Module format is a build and runtime concern; it does not change your
code's logic, only whether the toolchain can resolve and optimize it.
- Barrel files (`index.ts` re-exporting a directory) trade import
convenience for bundler and cold-start cost; use them at true public
API boundaries, not for every internal folder (see
python-project-structure's analogous `__init__` rule).
- The ESM/CJS transition is ongoing; some dependencies are ESM-only or
CJS-only, occasionally forcing dynamic import or a build step: check a
dependency's format before adopting it in a mismatched project.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!