Handbooks / Node.js / Chapter 1
Modules and npm
7 pages · ~5 min✓ Reviewed
Start here. Next up: The Node.js Event Loop.
CommonJS vs ES Modules
1. Loading model
| CommonJS | ESM | |
|---|---|---|
| Syntax | require() / module.exports | import / export |
| Loading | sync, whole file runs at the call, cached in require.cache | parsed first, async |
| Position | anywhere, computed path | hoisted, literal string |
- Conditional load:
import('./x.js')returns a Promise
2. Which system per file
.mjsalways ESM,.cjsalways CJS.js+"type": "module"in nearest package.json = ESM.jsdefault ("commonjs") = CJS
3. Example and gotchas
js
const fs = require('fs'); module.exports = { run };
CJS
js
import fs from 'fs'; export const run = () => {};
ESM
- Relative imports need the extension:
./util.js - No
__dirnameorrequire: useimport.meta.urlorimport.meta.dirname
package.json Essentials
1. Identity and Entry
name+versionrequired to publish; name lowercase, no spacesprivate: trueblocks accidentalnpm publishmain: the CJS entry file"type": "module": .js files become ESMexports: maps subpaths andimport/require; unlisted deep imports blocked; beatsmain
2. Starter File
npm init -y= defaults;npm initasks questions
json
{
"name": "app",
"version": "1.0.0",
"type": "module",
"scripts": { "start": "node index.js" }
}3. Mistakes
- Trailing comma or comment: strict JSON breaks
Cannot use import statement outside a module: older Node or"type": "commonjs"- Node 22.7+ runs typeless ESM with a warning; still add
"type": "module" - Hand edit without
npm install: lockfile out of sync
4. Interview Point
- main/module/exports?
mainCJS entry;modulebundler-only hint;exportswins "type": "module"? .js parsed as ESM- private: true? stops accidental publish of an app
dependencies vs devDependencies
1. Runtime vs Build
| dependencies | devDependencies | |
|---|---|---|
| Needed for | runtime in production (express, react) | build, test, lint (jest, eslint, typescript, vite) |
| Install | npm i pkg | npm i -D pkg |
2. What a Production Install Skips
npm install --omit=devskips devDependencies- Older flag:
--production;NODE_ENV=productiondoes the same - Installing your library gets its dependencies only, never its devDependencies
3. Other Dependency Kinds
| Field | Meaning |
|---|---|
peerDependencies | the host app must supply it (a React plugin needs react); auto-installed since npm 7 |
optionalDependencies | may fail to install without breaking the install |
dependencies: what you importpeerDependencies: what you share with the host
4. Mistakes
typescriptin devDependencies but compiled on deploy: the production install omits it, the build fails- Test libraries in dependencies: bloated images and installs
Semver and Lockfiles
1. Semver and Ranges
- MAJOR.MINOR.PATCH, e.g.
2.4.1 - PATCH: bug fix
- MINOR: backward-compatible feature
- MAJOR: breaking change
| Range | Allows |
|---|---|
^1.2.3 | 1.x.x at or above 1.2.3 (default) |
~1.2.3 | 1.2.x only |
1.2.3 | exact |
^0.2.3 | 0.2.x only: 0.x may break |
2. The Lockfile
package-lock.jsonpins exact version, tarball URL, integrity hash- Covers the whole tree, incl. transitive deps
- Commit it
| Command | Behaviour |
|---|---|
npm install | respects lock, may update it |
npm ci | strictly from lock, wipes node_modules, fails on mismatch: use in CI |
3. Mistakes
- Deleting the lockfile or committing two (npm + yarn): each machine gets other versions
- Never hand-edit the lockfile
npm outdatedshows what can changenpm updatemoves within ranges
npm Scripts and Everyday Commands
1. Scripts
npm run <name>runs a script; test, start,stopskiprun- node_modules/.bin is on the PATH during a script
- So
"lint": "eslint ."needs no global install
2. Hooks and Arguments
| Hook | Runs |
|---|---|
pretest, postbuild | automatically before / after that script |
prepare | after local npm install, before publish |
prepareis the place to build or install git hooks- Everything after -- is forwarded to the script
&&chains steps: each must succeed (tsc && node dist)npx <tool>runs a binary without a permanent install
bash
npm run test -- --watch
npx eslint .-- forwards --watch to the test script
3. Everyday Commands
| Command | Does |
|---|---|
npm install | install everything |
npm i pkg | add a dependency |
npm i -D pkg | add a devDependency |
npm uninstall pkg | remove it |
npm ls | show the tree |
npm outdated | show newer versions |
npm update | update within ranges |
- Global
-gtools cause version drift rm -rfin scripts breaks Windows teammates- Forgetting
--before forwarded flags
Cheat Sheet: Modules and npm
1. Modules
| CJS | ESM | |
|---|---|---|
| Syntax | require, module.exports | import, export |
| Load | sync, dynamic | static, async |
| Needs | default for .js | "type": "module" or .mjs |
| Relative path | no extension | extension required |
- CJS loads ESM:
import(), orrequire()on Node 22.12+/20.19+ (no top-level await) - ESM loads CJS: default import
2. npm
| Field | Holds |
|---|---|
| dependencies | runtime |
| devDependencies | build, test (-D) |
| peerDependencies | host app supplies it |
npm i --omit=devskips dev deps^same major (default),~same minor, none = exact^0.2.3= 0.2.x onlynpm run <name>,test/startshortcut,pre/posthooks, args after--- .bin on PATH;
npxruns one-off tools
Lockfile
Commit it, never hand-edit. npm ci in CI, npm install locally.
3. Fix-it list
Cannot use import statement: add"type": "module"or.mjs(older Node)require/__dirnamenot defined: in ESM, useimport(),import.metaERR_MODULE_NOT_FOUND: missing file extensionnpm cilock mismatch:npm install, commit lock
Check yourself
A file app.mjs runs console.log(__dirname). What happens, and what replaces it?
ReferenceError: ESM has no__dirname.- Use
import.meta.dirname, or derive it fromimport.meta.url.
package.json says ^0.2.3. The registry has 0.2.9, 0.3.0 and 1.0.0. Which version installs?
0.2.9.- Below 1.0.0, ^ pins the minor, because a minor bump may break.
A Dockerfile runs npm ci --omit=dev, then npm run build, which calls tsc. typescript is a devDependency. What breaks?
tscwas never installed, so the build fails.- Build before omitting dev packages, or use a multi-stage build.