Skip to main content

The Rise of Zero-Dependency Libraries

·PkgPulse Team
0

TL;DR

Zero-dependency packages are the most supply-chain-secure software you can install. No transitive vulnerabilities, no dependency conflicts, no lockfile bloat. In 2026, the best-designed libraries achieve full functionality with zero runtime dependencies — everything from state management (Zustand: 2KB, 0 deps) to ID generation (nanoid: 1KB, 0 deps) to event handling (mitt: 0.3KB, 0 deps). This is a deliberate design choice, not a constraint.

Key Takeaways

  • Zero deps = zero transitive risk — the supply chain attack surface is you + your code
  • Zustand, Jotai, nanoid, clsx, mitt — all zero runtime deps, all excellent
  • "0 dependencies" in package.json — check npm view package-name dependencies
  • Peer deps aren't the same — React as peer dep = you supply it, not the package
  • The trend is accelerating: new packages launched in 2024-2026 default to zero deps

Why Zero Dependencies?

The value proposition of zero dependencies:

1. Supply chain security
   → Each dependency is a potential attack vector
   → A package with 5 deps can introduce 50+ transitive packages
   → Zero deps = only your direct install is in scope for supply chain attacks
   → leftpad incident (2016): 1 package = internet broke
   → event-stream (2018): malicious code added 5 levels deep in dep tree

2. Bundle size
   → Each dep adds to bundle size
   → Zero deps = exactly what you see on bundlephobia
   → No hidden 40KB for a utility you only use 1 function from

3. Conflict avoidance
   → Version conflicts happen when multiple packages depend on same lib
   → Zero deps eliminates this class of problem entirely

4. Predictability
   → No surprise breaking changes from dependencies you didn't update
   → Your package.json changes are your app's changes

5. Trust
   → You've audited the package; you trust it
   → With dependencies, you've audited one step; then need to trust the chain

Zero-Dependency Packages Worth Knowing

State Management

// Zustand — 2KB, 0 runtime dependencies
import { create } from 'zustand';
// React is a peer dependency (you provide it; Zustand doesn't ship it)
// The entire state management system in 2KB with no transitive deps

// Jotai — 3.1KB, 0 runtime dependencies
import { atom, useAtom } from 'jotai';

// Valtio — 2.5KB, 0 runtime dependencies
import { proxy, useSnapshot } from 'valtio';
// React is peer dep

// These replaced Redux which has:
// redux: 0 deps ✅ (surprisingly)
// react-redux: 2 deps
// @reduxjs/toolkit: 5+ deps (immer, redux-thunk, reselect, etc.)
// → RTK requires ~40KB transitive packages

Utilities

// nanoid — 1.1KB, 0 dependencies
import { nanoid } from 'nanoid';
const id = nanoid();  // 21-char URL-safe ID

// clsx — 0.5KB, 0 dependencies
import { clsx } from 'clsx';
const classes = clsx('foo', { bar: true, baz: false });

// mitt — 0.3KB, 0 dependencies
import mitt from 'mitt';
const emitter = mitt<{ event: string }>();

// ms — 0.5KB, 0 dependencies
import ms from 'ms';
ms('2 days')  // 172800000

// klona — 1.1KB, 0 dependencies
import { klona } from 'klona';
const deep = klona(obj);  // Deep clone, faster than JSON.parse/stringify

// bytes — 0.8KB, 0 dependencies
import bytes from 'bytes';
bytes(1024 * 1024)  // '1MB'

HTTP

// In Node.js 18+: native fetch — 0 dependencies
const data = await fetch('https://api.example.com').then(r => r.json());
// No npm install needed

// undici — 0 dependencies (this is the actual Node.js HTTP impl)
import { request } from 'undici';
// Highest performance, built into Node.js core

Validation

// Valibot — designed to be tree-shakeable, 0 runtime deps
import { object, string, parse } from 'valibot';
// Each imported function is independent — zero unused code

// ArkType — 0 runtime dependencies
import { type } from 'arktype';
const user = type({ name: 'string', age: 'number' });

The Peer Dependency Distinction

# Zero dependencies ≠ no peer dependencies
# These are different:

# Dependencies (ships WITH the package, adds to your node_modules):
# npm view lodash dependencies  → {}  (zero deps — good)
# npm view react-router-dom dependencies  → { react, @remix-run/... } (ships deps)

# Peer dependencies (YOU provide these, NOT shipped by the package):
# npm view zustand peerDependencies  → { react: ">= 16.8" }
# This means: "zustand REQUIRES react but expects YOU to install it"
# Zustand itself has zero runtime dependencies

# How to check:
npm view package-name dependencies
# Empty object → zero runtime deps

npm view package-name peerDependencies
# Lists what you must provide but package doesn't ship

# The ideal pattern:
# peerDependencies: { react: "*" }  ← you control the react version
# dependencies: {}                   ← zero hidden packages
# devDependencies: { react, typescript, ... }  ← just for their tests

How Well-Designed Libraries Achieve Zero Deps

// Technique 1: Use Web Platform APIs instead of utility packages

// Instead of: npm install uuid
const id = crypto.randomUUID();  // Web Crypto API, built into Node 18+

// Instead of: npm install node-fetch
const data = await fetch(url).then(r => r.json());  // Built-in Node 18+

// Instead of: npm install deep-equal
const equal = JSON.stringify(a) === JSON.stringify(b);  // For JSON-safe objects
// Or: structuredClone comparison (Node 17+)

// Technique 2: Tiny implementation instead of heavy library

// Zustand's entire core (~200 lines of TypeScript):
// - Uses React.useSyncExternalStore (built-in React 18)
// - Subscription model using a Set (built-in JS)
// - No immer, no redux patterns, no middleware by default
// Result: complete state management in 200 lines, 0 deps

// Technique 3: Bundling micro-dependencies they control
// dayjs bundles all locales as optional plugin files
// No external dependency on a locale data package
// You get what you import, nothing extra

// Technique 4: Being opinionated about scope
// A good library does one thing and doesn't need 10 utilities to do it
// Scope creep requires dependencies; focused scope doesn't

The Math: How Dependencies Multiply

# Check how many packages a single dependency brings:

npm install --dry-run express 2>&1 | tail -3
# added 57 packages from 44 contributors

npm install --dry-run fastify 2>&1 | tail -3
# added 8 packages

npm install --dry-run zustand 2>&1 | tail -3
# added 1 package  ← just zustand itself

# The difference:
# express: 57 packages to audit, 57 potential attack vectors
# fastify: 8 packages
# zustand: 1 package

# At project scale, this compounds:
# A project with 20 direct deps might have 200-500 transitive deps
# Every one is in your supply chain

# Zero-dep packages: the "1 package" count is real
# They don't multiply

Zero-Dep Alternatives for Common Tasks

TaskWith DependenciesZero-Dep Alternative
Unique IDsuuid (14KB, 1 dep)crypto.randomUUID() built-in
Deep clonelodash.clonedeep (5KB)structuredClone() built-in
Directory creationmkdirp (0.5KB, 1 dep)fs.mkdirSync(path, {recursive:true})
Recursive deleterimraf (1KB, 1 dep)fs.rmSync(path, {recursive:true, force:true})
HTTP fetchnode-fetch (0.5KB)fetch() built-in Node 18+
Event emittereventemitter3 (2KB, 0 deps)mitt (0.3KB, 0 deps)
String colorschalk (1.5KB, varies)picocolors (0.3KB, 0 deps)
State managementRedux (10KB+)Zustand (2KB, 0 deps)
Date formattingmoment (72KB)dayjs (2.7KB, 0 deps)

The Zero-Dep Checklist

# Before installing any package, verify:

# 1. Check runtime deps:
npm view package-name dependencies
# Empty? → Zero deps ✅

# 2. Verify it's not cheating (bundling huge libs):
npx bundlephobia-cli package-name
# If zero deps but still 50KB+: it bundled something

# 3. Check peer deps (fine — you control these):
npm view package-name peerDependencies

# 4. Ask: is there a built-in alternative?
# Node.js 18-22 added: fetch, crypto.randomUUID, structuredClone,
#   fs.rm, fs.mkdir recursive, Web Streams API, AbortController
# Modern browsers: same APIs
# Check MDN/Node.js docs before npm installing anything

# 5. For utilities you do need: prefer the zero-dep option
# Same functionality, less risk, smaller bundle

Compare bundle sizes and dependency counts for npm packages at PkgPulse.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.