Back to core workflows Fix dependency resolution Tune package metadata Jump to monorepo patterns

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 name against registry availability and RFC 1123 constraints (^[a-z0-9-]+$).
  • [ ] Pin version to exact SemVer format; strip pre-release tags (-alpha, -rc) in main/release branches.
  • [ ] Set license to a valid SPDX identifier (MIT, Apache-2.0, BSD-3-Clause).
  • [ ] Apply "private": true at the monorepo root to block npm publish execution.

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.