Dependency Rollercoaster: Navigating the NPM Theme Park

Published: (January 12, 2026 at 12:12 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

The “Aha!” Moment That Started It All

I was implementing a new feature, feeling like a code wizard 🧙‍♂️. I submitted the PR, and then my TL dropped a comment that sounded like it was in a foreign language:

You need to add these as peer dependencies so the consuming app can install them.

Wait, what? 🤔

I stared at my screen. I’d been using dependencies and devDependencies since I started with Node, but peerDependencies? And why do I need to install react-hook-form when neither the library I’m using has it as a direct dependency nor am I using it directly?

That confusion led me down a rabbit hole that completely changed how I understand npm’s dependency ecosystem. It turns out, managing a package.json is a lot like running an amusement park.

Analogy: Think of your project as a world‑class theme park. To keep the rides running and the guests happy, you need different types of resources.


Dependencies — The Main Attractions

{
  "dependencies": {
    "react": "^18.2.0",
    "axios": "^1.6.0"
  }
}

These are the actual roller‑coasters. If the “Big Loop” package isn’t there, the park isn’t a theme park—it’s just an empty parking lot.

  • When to use: Any package your code directly imports and needs at runtime.
  • The rule: These get installed automatically. If they’re missing, the park (your app) is closed (code won’t run in production).

DevDependencies — The Maintenance Crew

{
  "devDependencies": {
    "jest": "^29.0.0",
    "eslint": "^8.0.0",
    "typescript": "^5.0.0"
  }
}

These are the hard hats, wrenches, and blueprints. Guests don’t see them, but you can’t build or repair the roller‑coaster without them.

  • When to use: Tools for development, testing, building, or linting (e.g., Jest, ESLint, Vite).
  • The rule: Not installed when someone installs your package as a library. They only exist at the construction site (your local machine).

PeerDependencies — The “Bring Your Own Gear” Requirement

{
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

This is the “Requirement” sign at the entrance of a ride. Imagine a water slide that says: “We provide the slide, but YOU must bring your own swimsuit.”

The “Aha!” Moment

Our component library is the water slide. If we bundled a swimsuit with every slide (i.e., put React in dependencies), the park would be full of redundant, soggy clothes that don’t fit the guests (bundle bloat & version conflicts).

Instead we check at the gate: “Do you have a swimsuit (React 19)?” Our library expects the host environment to already have this package installed.

The Chain Mystery – Transitive Peer Dependencies

If our slide uses a specific floatie (another library, e.g., awesome-form-components), and that floatie requires a pump (react-hook-form), the guest suddenly needs both—even though our library doesn’t use react-hook-form directly.

Main App (The Guest)
 └── My Library (The Slide)
       └── awesome-form-components (The Floatie)
             └── [peer] react-hook-form (The Pump) 🚩

Result: The main app must install react-hook-form. If it doesn’t, npm throws a Peer Dependency Resolution Error.

  • When to use:
    • Plugins & UI kits that “plug into” a larger framework (React, Vue, Tailwind).
    • Singleton enforcement (only one instance of a package should exist).
    • Version flexibility (let developers use any compatible version within your specified range).

OptionalDependencies — The VIP Fast Pass

{
  "optionalDependencies": {
    "fsevents": "^2.3.0"
  }
}

These are the “nice‑to‑have” extras. Maybe it’s a heated seat on the log flume. If the heater is out of stock, the ride still works—it’s just a bit colder.

  • When to use: Platform‑specific optimizations (e.g., macOS‑only features).
  • The rule: If installation fails, npm just shrugs and continues. Your code should handle the “empty seat” gracefully.

Transitive Dependencies – The Supply Chain

Transitive dependencies are the “dependencies of our dependencies.”

Imagine you bought a roller‑coaster (direct dependency), but that roller‑coaster was built using a specific brand of bolts and grease (transitive). You didn’t order the bolts, but they’re now in your park!

Your Park (Project)
 ├── Roller‑coaster (Direct)
 │    ├── Specialized Bolts (Transitive)
 │    └── Industrial Grease (Transitive)

The danger: If those bolts have a safety recall (security vulnerability), your whole ride is at risk—even though you never talked to the bolt manufacturer.


Quick Reference Table

TypeInstalled When?Analogy
dependenciesAlwaysThe rides (essential)
devDependenciesLocal onlyThe tools (construction)
peerDependenciesBy the userThe gear (swimsuits/helmets)
optionalDependenciesIf possibleThe VIP perks (extras)

Practical Tips

  • Building a library? Use peerDependencies for framework packages so you don’t force a specific React version on your users. This gives them flexibility and keeps bundle size small.
  • Seeing duplicates? Run npm dedupe or yarn dedupe to ensure your park isn’t storing five copies of the same “bolt.”
  • Lock it down: Always commit package-lock.json (or yarn.lock). It’s the “as‑built” blueprint that guarantees every developer (and every CI run) gets the same versions.

Version Ranges – How We Tell Suppliers What to Send

SymbolNameMeaningExampleAcceptsRejects
^CaretMinor + Patch updates^1.2.31.2.3, 1.3.0, 1.9.92.0.0
~TildePatch‑only updates~1.2.31.2.3, 1.2.4, 1.2.91.3.0
(none)ExactExact version required1.2.31.2.3 only1.2.4
>=RangeGreater than or equal>=1.2.31.2.3, 1.3.0, 2.0.0

Understanding these symbols helps you control how permissive (or strict) your dependency versions are, keeping your park safe and up‑to‑date.

Version Compatibility

  • 1.2.0 “Our project trips the circuit and throws:

    SyntaxError: Cannot use import statement outside a module

In Part 2 I’ll walk through the technical “wiring” behind this problem:

  1. 15 A vs 5 A – Why ESM and CommonJS don’t just “plug and play.”
  2. The Short Circuit – Why the build breaks even when the code logic is perfect.
  3. The Heavy‑Duty Adapter – Solving the mystery of transpilePackages.

Stay tuned! ⚡ We’ll be fixing the wiring soon.

Till then, Pranipat 🙏

Back to Blog

Related posts

Read more »