cheat sheet
package.json Reference
Complete reference for the Node.js package manifest — name, version, scripts, dependencies, exports, workspaces, and the packageManager field for Corepack.
package.json Reference
What it is
package.json is the manifest file for every Node.js project and npm package. It declares:
- Package identity (
name,version) - Entry points (
main,module,exports,bin) - Module system (
type) - Lifecycle and custom scripts (
scripts) - Runtime and development dependencies
- Publishing metadata (
keywords,license,repository) - Monorepo membership (
workspaces)
Every npm install, pnpm add, yarn add, and node --require decision ultimately flows through this file.
Minimal example
{
"name": "my-app",
"version": "1.0.0",
"description": "A minimal Node.js app",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "node --test"
},
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"typescript": "^5.5.2"
},
"license": "MIT"
}
Essential identity fields
{
"name": "@my-org/my-lib", // scoped package name (lowercase, URL-safe)
"version": "2.3.1", // semver: MAJOR.MINOR.PATCH
"description": "One sentence describing the package",
"keywords": ["http", "framework", "server"],
"license": "MIT",
"private": true // prevents accidental npm publish
}
namemust be unique on npm if you intend to publish. Scoped names (@scope/name) group related packages.versionfollows semver. Bump withnpm version patch|minor|major.- Set
"private": truefor applications that should never be published to npm.
Entry point fields
{
"main": "./dist/index.js", // CJS entry point (all Node versions)
"module": "./dist/index.esm.js", // ESM entry point (bundlers like Webpack/Rollup)
"types": "./dist/index.d.ts", // TypeScript type declarations
"bin": {
"mycli": "./dist/cli.js" // name → script; installs symlink in node_modules/.bin
}
}
"module"is not a Node.js field — it is a bundler convention. For Node.js ESM use"exports"with"import"conditions instead.
The type field
{
"type": "module" // all .js files in this package are treated as ESM
}
{
"type": "commonjs" // (default) all .js files are treated as CJS
}
Regardless of "type", you can always force the module system with file extensions:
.mjs— always ESM.cjs— always CommonJS
The exports field (modern entry points)
"exports" supersedes "main" for Node.js 12+ and supports conditional exports, subpath exports, and import blocking.
{
"exports": {
".": {
"import": "./dist/index.mjs", // ESM import
"require": "./dist/index.cjs", // CJS require()
"types": "./dist/index.d.ts", // TypeScript
"default": "./dist/index.cjs" // fallback
},
"./utils": {
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
},
"./package.json": "./package.json" // allow reading the manifest
}
}
Subpath patterns (glob):
{
"exports": {
"./components/*": {
"import": "./dist/components/*.mjs",
"require": "./dist/components/*.cjs"
}
}
}
Once
"exports"is defined, only the listed paths are accessible to consumers. Deep imports likeimport 'my-pkg/dist/internal'will throw unless explicitly exported.
Conditional export conditions (full list):
| Condition | When it applies |
|---|---|
"import" | import / import() statements |
"require" | require() calls |
"node" | Node.js runtime |
"node-addons" | Node.js with native addons |
"browser" | Browser-targeting bundlers |
"deno" | Deno runtime |
"worker" | Web Worker / Service Worker |
"default" | Catch-all fallback (must be last) |
The engines field
{
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
}
}
npm warns if a consumer's environment does not satisfy these ranges. Set "engine-strict": true in .npmrc to make mismatches fatal.
Scripts
{
"scripts": {
"preinstall": "node scripts/check-node-version.js",
"install": "node-gyp build",
"postinstall": "patch-package",
"prestart": "node scripts/pre-flight.js",
"start": "node dist/server.js",
"prebuild": "rimraf dist",
"build": "tsc",
"postbuild": "node scripts/copy-assets.js",
"dev": "tsx watch src/index.ts",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src --ext .ts,.tsx",
"format": "prettier --write src",
"typecheck": "tsc --noEmit",
"ci": "pnpm run typecheck && pnpm run lint && pnpm run test"
}
}
Lifecycle hooks run automatically:
pre<name>runs before<name>post<name>runs after<name>preinstall,install,postinstall— triggered bynpm installprepublishOnly,publish,postpublish— triggered bynpm publish
Chain multiple commands in one script:
{
"scripts": {
"build": "tsc && rollup -c && node scripts/minify.js"
}
}
Access package.json fields inside scripts via npm_package_* environment variables:
# Inside a script, $npm_package_version equals the version field
echo "Building v$npm_package_version"
Output: (none — exits 0 on success)
Dependency types
{
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"typescript": "^5.5.2",
"vitest": "^2.0.0"
},
"peerDependencies": {
"react": ">=17.0.0 <20"
},
"peerDependenciesMeta": {
"react": { "optional": true }
},
"optionalDependencies": {
"fsevents": "^2.3.3"
},
"bundleDependencies": ["my-bundled-lib"]
}
| Type | Installed for | When to use |
|---|---|---|
dependencies | App + library consumers | Runtime requirements |
devDependencies | App only | Build tools, testing, linters |
peerDependencies | Consumer must install | Plugin host requirements |
optionalDependencies | App only, failure OK | Platform-specific native modules |
bundleDependencies | Bundled into tarball | When you can't rely on npm registry |
Version specifiers
{
"dependencies": {
"exact": "1.2.3", // exactly 1.2.3
"caret": "^1.2.3", // >=1.2.3 <2.0.0 (compatible with 1.x)
"tilde": "~1.2.3", // >=1.2.3 <1.3.0 (patch updates only)
"range": ">=1.2.3 <2", // explicit range
"wildcard": "*", // any version (not recommended)
"latest": "latest", // whatever npm considers latest
"pre-release": "2.0.0-beta.1", // exact pre-release
"github": "github:user/repo#main",
"git-url": "git+https://github.com/user/repo.git#v1.2.3",
"local": "file:../my-local-pkg"
}
}
Use
^(caret) for almost everything. It allows minor and patch updates but not breaking major changes. Use~only when you have known instability in minor releases.
workspaces — monorepo support
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*",
"!packages/deprecated-*"
]
}
npm, Yarn, and pnpm all read this field (pnpm also uses pnpm-workspace.yaml). Workspaces are hoisted and cross-linked automatically.
files — control what gets published
{
"files": [
"dist",
"src",
"!src/**/*.test.ts",
"README.md"
]
}
Only listed files are included in the npm tarball. package.json, README, CHANGELOG, and LICENSE are always included. node_modules and files in .gitignore are always excluded.
packageManager — Corepack integration
{
"packageManager": "pnpm@9.12.1+sha256.a7e4e2b7"
}
When this field is set, Corepack enforces that the specified manager and version is used. Running npm install in a pnpm project will print an error and exit. This prevents the classic "why does CI fail? you ran npm, not pnpm" problem.
repository, bugs, homepage
{
"repository": {
"type": "git",
"url": "https://github.com/my-org/my-repo.git"
},
"bugs": {
"url": "https://github.com/my-org/my-repo/issues",
"email": "bugs@example.com"
},
"homepage": "https://my-org.github.io/my-repo"
}
Complete maximal example
{
"name": "@my-org/my-lib",
"version": "3.2.1",
"description": "A production-grade example library",
"keywords": ["utility", "typescript", "esm"],
"license": "MIT",
"author": {
"name": "Alice Dev",
"email": "alice@example.com",
"url": "https://alice.example.com"
},
"repository": {
"type": "git",
"url": "https://github.com/my-org/my-lib.git"
},
"bugs": { "url": "https://github.com/my-org/my-lib/issues" },
"homepage": "https://my-org.github.io/my-lib",
"packageManager": "pnpm@9.12.1",
"engines": { "node": ">=20.0.0" },
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs",
"types": "./dist/utils.d.ts"
}
},
"bin": { "my-cli": "./dist/cli.js" },
"files": ["dist", "README.md", "CHANGELOG.md"],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"dev": "tsup src/index.ts --watch",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src",
"typecheck": "tsc --noEmit",
"prepublishOnly": "pnpm run build && pnpm run test"
},
"dependencies": {
"zod": "^3.23.8"
},
"devDependencies": {
"typescript": "^5.5.2",
"vitest": "^2.0.0",
"tsup": "^8.2.4",
"eslint": "^9.7.0"
},
"peerDependencies": {
"typescript": ">=5.0.0"
},
"peerDependenciesMeta": {
"typescript": { "optional": true }
}
}
bin — installing CLI entry points
A package can ship one or more executables via the bin field. When the package is installed, the package manager creates symlinks (or shim scripts on Windows) in node_modules/.bin/ and — for global installs — in the system PATH (e.g. /usr/local/bin/ or %APPDATA%\npm\).
{
"name": "mytool",
"version": "1.0.0",
"bin": "./dist/cli.js"
}
Single-string form: the symlink is named after the package (mytool). Multi-entry form maps names to scripts:
{
"bin": {
"mytool": "./dist/cli.js",
"mytool-fast": "./dist/cli-fast.js"
}
}
The target file must start with a shebang line and be executable:
#!/usr/bin/env node
import { program } from "commander";
program.parse();
chmod +x dist/cli.js # not required for npm; some package managers preserve mode
Output: (none — exits 0 on success)
Use
npxto run abinfrom an installed-but-not-PATH package:npx mytool --helprunsnode_modules/.bin/mytool.
imports — internal subpath aliases
"imports" is the inverse of "exports" — it defines aliases for the package's own internal imports, scoped to the package. The convention is to prefix aliases with # to mark them as private. Use this to avoid deep ../../../utils relative paths.
{
"imports": {
"#utils/*": "./src/utils/*.js",
"#config": {
"node": "./src/config.node.js",
"browser": "./src/config.browser.js",
"default": "./src/config.js"
}
}
}
// src/server.js — inside the same package
import { log } from "#utils/log.js";
import config from "#config";
This works in plain Node (no bundler needed) and is the standards-track replacement for module-alias and tsconfig-paths runtime hacks. Note that TypeScript still needs paths in tsconfig.json to resolve these for type-checking.
exports — advanced conditional resolution
The earlier section covered the basics. Here are the patterns real packages use.
Per-condition + per-environment fallback chain
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"browser": {
"import": "./dist/index.browser.mjs",
"require": "./dist/index.browser.cjs"
},
"node": {
"import": "./dist/index.node.mjs",
"require": "./dist/index.node.cjs"
},
"default": "./dist/index.js"
}
}
}
The resolver walks top-to-bottom and picks the first matching condition. "types" must come first for TypeScript to resolve correctly. "default" must come last as the catch-all.
Pattern exports (glob)
{
"exports": {
"./components/*": {
"types": "./dist/components/*.d.ts",
"import": "./dist/components/*.js"
}
}
}
Consumers can then import Button from "my-lib/components/Button". Each request gets resolved by string substitution.
Block deep imports explicitly
{
"exports": {
".": "./dist/index.js"
}
}
With just ".", every other import (my-lib/utils, my-lib/internal) throws ERR_PACKAGE_PATH_NOT_EXPORTED. This is the modern way to enforce "public API only" boundaries on consumers.
package.json itself
If consumers need to read your package.json (e.g. for the version), expose it explicitly:
{
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
}
}
scripts — lifecycle deep dive
npm runs lifecycle scripts implicitly during certain operations. Custom scripts (npm run <name>) also auto-trigger their pre<name> and post<name> hooks. Understanding which trigger when prevents "why didn't my install hook run?" mysteries.
Lifecycle order (npm install)
preinstall → before any deps are resolved
install → during install (rare; legacy node-gyp packages)
postinstall → after deps installed (run codegen, patches)
prepublish → DEPRECATED — runs on `npm install` AND publish
prepare → runs after install + before publish + on git deps
prepublishOnly → runs only on `npm publish`, after `prepare`
prepack → runs before `npm pack` (tarball creation)
postpack → runs after `npm pack`
prepare is the modern hook for "build before publishing" — it runs reliably on git-installed deps (where the registry tarball doesn't exist), so a consumer who does npm install github:my-org/repo still gets a built copy.
{
"scripts": {
"prepare": "npm run build",
"prepublishOnly": "npm test && npm run lint"
}
}
Pre/post hooks for arbitrary scripts
{
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc",
"postbuild": "node scripts/copy-assets.js"
}
}
Running npm run build runs prebuild → build → postbuild automatically. There is no way to disable this (other than renaming the script).
Cross-platform script runners
Bash-specific scripts break on Windows. Use cross-platform tools:
{
"scripts": {
"clean": "rimraf dist coverage", // cross-platform rm -rf
"copy": "cpy 'src/assets/**/*' dist/assets", // cross-platform cp
"envset": "cross-env NODE_ENV=production node ./dist/server.js",
"parallel": "npm-run-all --parallel dev:server dev:client",
"sequential": "npm-run-all build:lib build:cli"
}
}
| Tool | Purpose |
|---|---|
rimraf | Cross-platform rm -rf |
cpy-cli | Cross-platform file copy |
cross-env | Set env vars across Windows/POSIX |
npm-run-all / concurrently | Run scripts in parallel or sequence |
wait-on | Wait for URL/file before next step |
npm_* env vars
Inside any script, npm exposes config and package metadata as env vars:
echo "Package: $npm_package_name"
echo "Version: $npm_package_version"
echo "Node: $npm_config_user_agent"
Output:
Package: my-app
Version: 1.0.0
Node: npm/10.8.1 node/v20.15.0 darwin arm64
-- to forward arguments
npm run test -- --watch --coverage
Output: (Vitest receives --watch --coverage)
The -- separator passes everything after to the underlying script. Without it, the flags bind to npm itself.
workspaces — monorepo deep dive
Workspaces wire multiple packages into one install. Every workspace package gets its own package.json; dependencies are hoisted to the root node_modules/ when compatible, and cross-package references are symlinks.
// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*", "apps/*"]
}
my-monorepo/
├── package.json ← workspaces declared here
├── node_modules/ ← hoisted deps (single copy of shared packages)
├── packages/
│ ├── ui/
│ │ ├── package.json ← name: "@my/ui"
│ │ └── src/
│ └── utils/
│ ├── package.json ← name: "@my/utils"
│ └── src/
└── apps/
└── web/
├── package.json ← deps: { "@my/ui": "workspace:*" }
└── src/
Workspace protocols (pnpm/Yarn/npm)
pnpm and Yarn support workspace:* (any version), workspace:^ (with caret), workspace:1.2.3 (exact). npm uses plain version specifiers and symlinks if it finds a matching workspace.
// apps/web/package.json
{
"dependencies": {
"@my/ui": "workspace:*",
"@my/utils": "workspace:^"
}
}
Running scripts across workspaces
# npm — run "build" in every workspace
npm run build --workspaces
# Skip workspaces missing the script
npm run test --workspaces --if-present
# Only one workspace
npm run dev --workspace=@my/ui
# pnpm — recursive
pnpm -r build
pnpm --filter @my/ui dev
# Yarn
yarn workspaces foreach -A run build
Output: (each workspace's script runs in turn or in parallel depending on flags)
Hoisting and nohoist
The default behaviour is to hoist every dep to root. When a workspace needs a specific version that conflicts with hoisting, pnpm and Yarn place a private copy under the workspace's own node_modules/. npm 7+ uses an installed-tree model that is mostly equivalent.
// Yarn — nohoist specific packages
{
"workspaces": {
"packages": ["packages/*"],
"nohoist": ["**/react-native"]
}
}
engines — runtime requirements
{
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0",
"pnpm": ">=9.0.0",
"bun": ">=1.0.0"
}
}
By default this is a warning. Make it fatal via .npmrc:
# .npmrc
engine-strict=true
Output:
npm ERR! code EBADENGINE
npm ERR! Unsupported engine {
npm ERR! package: 'my-app@1.0.0',
npm ERR! required: { node: '>=20.0.0' },
npm ERR! current: { node: 'v18.20.4', npm: '10.7.0' }
npm ERR! }
pnpm honours engines unconditionally — no strict flag needed.
packageManager — Corepack
Corepack ships with Node 16.10+ and reads packageManager from package.json to install and pin the exact version of npm, pnpm, Yarn, or Bun used by the project.
{
"packageManager": "pnpm@9.12.1+sha256.a7e4e2b7c4b9c4d5e6f7a8b9c0d1e2f3"
}
Enable Corepack once per machine:
corepack enable
Output:
Internal Error: EEXIST: file already exists
(safe — Corepack symlinks already in place)
After enabling, running pnpm / yarn / npm in a repo first reads packageManager, downloads the pinned version into ~/.cache/node/corepack/, and invokes that exact version. Mismatched commands abort:
Output:
This project is configured to use pnpm because packageManager is "pnpm@9.12.1"
Please use pnpm instead of npm to run scripts.
This eliminates the "works on my machine because I have npm 10, you have npm 7" class of bug.
Overrides — forcing dependency versions
Sometimes a transitive dependency has a bug or vulnerability that the direct dep hasn't picked up. Overrides force a specific version across the tree.
npm — overrides
{
"overrides": {
"vulnerable-pkg": "1.2.4",
"react": "$react", // pin to your own version
"some-pkg": {
"transitive-dep": "^2.0.0"
},
"foo@<1.0.0": {
"bar": "1.0.0"
}
}
}
The $name syntax reuses your own dependencies entry — handy for ensuring a single React copy across the tree.
Yarn — resolutions
{
"resolutions": {
"vulnerable-pkg": "1.2.4",
"**/vulnerable-pkg": "1.2.4",
"some-pkg/transitive-dep": "^2.0.0"
}
}
pnpm — pnpm.overrides
{
"pnpm": {
"overrides": {
"vulnerable-pkg": "1.2.4",
"vulnerable-pkg@<2": "2.0.0"
},
"peerDependencyRules": {
"ignoreMissing": ["react-native"],
"allowedVersions": {
"react": "18"
}
},
"patchedDependencies": {
"some-pkg@3.1.0": "patches/some-pkg@3.1.0.patch"
}
}
}
Overrides bypass semver. They're a hotfix, not a strategy. Document each override in
CHANGELOG.mdand revisit when the upstream issue closes.
peerDependencies and peerDependenciesMeta
Peer dependencies say "I need this, but the host app should install it." Classic example: a React component library — the consumer's app already has React, you don't want to bundle a second copy.
{
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"peerDependenciesMeta": {
"react-dom": { "optional": true }
}
}
peerDependenciesMeta.<name>.optional = true tells the package manager not to warn when the peer is missing — useful when a feature only kicks in if a peer is present.
npm 7+ auto-installs peers (configurable via legacy-peer-deps in .npmrc). pnpm requires explicit peer install by default — its strictness is what catches "missing peer" bugs that npm hides.
optionalDependencies and bundleDependencies
optionalDependencies install on a best-effort basis. If a native build fails (the canonical case: fsevents on Linux), the install continues without it.
{
"optionalDependencies": {
"fsevents": "^2.3.3"
}
}
bundleDependencies (alias: bundledDependencies) lists deps that should be bundled inside the tarball published to npm. Useful for shipping a forked or private dep without a registry round-trip.
{
"bundleDependencies": ["my-private-lib"],
"dependencies": {
"my-private-lib": "1.0.0"
}
}
When a consumer npm installs your package, my-private-lib is unpacked from your tarball — npm never queries the registry for it.
funding, private, sideEffects
funding
{
"funding": "https://github.com/sponsors/alicedev"
}
Or an array for multiple sources:
{
"funding": [
{ "type": "github", "url": "https://github.com/sponsors/alicedev" },
{ "type": "opencollective", "url": "https://opencollective.com/myproject" }
]
}
npm fund lists every funded dep in your tree:
npm fund
Output:
my-app@1.0.0
├── https://github.com/sponsors/alicedev
│ └── lodash@4.17.21
└── https://opencollective.com/postcss
└── postcss@8.4.0
private
{ "private": true }
Prevents npm publish with an explicit error. Always set this on application repos and the root of a monorepo.
sideEffects — tree-shaking hint
Tells bundlers (Webpack, Rollup, esbuild) which files are pure (safe to eliminate if their exports aren't imported). false = entire package is pure.
{
"sideEffects": false
}
{
"sideEffects": ["*.css", "./src/polyfill.js"]
}
A misconfigured sideEffects is a common source of "import works in dev but is undefined in prod" bugs — the bundler legitimately tree-shook the module thinking it had no side effects.
browser field
Bundler hint: redirect Node-only modules to browser equivalents at build time.
{
"main": "./dist/index.js",
"browser": "./dist/index.browser.js"
}
{
"browser": {
"./dist/server.js": "./dist/browser.js",
"fs": false // stub out Node's fs
}
}
Largely superseded by "exports" with "browser" conditions for new packages.
Validators and tooling
# Validate package.json against the npm schema
npx publint # check publish-readiness
# Find duplicate / outdated deps
npx npm-check-updates # interactive upgrade
# Sort keys to canonical order
npx sort-package-json # or use prettier-plugin-packagejson
# Spot dependency-confusion / typosquatting
npx better-npm-audit audit
Output (publint):
✔ All exports resolve correctly
✔ Types are referenced for every entry
✖ "main" field uses ./dist/index.js but exports prefer ./dist/index.cjs
Common pitfalls
type: "module"breaks all.jsCommonJS files — switch ambiguous files to.cjsor rewrite to ESM.exportsblocks legitimate deep imports — once you add"exports", consumers can no longer reach intodist/internal/foo. List every public path explicitly.binscript has no shebang — Windows shims fall back tonode, but POSIX symlinks fail with "exec format error".postinstallruns on consumers too — everynpm install <your-pkg>runs yourpostinstallin their machine. Move build steps toprepareso they only run during your own development.peerDependenciesnot installed — npm 7+ auto-installs, but old setups (or pnpm with strict mode) won't. Document peers in the README.packageManagerfield ignored without Corepack enabled — runcorepack enableonce per machine.- Hoisted deps leak undeclared imports — code imports
lodashwithout listing it as a dep, works because some other dep hoisted it. Catch witheslint-plugin-import(import/no-extraneous-dependencies) or pnpm's strict mode. workspaces: ["packages/*"]traversesnode_modules— exclude with!packages/**/node_modulesor rely on the package manager's default ignore.- Lockfile mismatches in CI —
npm installupdatespackage-lock.json; CI should usenpm ci(immutable) to catch drift. - Version specifier
*resolves to the latest including breaking changes — never publish*; always use^or~.
Real-world recipes
Pin Node + pnpm + lockfile across the team
{
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
},
"packageManager": "pnpm@9.12.1"
}
Pair with .npmrc:
engine-strict=true
package-manager-strict=true
Now any colleague who runs npm install gets a hard error directing them to pnpm.
Single-source the version across multiple packages
In a monorepo, hard-code the version in one place and $version it in others.
// packages/core/package.json
{
"name": "@my/core",
"version": "1.2.3",
"dependencies": { "@my/utils": "workspace:*" }
}
// packages/cli/package.json
{
"name": "@my/cli",
"version": "1.2.3",
"dependencies": { "@my/core": "workspace:^" }
}
Bump all versions in lockstep with pnpm -r exec pnpm version 1.2.4.
Run pre-commit checks via a prepare script
{
"scripts": {
"prepare": "husky"
},
"devDependencies": { "husky": "^9.0.0" }
}
When teammates npm install, Husky installs the git hook automatically because prepare runs after install.
Dual ESM + CJS publishing
{
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"]
}
Tools like tsup, unbuild, and Vite's library mode generate both bundles in one build.
Allow GitHub Sponsors to surface in npm fund
{
"funding": {
"type": "github",
"url": "https://github.com/sponsors/alicedev"
}
}
Audit and fix only production dependencies
npm audit --omit=dev
npm audit fix --omit=dev
Output:
found 0 vulnerabilities
Strip dev-only fields when publishing
files already does this, but for a finer cut use a publish-time transform:
# Publish a transformed package.json (with @clean-publish)
npx clean-publish
Output:
✔ Wrote tarball with stripped scripts.dev, scripts.test, devDependencies
See also
- npm, pnpm, yarn, bun — package managers that read this file
- Node runtime, modules — how
type,exports,importsare resolved - Vite, Vitest — typical
scriptsanddevDependenciescompanions - Bun, Deno — alternative runtimes that also read
package.json(Bun fully; Deno selectively) - Biome, ESLint, Prettier —
devDependencieslint/format ecosystem