Skip to main content

Hot Take: Most npm Packages Should Be stdlib

·PkgPulse Team

TL;DR

The npm ecosystem exists partly because JavaScript's standard library is impoverished. We install lodash for array operations, uuid for IDs, path for file paths, and dotenv for environment variables — all things Python, Ruby, and Go ship out of the box. Node.js 18-22 has been quietly filling these gaps: built-in fetch, crypto.randomUUID(), URL parsing, and more. This is a hot take: many of the most-downloaded npm packages are band-aids over language/runtime deficiencies. Here's what we have now, and what we still need.

Key Takeaways

  • Node.js 18-22 added: fetch, crypto.randomUUID(), URL API, readline, structuredClone, Web Streams
  • Still missing: proper date manipulation, deep equality, EventEmitter (in browsers), rich collections
  • Already removable from most projects: node-fetch, uuid, url, cross-fetch, FormData polyfills
  • The Temporal API (Stage 3) will finally kill Moment.js, date-fns, dayjs at the platform level
  • The WHATWG push: aligning Node.js APIs with browsers = fewer polyfill packages needed

What Node.js Has Quietly Replaced

// Things you no longer need to install:

// 1. node-fetch → built-in fetch (Node.js 18+)
// Before:
import fetch from 'node-fetch';
const res = await fetch('https://api.example.com/data');
// After:
const res = await fetch('https://api.example.com/data');  // Built-in!
// npm: node-fetch has 73M weekly downloads. Mostly inertia now.

// 2. uuid → crypto.randomUUID() (Node.js 14.17+, all browsers)
// Before:
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// After:
const id = crypto.randomUUID();  // Built-in!
// npm: uuid has 28M weekly downloads. ~90% could use this instead.

// 3. url module → WHATWG URL API
// Before:
const url = require('url');
const parsed = url.parse('https://example.com/path?q=1');
// After:
const parsed = new URL('https://example.com/path?q=1');
parsed.hostname;  // "example.com"
parsed.searchParams.get('q');  // "1"
// Built-in for years. Still: url has 46M weekly downloads.

// 4. FormData polyfills → built-in FormData
// Node.js 18+ includes FormData natively
const form = new FormData();
form.append('file', buffer, 'filename.txt');
// No package needed.

// 5. structuredClone → built-in
// Before:
const deep = JSON.parse(JSON.stringify(obj));  // Lossy
// Or:
import cloneDeep from 'lodash/cloneDeep';
// After:
const deep = structuredClone(obj);  // Handles Date, RegExp, Set, Map
// Built-in Node.js 17+, all modern browsers.

The Lodash Problem

lodash: 28M weekly downloads, 71KB gzipped

What lodash provides:
→ Array operations (flatten, chunk, groupBy, uniq)
→ Object operations (merge, pick, omit, get)
→ String operations (camelCase, kebabCase, truncate)
→ Function utilities (debounce, throttle, memoize)
→ Math utilities (sum, mean, min, max)
→ Type checking (isNull, isUndefined, isArray)

What JavaScript already has (ES2015-2024):
→ Array.from(), Array.of(), Array.prototype.flat(), .flatMap() — covers 40% of lodash array ops
→ Object.assign(), Object.entries(), Object.fromEntries() — covers 50% of lodash object ops
→ Array.prototype.findIndex(), .find() — built-in
→ Spread syntax for many merge operations
→ Optional chaining (?.) for nested property access — kills _.get()
→ Nullish coalescing (??) — kills many lodash guards
→ structuredClone — kills _.cloneDeep()
→ Array.from(new Set(arr)) — kills _.uniq()

What JavaScript still doesn't have (where lodash shines):
→ _.groupBy() — no built-in (Object.groupBy is Stage 4, lands soon)
→ _.debounce() / _.throttle() — no built-in equivalent
→ _.merge() deep merge — spread only does shallow
→ _.chunk() — no built-in
→ _.difference(), _.intersection() — no built-in
→ _.pick() / _.omit() — Object destructuring covers most cases

Honest take: lodash is still useful for ~20% of its API.
The other 80% is filling gaps that the language has now closed.

What the Temporal API Will Kill

The Temporal API (Stage 3 TC39, shipping in engines 2025-2026):
→ Replaces the entire JavaScript date ecosystem

Current date handling packages and their downloads:
moment:    14M/week (deprecated! still downloaded)
date-fns:  14M/week
dayjs:      8M/week
luxon:      3M/week
chrono-node: 1M/week

Total: ~40M downloads/week for date handling.

Temporal will replace all of these:

Current (date-fns):
import { format, addDays, parseISO, differenceInDays } from 'date-fns';
const date = parseISO('2026-03-08');
const nextWeek = addDays(date, 7);
const formatted = format(nextWeek, 'MMM d, yyyy');

Temporal (no package needed):
const date = Temporal.PlainDate.from('2026-03-08');
const nextWeek = date.add({ days: 7 });
const formatted = nextWeek.toLocaleString('en-US', { dateStyle: 'medium' });

Temporal advantages:
→ Immutable (no mutation bugs like new Date())
→ Timezone-aware from the start
→ ISO 8601 by default
→ Duration arithmetic built-in
→ Calendar system support
→ No package to install, no security vulnerabilities

Timeline:
→ TC39 Stage 3 since 2021
→ polyfill available: npm install @js-temporal/polyfill
→ Chrome/V8 shipping in 2025
→ Node.js following browser engines

In 5 years: date-fns/dayjs/moment downloads will look like jQuery's decline.
In 2 years: most new projects should use the polyfill.

The Deeper Problem: Platform Neglect

Why does npm have 3 million packages?
Some are innovation. Many are filling language/platform gaps.

JavaScript's stdlib compared to other languages:

Python stdlib (batteries included):
→ json, csv, xml, html.parser
→ datetime, calendar
→ pathlib (rich file paths)
→ itertools (groupby, chain, combinations)
→ collections (Counter, defaultdict, OrderedDict)
→ functools (reduce, partial, cache/lru_cache)
→ re (full regex)
→ uuid
→ hashlib (crypto)
→ urllib (HTTP client)
→ email, smtp
→ sqlite3

Go stdlib:
→ net/http (full HTTP server + client)
→ encoding/json, encoding/csv, encoding/xml
→ time (comprehensive)
→ io/fs (file system)
→ testing (test runner!)
→ database/sql
→ crypto/* (comprehensive)

Node.js stdlib (what you can actually use without npm):
→ fs, path, os — file system
→ http, https, net — networking (low-level)
→ crypto — crypto (comprehensive)
→ url — URL parsing
→ querystring — URL params
→ stream — streams
→ events — EventEmitter
→ worker_threads — workers
→ child_process — subprocesses

Missing from Node.js that other platforms have:
→ No test runner (jest, vitest, mocha) until Node 18 (now has --test)
→ No date manipulation (Temporal coming)
→ No HTTP client (fetch finally added Node 18)
→ No task queue (setTimeout is not a task queue)
→ No deep equality check
→ No rich collections (Set has no union/intersection/difference)
→ No string templating beyond basic
→ No config file loading
→ No ENV file loading (dotenv: 14M/week downloads!)

dotenv has 14M weekly downloads.
dotenv loads a .env file.
That's it.
Every server-side platform has this built in except Node.js.

What You Can Drop Right Now (2026)

# Packages you can remove from Node.js 18+ projects:

npm uninstall node-fetch cross-fetch whatwg-fetch
# → Use built-in fetch()

npm uninstall uuid
# → Use crypto.randomUUID()

npm uninstall lodash.clonedeep
# → Use structuredClone()

npm uninstall object-assign
# → Use Object.assign() or spread

npm uninstall path-browserify  # In browser-targeted code
# → Use URL API instead of path manipulation

# Polyfills you can remove (if targeting Node 18+ or modern browsers):
npm uninstall promise  # native Promise
npm uninstall es6-map  # native Map
npm uninstall es6-set  # native Set
npm uninstall core-js  # (if you're targeting modern browsers only)

# Audit your package.json:
# For each package: "Is there a native equivalent in Node 18+?"
# Many packages exist purely as polyfills. If you're on modern Node.js, cut them.

# The expected result:
# Most Node.js apps can drop 10-20 packages by using built-ins
# Bundle size reduction: 50-200KB
# Security attack surface reduction: significant

The Vision: Node.js as a Platform You Can Actually Use Without npm

The trend is going the right way:
→ Node.js 18: fetch, FormData, Web Streams, structuredClone
→ Node.js 20: stable test runner, permission model
→ Node.js 22: Web Crypto stable, readline improvements
→ Temporal: Stage 3, shipping 2025-2026
→ Object.groupBy: Stage 4, ships in engines 2024
→ Set methods (union, intersection, difference): Stage 4, shipping

The community is pushing for more:
→ The Deno project's thesis: Node.js + good stdlib = fewer package problems
→ Deno 1.x shipped with much richer stdlib
→ Bun ships with extensive built-in utilities
→ Both push Node.js to close the gap

The hot take restated:
The npm ecosystem's size is partly a sign of language health (innovation)
and partly a sign of platform failure (filling gaps that should be stdlib).
As Node.js's platform improves, the packages filling gaps will fade.
The packages solving real problems will remain.
3 million npm packages will slowly become 2 million packages
that actually matter.

Explore npm package download trends and health scores at PkgPulse.

See the live comparison

View pnpm vs. npm on PkgPulse →

Comments

Stay Updated

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