Loading...
Loading...
Beyond npm install. Workspaces, monorepos, and professional dependency workflows.
Module 12 · Path to Frameworks
Frameworks like React and Next.js build on JavaScript fundamentals. Node.js runs JavaScript on servers, npm manages packages, and build tools prepare code for production. This module bridges vanilla JS to the modern ecosystem.
Learn Package Management in JavaScript — covering Dependency Resolution, Lock Files, Workspaces, Updating with interactive visual examples and step-by-step explanations.
Vanilla JavaScript teaches you how the language works. Frameworks like React teach you how teams ship user interfaces at scale. This module bridges the gap — showing how the fundamentals you have learned map directly to React components, Node.js servers, and npm workflows.
You do not need to pick a framework on day one, but understanding how JavaScript powers React, Next.js, and Node.js makes every future tutorial easier to follow.
Understanding package management is essential for every JavaScript developer. It shows up in frontend UI code, backend APIs, and framework internals — and getting it wrong leads to bugs that are hard to trace. This chapter builds intuition with visuals so the behavior sticks.
How package managers figure out what to install.
// When you install "express", it has its OWN dependencies: // express → accepts, body-parser, cookie, ... // body-parser → bytes, content-type, debug, ... // Each of those has dependencies too! // Your project installs 1 package → gets 50+ in node_modules // This is the "dependency tree" // Dependency types: // Direct — packages YOU install (in your package.json) // Transitive — dependencies OF your dependencies // Peer — packages the library expects YOU to provide // node_modules structure: // node_modules/ // ├── express/ ← your dependency // ├── accepts/ ← express's dependency (hoisted) // ├── body-parser/ ← express's dependency (hoisted) // └── .pnpm/ ← pnpm's content-addressable store // Hoisting: npm/yarn flatten dependencies to top level // This can cause "phantom dependencies" (using packages // you didn't explicitly install) // pnpm is strict: only YOUR dependencies are accessible // at the top level. Transitive deps are hidden. // This prevents accidental phantom dependency usage. // Peer dependencies: // "peerDependencies": { "react": "^18.0.0" } // Means: "I work with React, but YOU must install it" // Common in plugins and component libraries
Pin exact versions for reproducible installs across machines.
// Without lock file: // package.json: "lodash": "^4.17.0" // Developer A installs → gets 4.17.21 // Developer B installs (months later) → gets 4.17.25 // Different versions = potential bugs! // With lock file: // Pins EVERY package (including transitive) to exact version // npm install reads lock file → same versions everywhere // Lock file formats: // npm → package-lock.json // pnpm → pnpm-lock.yaml // yarn → yarn.lock // bun → bun.lockb (binary) // Rules: // ✓ ALWAYS commit your lock file to git // ✓ NEVER edit lock files manually // ✓ Use 'npm ci' in CI/CD (clean install from lock file) // ✓ Use only ONE package manager per project // npm install vs npm ci: // npm install — updates lock file if package.json changed // npm ci — installs EXACTLY what's in lock file // (fails if lock file is out of sync) // Use this in CI/CD pipelines! // pnpm equivalent: // pnpm install --frozen-lockfile // (fails if lock file needs updating) // Detecting lock file drift: // If package.json and lock file disagree: // npm ci will FAIL (which is what you want in CI) // This catches "forgot to run npm install" mistakes
Manage multiple packages in one repository (monorepo).
// Monorepo: multiple related packages in one git repo // Example: a shared UI library + web app + API server // Structure: // my-project/ // ├── package.json ← root (workspace config) // ├── pnpm-workspace.yaml ← pnpm workspace definition // ├── packages/ // │ ├── ui/ ← shared component library // │ │ ├── package.json // │ │ └── src/ // │ ├── web/ ← Next.js app // │ │ ├── package.json // │ │ └── src/ // │ └── api/ ← Express server // │ ├── package.json // │ └── src/ // pnpm-workspace.yaml: packages: - "packages/*" // Root package.json: { "private": true, "scripts": { "dev": "pnpm -r dev", "build": "pnpm -r build", "lint": "pnpm -r lint" } } // packages/web/package.json: { "name": "@myproject/web", "dependencies": { "@myproject/ui": "workspace:*" } } // Benefits: // - Shared code without publishing to npm // - One git repo, one CI pipeline // - Atomic changes across packages // - Single node_modules (pnpm links efficiently) // Commands: // pnpm -r dev → run "dev" in ALL packages // pnpm --filter web dev → run "dev" only in web package // pnpm --filter ui build → build only the UI package // pnpm add lodash --filter api → add lodash only to api
Keep dependencies fresh without breaking things.
// Check for outdated packages: $ npm outdated // Package Current Wanted Latest // lodash 4.17.20 4.17.21 4.17.21 // react 18.2.0 18.2.0 19.0.0 // Update within semver range (safe): $ npm update // updates all to "wanted" version $ pnpm update // same // Update to latest (may include breaking changes): $ npm install react@latest $ pnpm update --latest // updates everything to latest // Interactive update (pnpm): $ pnpm update --interactive // Shows each package, lets you choose which to update // Automated updates: // - GitHub Dependabot: auto-creates PRs for updates // - Renovate Bot: more configurable alternative // Both run your tests to verify updates don't break anything // Handling major version updates (breaking changes): // 1. Read the changelog/migration guide // 2. Create a branch // 3. Update the package // 4. Fix breaking changes (compiler/tests will guide you) // 5. Run full test suite // 6. Merge when green // Pinning a specific version (prevent auto-updates): // Remove ^ or ~ prefix: "lodash": "4.17.21" // exactly this version, never update // Overrides — force a version for transitive dependencies: // package.json: { "pnpm": { "overrides": { "vulnerable-package": ">=2.0.1" } } }
Professional dependency management habits.
// 1. Use exact lock file installs in CI: // npm ci (not npm install) // pnpm install --frozen-lockfile // 2. Audit regularly: $ pnpm audit // Fix vulnerabilities before deploying // 3. Minimize dependencies: // Before installing: "Can I write this in 10 lines?" // Use bundlephobia.com to check package size // Prefer smaller, focused packages over huge utilities // 4. Keep dependencies up to date: // Monthly update cycle → easier than yearly catch-up // Use Dependabot or Renovate for automation // 5. Use .npmrc for project config: // .npmrc file in project root: engine-strict=true auto-install-peers=true save-exact=true // 6. Set engine requirements: // package.json: { "engines": { "node": ">=20.0.0", "pnpm": ">=9.0.0" } } // 7. Use corepack (Node 20+): // $ corepack enable // Ensures correct package manager version // package.json: { "packageManager": "pnpm@9.1.0" } // Anyone who clones gets the exact pnpm version // 8. Review lock file diffs in PRs: // Large lock file changes = red flag // New dependencies should be intentional // Check for supply chain attack indicators // 9. Separate prod from dev dependencies: // pnpm add express → dependencies (ships to production) // pnpm add -D vitest → devDependencies (dev only)
Common questions about package management.