Understanding package.json Fields
Core Metadata & Package Identity
Establish strict package identity using RFC 1123 compliant name and SemVer 2.0.0 version. Mandate SPDX-compliant license strings to satisfy automated compliance scanners. For monorepo roots, enforce private: true to prevent accidental publication of internal scaffolding. This foundational configuration integrates directly into broader Core JavaScript Package Workflows for automated CI/CD pipelines and registry publishing.
Implementation Checklist
- [ ] Validate
nameagainst registry availability and RFC 1123 constraints (^[a-z0-9-]+$). - [ ] Pin
versionto exact SemVer format; strip pre-release tags (-alpha,-rc) inmain/releasebranches. - [ ] Set
licenseto a valid SPDX identifier (MIT,Apache-2.0,BSD-3-Clause). - [ ] Apply
"private": trueat the monorepo root to blocknpm publishexecution.
CLI Validation & Safety Guards
# Verify package name availability before commit
npm view <package-name> version 2>/dev/null || echo "Name available"
# Enforce strict versioning in CI
npx semver --coerce "$(node -p "require('./package.json').version")" || exit 1
# Block accidental root publication
echo '"private": true' >> package.json
Module Resolution & Export Maps
Configure explicit exports maps to replace legacy main and module fields. Define conditional exports (import, require, types, default) to guarantee deterministic resolution across bundlers and runtimes. Implement dual-module packaging strategies by referencing How to Configure package.json for Dual Modules to prevent tree-shaking failures and runtime ERR_MODULE_NOT_FOUND errors.
Explicit Conditional Exports Map
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"default": "./dist/esm/index.js"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/esm/utils.js",
"require": "./dist/cjs/utils.js"
}
}
}
Note: Order matters. Place types first to satisfy TypeScript resolution before JS execution paths.
Build Pipeline Integration
# Remove legacy fallback fields to prevent bundler ambiguity
jq 'del(.main, .module)' package.json > package.tmp && mv package.tmp package.json
# Verify export resolution locally
node -e "import('./dist/esm/index.js').then(() => console.log('ESM OK'))"
node -e "require('./dist/cjs/index.js'); console.log('CJS OK')"
Dependency Graphs & Security Overrides
Differentiate dependencies, devDependencies, and peerDependencies to minimize production bundle size and prevent runtime bloat. Use overrides (npm) or resolutions (yarn) to patch vulnerable transitive dependencies deterministically. Align dependency pinning with strict Lockfile Management Strategies to guarantee reproducible builds across CI environments and block supply-chain drift.
Dependency Classification & Overrides
{
"dependencies": {
"lodash-es": "^4.17.21"
},
"devDependencies": {
"typescript": "^5.4.0",
"vitest": "^1.3.0"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"overrides": {
"semver": "^7.5.4",
"postcss": "^8.4.35"
}
}
CI/CD Lockfile Enforcement & Audit
# .github/workflows/dependency-audit.yml
name: Dependency & Lockfile Guard
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 8
- run: pnpm install --frozen-lockfile
- run: pnpm audit --audit-level=high
- name: Verify no drift
run: pnpm diff
Workspace Orchestration & Engine Constraints
Define workspaces arrays to enable local linking, dependency hoisting, and cross-package script execution. Configure engines to enforce Node.js and package manager versions at runtime, blocking execution on unsupported environments. Integrate workspace topology with Workspace Configuration Deep Dive to optimize script execution, dependency hoisting, and cache isolation in large-scale monorepos.
Root-Level Configuration
{
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"packageManager": "pnpm@8.15.0",
"overrides": {
"semver": "^7.5.4"
},
"workspaces": [
"packages/*",
"apps/*"
]
}
Execution & Runtime Guards
# Enforce package manager version via corepack
corepack enable
corepack prepare pnpm@8.15.0 --activate
# Run workspace-aware scripts (delegated to Turborepo/Nx)
pnpm --filter "@scope/ui" build
pnpm -r --parallel test
# Validate engine constraints before install
pnpm install --engine-strict
Common Anti-Patterns
| Anti-Pattern | Impact | Remediation |
|---|---|---|
Using main/module alongside exports |
Bundler resolution conflicts, broken tree-shaking | Delete main/module; rely exclusively on exports |
Omitting types in conditional exports |
TS consumers fallback to implicit @types or fail |
Always declare "types": "./dist/index.d.ts" first |
Leaving private unset at monorepo root |
Accidental npm publish of scaffolding/CI secrets |
Set "private": true in root package.json |
Using ^/~ for peerDependencies |
Incompatible runtime versions in consumer apps | Use explicit minimums: ">=18.0.0" |
Hardcoding file: paths in dependencies |
Broken CI installs, non-portable graphs | Use workspace protocols: "workspace:*" or "link:" |
Frequently Asked Questions
Should I use main or exports for modern package distribution?
Always prioritize exports. It provides explicit, secure resolution paths, prevents unauthorized access to internal files, and supports conditional loading for ESM/CJS/types. main is deprecated for new packages and should only exist as a fallback for legacy tooling.
How do I enforce strict dependency versions across a monorepo?
Use overrides (npm) or resolutions (yarn) at the root package.json to force specific transitive dependency versions. Combine this with engines constraints and a strict lockfile policy to prevent drift across workspace packages.
What is the correct way to handle peerDependencies in library authoring?
Declare peerDependencies with explicit minimum versions (e.g., "react": ">=18.0.0") and omit caret/tilde ranges. Use peerDependenciesMeta with optional: true for framework-specific integrations to prevent installation failures in non-consumer environments.