Skip to main content

The Most Depended-On npm Packages (And Their Alternatives)

·PkgPulse Team

TL;DR

A handful of packages support the entire npm ecosystem. Semver, minimatch, glob, lodash, chalk — these have millions of dependent packages and are effectively critical infrastructure. The famous leftpad incident of 2016 (75 lines, 11 dependents) broke the internet. Today's risk is concentrated in hundreds of utility packages that everything else depends on. Knowing the dependency tree of your dependencies is supply chain security.

Key Takeaways

  • semver has 95M dependents — every package manager depends on it
  • lodash has 160K direct dependents — slowly declining as native JS covers more ground
  • glob/minimatch: 60M+ dependents — used by every build tool on the planet
  • The real risk: single-maintainer packages deep in your dependency tree
  • Modern alternatives exist for most foundational packages — often built-in to Node.js

Tier 1: The Foundation (Millions of Dependents)

semver — ~95M dependents

npm install semver
# Used by: npm, yarn, pnpm, every package manager, every tool that reads package.json
# What it does: parses and compares semantic version strings
# Maintainer: npm (npm, Inc. / GitHub)
# Security track record: CVE-2022-25883 (ReDoS) — patched quickly
# Risk level: LOW (well-funded, institutional maintenance)

import semver from 'semver';
semver.satisfies('1.2.3', '^1.0.0')  // true
semver.gt('2.0.0', '1.9.9')          // true

glob / minimatch — ~60M dependents combined

# glob: file system glob pattern matching
# minimatch: the pattern matching engine underneath
# Used by: webpack, jest, eslint, prettier, TypeScript, every build tool

# Modern alternative: fast-glob (actively developed, faster)
npm install fast-glob  # 3x faster than glob, same patterns

# Or: Node.js 22+ built-in glob
import { glob } from 'node:fs/promises';
const files = await glob('src/**/*.ts');  // Native, no npm needed

chalk — ~50M dependents

# Terminal string styling: colors, bold, underline
# Used by: every CLI tool, jest output, webpack, prettier, eslint

import chalk from 'chalk';
console.log(chalk.blue('Hello'));
chalk.bold.red('Error message');

# Alternative for zero-dep: picocolors (3x smaller, faster)
import pc from 'picocolors';
console.log(pc.blue('Hello'));

# Note: chalk v5 is ESM-only; chalk v4 for CommonJS projects

Tier 2: The Utility Layer (100K+ Dependents)

lodash — ~160K direct dependents

# JavaScript utility library: arrays, objects, functions, strings
# At peak: 35M weekly downloads
# Now: ~28M weekly downloads (declining slowly as native JS improves)
# Dependents: ~160K packages depend on it

# Why it persists:
# 1. Extremely battle-tested (20+ years)
# 2. Zero dependencies
# 3. Handles edge cases that native methods don't

# What native JS now covers:
# _.cloneDeep → structuredClone() (Node 17+)
# _.flatten → Array.flat()
# _.get → optional chaining ?.
# _.defaultTo → nullish coalescing ??
# _.uniq → [...new Set(arr)]

# Still better in lodash:
# _.merge (deep merge) → no native equivalent
# _.throttle, _.debounce → use lodash or a tiny package
# _.isEqual (deep equality) → no clean native alternative

is-xxx packages (100+ packages, millions of dependents)

# is-string, is-number, is-array, is-object, is-plain-object...
# These tiny packages exist because JavaScript typeof is notoriously inconsistent

# typeof null === 'object'  ← famous JS quirk
# Array.isArray() exists but Object.isObject() doesn't

# Modern replacement: TypeScript + Zod handle this at type level
# Runtime: use built-ins where possible

# is-number → typeof n === 'number' && !isNaN(n)
# is-array → Array.isArray(arr)
# is-string → typeof s === 'string'

debug — ~65K dependents

# Tiny debug logging utility used by countless packages
npm install debug
# DEBUG=express:* node app.js  ← enables debug output

# You've likely seen: "[express] router: dispatching GET /" in your console
# That's debug in action — used by express, mongoose, socket.io, many more

# Modern replacement: pino child loggers (for your own code)
# But debug in dependencies: just accept it, it's stable

Tier 3: Security-Critical Utilities

node-forge / node-crypto wrappers — varies

# Many packages wrap Node.js crypto for common operations
# These are high-risk: cryptography bugs can be silent and catastrophic

# Key packages in dependency trees:
# - bcryptjs: password hashing
# - jsonwebtoken (jwt): auth tokens
# - node-forge: TLS/cryptography primitives

# Best practice: prefer packages with:
# ✅ Active CVE scanning (Snyk or GitHub security advisories)
# ✅ Recent security review by a third party
# ✅ High download counts (more eyes = faster bug detection)
# ✅ Zero dependencies or well-audited deps only

parse libraries (csv-parse, xml2js, etc.)

# Input parsing libraries are common attack vectors:
# - Buffer overflows from malformed input
# - ReDoS from regex on user input
# - Prototype pollution (fixed in most, but check older versions)

# Example: prototype pollution was found in:
# - lodash <4.17.21
# - minimist <1.2.6
# - ajv (older versions)
# - Set of deep merge utilities

# Check your dep tree:
npm audit  # Will catch known CVEs
# Or: use Socket.dev for behavioral analysis

The Real Supply Chain Risk: Single-Maintainer Utilities

# The highest risk isn't lodash (institutional backing)
# It's the single-maintainer utility package buried in your dep tree

# How to find high-risk packages in YOUR project:
npx snyk test --all-projects
# Shows: dependency tree + vulnerability data

# Or: list all packages with single maintainer
npm ls --all 2>/dev/null | while read pkg; do
  echo "Checking $pkg..."
  npm view "$pkg" maintainers --json
done
# (simplified — use snyk or Socket for this in practice)

# What "high risk" looks like:
# - 1 maintainer, last active 18+ months ago
# - 50K+ packages depending on it
# - No corporate backing
# - npm 2FA not enabled (npm access 2fa-not-required)

# Real example from 2025: a single-maintainer utility
# package deep in 30K+ packages' dep trees was abandoned
# for 18 months with an unpatched ReDoS vulnerability

Alternatives to Foundational Packages

PackageDependentsBetter AlternativeReason
glob30M+fast-glob3x faster, actively maintained
chalk50M+picocolors5x smaller, no deps
uuid40K+crypto.randomUUID()Built-in Node 18+
minimist40K+yargs-parserSafer, actively maintained
request75K+undici, ky, fetchrequest is deprecated
node-fetch25K+native fetchBuilt-in Node 18+
lodash.clonedeep60K+structuredClone()Built-in Node 17+
moment40K+dayjs, date-fns72KB → 2.7KB
rimraf50K+fs.rm()Built-in Node 14.14+
mkdirp30K+fs.mkdir recursiveBuilt-in Node 10.12+

Dependency Minimization Strategy

# Goal: reduce your attack surface

# Step 1: Find unused dependencies
npx depcheck
# Reports: Unused dependencies you can remove

# Step 2: Check bundle size of each dep
npx cost-of-modules --no-install

# Step 3: For each dep, ask:
# - Is there a Node.js built-in that does this now?
# - Is there a smaller alternative?
# - Is this dep maintained?
# - How many transitive deps does it pull in?

npm view package-name --json | jq '.dependencies | keys | length'
# 0 deps = much safer (no transitive risk)

# Step 4: For production servers, audit regularly:
npm audit --audit-level=moderate
# Review and address before they become critical

See dependency counts, health scores, and alternatives for any npm package at PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.