Skip to main content

The Rise of Zero-Dependency Libraries

·PkgPulse Team

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.

Comments

Stay Updated

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