npm-patterns
Package management discipline for any JavaScript/TypeScript project: dependency declaration, semver, scripts conventions, lockfile hygiene, package-manager detection (npm/yarn/pnpm). Stack-agnostic —
SKILL.mdplugins/js-foundation/skills/npm-patterns/SKILL.md
GitHubnpm / yarn / pnpm Patterns (stack-agnostic)
This skill consolidates package management idioms applicable to any JS/TS project — backend, frontend, mobile, library. Apply when modifying package.json, adding dependencies, or defining scripts.
Detect the package manager
Look at the lockfile in the project root:
| Lockfile present | Use |
|---|---|
package-lock.json |
npm |
yarn.lock |
yarn |
pnpm-lock.yaml |
pnpm |
bun.lockb / bun.lock |
bun (rare; mostly drop-in for npm) |
| (none) | npm — but flag in DECISIONS that no lockfile is committed |
Also check package.json "packageManager" field (e.g. "packageManager": "pnpm@8.6.0") — when present, it overrides lockfile inference.
Map common commands:
| Action | npm | yarn | pnpm |
|---|---|---|---|
| Install all | npm install |
yarn install |
pnpm install |
| Add runtime dep | npm install pkg |
yarn add pkg |
pnpm add pkg |
| Add dev dep | npm install -D pkg |
yarn add -D pkg |
pnpm add -D pkg |
| Run script | npm run x |
yarn x |
pnpm x |
| Update lockfile | npm install |
yarn install |
pnpm install |
Always run the install command after editing package.json — never edit the lockfile by hand.
Dependency fields
| Field | Use for |
|---|---|
dependencies |
Runtime — what the production app needs to run (express, react, pinia, axios). |
devDependencies |
Dev tooling — bundlers, test runners, linters, type definitions, dev servers. |
peerDependencies |
Library authors only — declares what host app must provide (e.g., react for a React component library). |
optionalDependencies |
Rare — install failure should not break install (native binaries with fallbacks). |
bundledDependencies |
Rare — packaged inside npm pack tarball. |
Rule of thumb: if the code is bundled into the production output for runtime use, it's a dependency. If it only runs during npm test / npm run build / lint, it's a devDependency.
Frontend nuance: bundlers (Vite/Webpack/esbuild) often tree-shake unused exports — but the package still belongs in dependencies if any production-rendered code imports it.
Semver discipline
| Range | Meaning | When to use |
|---|---|---|
^1.2.3 |
>=1.2.3 <2.0.0 (no breaking changes) | Default — most deps |
~1.2.3 |
>=1.2.3 <1.3.0 (only patch) | Conservative — production-critical |
1.2.3 |
Exact | When you've debugged a specific version (rare) |
>=1.2.3 |
Any version >=1.2.3 | Almost never — too loose |
* or latest |
Anything | NEVER. Reproducible builds matter. |
git+https://... |
Git ref | Only as last resort; pin to commit SHA. |
Default: ^x.y.z. Lockfile pins exact versions for reproducibility — that's what makes ^ safe.
scripts conventions
Common script names (be consistent — these are de-facto standard):
| Script | Purpose |
|---|---|
start |
Production entry: node dist/index.js (Node) or static server (frontend) |
dev |
Dev entry: nodemon / tsx watch / vite / next dev / ng serve |
build |
Compile/bundle: tsc, vite build, next build, ng build, esbuild, webpack |
test |
Test runner: jest, vitest, mocha, ng test |
test:watch |
Watch-mode tests |
test:coverage |
Coverage report |
test:e2e |
E2E tests (Playwright/Cypress/Detox) |
lint |
eslint . |
lint:fix |
eslint . --fix |
format |
prettier --write . |
format:check |
prettier --check . |
typecheck |
tsc --noEmit (or vue-tsc --noEmit for Vue) |
clean |
Remove dist/, coverage/, .next/, out/ |
prepare |
Auto-runs after install (husky setup, etc.) |
Don't invent new names without a reason. CI configs and other tools assume these.
Lockfile policy
- Always commit the lockfile to git.
- Never edit by hand. Always regenerate via the package manager.
- Match the package manager: don't commit
yarn.lockandpackage-lock.jsonsimultaneously. - Resolve conflicts by accepting one side, then running
<pm> installto regenerate. - For library packages published to npm, lockfile is committed but not published (it's in
.npmignoreby default).
engines field
Pin Node version when it matters (e.g., uses node:test requires ≥18, or your bundler requires ≥20):
{
"engines": {
"node": ">=18.0.0"
}
}
Add engines.npm only if you need a specific npm major version.
package.json type field
"type": "module"— files default to ESM. Useimport/export. CJS files must use.cjsextension.- Absent or
"type": "commonjs"— files default to CJS. Userequire/module.exports. ESM files must use.mjsextension.
Don't change this unless the BA spec explicitly asks; switching modes mid-project is invasive (especially for backend Node projects).
Frontend bundler-driven projects (Vite/Webpack/Next/Angular CLI) typically use "type": "module" regardless — bundler handles output.
Workspaces (monorepos)
For monorepos:
// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"]
}
- pnpm: uses
pnpm-workspace.yamlinstead. - yarn: same
workspacesfield; berry vs classic differ in detail. - Monorepo runners (Nx, Turborepo, Lerna): override default install/build/test orchestration. Match what's installed.
Per-app package.json files declare app-specific deps; root-only deps go in root.
Dependency hygiene
- Run
npm audit(or equivalent) when adding deps; fix Critical/High before committing. - Avoid
--forceresolution unless you know what you're overriding and document it. - Don't add deps for trivial utilities you can write in 5 lines (e.g., is-odd, padleft) — supply chain risk.
- Watch for typo-squatting:
lodashvslodahs,chalkvschal-k. - For frontend: bundle size matters — measure before adding heavy deps (use
bundlephobia.comas a quick gauge).
Anti-patterns
- ❌
npm install --forcewithout justification. - ❌ Editing
package-lock.json/yarn.lock/pnpm-lock.yamlby hand. - ❌ Using
*orlatestas a version range. - ❌ Adding the same package to both
dependenciesanddevDependencies. - ❌ Committing
node_modules/. - ❌ Mixing package managers (lockfile from one, install via another).
- ❌ Adding
engines.nodewithout testing on the lower bound. - ❌ For frontend: putting build-time tools in
dependencies(bloatsnpm cifor production-only installs).