Hot Take: Most npm Packages Should Be stdlib 2026
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.
The Web Platform APIs That Replaced npm Packages
The stdlib absorption story is not exclusive to Node.js. The web platform — browsers and the APIs they expose — has been quietly eliminating npm dependencies on the frontend side with the same steady pace that Node.js has been eliminating them on the server side.
The pattern is now consistent enough to be a rule of thumb: for every common web utility that existed as a package in 2018, there is now usually a native API. fetch arrived in all major browsers years ago, meaning axios is unnecessary for simple request/response patterns. AbortController provides a standard mechanism for canceling in-flight requests without a wrapper library. The URL and URLSearchParams constructors handle URL manipulation cleanly, replacing the qs and similar parsing packages for most use cases. structuredClone handles deep copying including Date, RegExp, Set, and Map instances — no package needed. crypto.randomUUID() is available in all modern browsers, not just Node.js. ResizeObserver and IntersectionObserver handle DOM observation patterns that previously required third-party SDKs or manual scroll event listeners. The Intl.DateTimeFormat and Intl.RelativeTimeFormat APIs replace significant slices of Moment.js and date-fns usage, particularly for the common pattern of formatting dates for display. Intl.Segmenter handles text segmentation tasks that once required dedicated packages.
The CSS platform has also absorbed functionality that packages used to provide. CSS custom properties replace many theming library responsibilities. CSS animations and transitions reduce the need for JavaScript animation packages for all but the most complex motion work. CSS grid and flexbox layouts reduce the need for utility classes from external layout libraries.
The service worker API enables offline functionality and push notifications that previously required substantial third-party SDK code. The message for frontend projects is the same as for Node.js projects: before installing a package that wraps a platform API with a thin abstraction layer, check whether the platform API is now good enough to use directly. In the majority of cases in 2026, it is.
The Temporal API: The Last Big Date-Related Migration
Among all the stdlib improvements on the horizon, Temporal is the most consequential for npm's download numbers. It addresses the single largest remaining gap in JavaScript's standard library: a comprehensive, correct, timezone-aware date and time API.
The current Date object is notoriously broken — mutable by default, timezone handling is error-prone, month indexing is zero-based while day indexing is one-based, and arithmetic requires manual calculation that is easy to get wrong across DST boundaries. The result is that 40 million npm downloads per week flow to moment, date-fns, dayjs, luxon, and related packages. Every one of those downloads is a band-aid over a platform deficiency.
Status in 2026: the Temporal proposal reached Stage 3 in TC39, meaning the API is stable enough to polyfill against. Browser support is partial — Chrome and Firefox have experimental implementations behind flags. Node.js support is expected in Node 24 or 25 as V8's Temporal implementation stabilizes. The @js-temporal/polyfill package is available for projects that want to adopt the API now.
The migration implication for existing codebases is significant: when Temporal reaches full browser and Node.js support, dayjs and date-fns will occupy the same position that Moment.js occupies today — technically functional, widely used, but unnecessary wrappers around an adequate platform API. The transition period running roughly from 2026 through 2028 is the window when engineering teams should monitor Temporal's stability and plan eventual migration.
The practical recommendation for new projects in 2026: use date-fns or Day.js today. Both are zero-dependency, well-maintained, and have large enough install bases that they will be maintained through the Temporal transition. Do not migrate to Temporal based on the polyfill in production — the polyfill carries non-trivial bundle size and the edge cases are not yet battle-tested at scale. Wait for stable runtime support before committing to the native API in production code.
The Historical Pattern: Popular Packages Becoming stdlib
The absorption of popular npm packages into Node.js core or the web platform follows a consistent historical pattern that is worth understanding explicitly. It is not a new phenomenon. The cycle begins with a real gap in the platform, which the ecosystem fills with npm packages. As the packages accumulate significant adoption and their API surfaces stabilize, the platform standardizes on an equivalent — sometimes adopting the npm package's API, sometimes designing something cleaner. The npm package then either declines into irrelevance or persists through institutional inertia.
The querystring module is an early example. Node.js shipped its own querystring module years before qs became the dominant URL parameter parsing package. The built-in module was limited — it did not handle nested objects, arrays, or complex serialization cases — so the ecosystem reached for qs. When the WHATWG URLSearchParams API arrived in Node.js 10, it replaced simple querystring use cases entirely. For more complex serialization, qs persists with 50 million weekly downloads, partially as inertia and partially because its nested object handling still has no native equivalent.
The path module tells a related story. Node.js has always shipped a path module, but its handling of cross-platform path separators and URL-to-path conversion was incomplete enough that packages like slash and upath accumulated millions of downloads specifically to normalize Windows path separators. As the web platform converged on the URL API for path manipulation in browser contexts, the practical need for these packages in isomorphic code diminished significantly.
The pattern for predicting which current packages will be absorbed next: look for packages that implement a standardized specification (a W3C spec, a TC39 proposal, an IETF RFC) rather than a novel algorithm. Platform implementations tend to target specifications. A package implementing a specification is a strong candidate for native replacement when the platform catches up with the spec; a package implementing a genuinely novel algorithm is not going anywhere because there is no specification for the platform to adopt.
The Risk of Depending on Packages That May Become stdlib
The stdlib absorption cycle creates a specific category of dependency risk that is easy to miss: the risk that a package you depend on will be superseded by a native API with an incompatible interface. This is not hypothetical — it has already happened with node-fetch and browser fetch, and the API surfaces are similar enough that the migration was simple. But the risk case is when the native API diverges meaningfully from the package's API, leaving developers with a migration rather than a simple removal.
The crypto.randomUUID() case is instructive. The uuid package has supported v1, v4, v5, v7, and other UUID variants for years and has built significant API surface around UUID generation, validation, and parsing. The native crypto.randomUUID() generates only v4 UUIDs. For the majority of use cases — generating a random unique identifier — the native function is a drop-in replacement. For use cases that rely on uuid's parse and validate utilities, or that need UUID v7's time-ordered properties, the native function is not a replacement and the package remains necessary. The API incompatibility here is partial rather than total, which creates the messy situation where some callsites can remove the import and others cannot.
The URL API transition from Node's legacy url module illustrates a cleaner resolution: the WHATWG URL class is genuinely superior to the legacy module, the migration is straightforward, and the old module persisted only for backward compatibility reasons, not because any new code prefers it. The Node.js team deprecated the legacy url module's functional API in documentation without removing it, which is the correct approach — it allows migration without breaking existing code.
For teams choosing dependencies today, the question worth asking about any package implementing a web platform spec is: what happens to my code when this becomes native? If the answer is "I remove the import and everything still works," the risk is low. If the answer is "I would need to audit every callsite for API differences," the risk is higher and worth factoring into the initial dependency decision.
Which Current Popular Packages Are stdlib Candidates
Looking at the current npm package landscape through the lens of "which of these is filling a platform gap rather than solving a novel problem," several strong candidates emerge for eventual platform absorption — or are already far enough along in the standards process that the timing is more a question of when than if.
p-limit, with around 15 million weekly downloads, implements concurrency control for promise-based async operations. Its API — limiting simultaneous in-flight promises to a maximum count — is a fundamental pattern in async programming that has no native JavaScript equivalent. The TC39 proposal for AsyncContext addresses related territory (propagating context across async operations), but concurrency limiting specifically is not on any near-term standardization path. p-limit is likely to remain a healthy npm package for the foreseeable future rather than being absorbed.
zod and similar schema validation libraries occupy territory that TypeScript's type system handles at compile time but JavaScript cannot express at runtime without additional tooling. The TC39 Type Annotations proposal would allow TypeScript-style annotations in JavaScript syntax without affecting runtime behavior — it explicitly does not solve runtime validation. There is no active proposal for built-in schema validation, making zod's 12 million weekly downloads structurally safe from stdlib absorption. It is filling a real gap that the platform has no current plans to close.
dotenv, with 14 million weekly downloads, is perhaps the starkest example of a platform gap. Every server-side runtime except Node.js handles environment configuration natively. Bun supports .env file loading out of the box without a package. Deno supports it natively. The Node.js team has discussed native .env file loading in multiple GitHub issues, and Node.js 20.6 actually added experimental --env-file flag support. The path for dotenv's eventual obsolescence in Node.js projects is clearer than for almost any other high-download package — it is implementing a feature the runtime has started to provide directly.
Lodash's groupBy function, which has been one of the library's most-used methods, crossed the finish line with Object.groupBy reaching Stage 4 in TC39 and shipping in V8. For new code targeting Node.js 21+ and modern browsers, Object.groupBy is a direct replacement. This is the pattern playing out in real time: the specific lodash function that addressed a real language gap is becoming unnecessary as the language closes the gap, even while the remaining 80% of lodash's API surface continues to address stable algorithmic needs that the platform is not racing to absorb.
Why the npm Ecosystem Is Still Healthy Despite Stdlib Absorption
A reading of the stdlib absorption trend that concludes "npm is shrinking in importance" misses the actual dynamics. The packages being absorbed are systematically the simplest, most specification-driven ones — thin wrappers over functionality that platforms were always going to implement. The packages that address genuinely complex problems, implement sophisticated algorithms, or solve coordination problems across multiple platform capabilities are not candidates for absorption and are not at risk.
The npm packages that are thriving and growing in 2026 are solving problems the platform cannot credibly address: UI component systems, state management patterns, build tooling with complex dependency graphs, authentication flows that integrate dozens of services, testing frameworks that understand TypeScript, ORM layers that abstract multiple database backends. None of these are stdlib candidates. They are frameworks and tools that require ecosystem-level maintenance investment far beyond what a runtime team can sustain.
The net effect of stdlib absorption is to raise the floor of what you can do without npm rather than to lower the ceiling of what npm packages can accomplish. Developers writing a simple Node.js script in 2026 need far fewer packages than they did in 2018 — fetch is built in, UUID generation is built in, URL parsing is built in, test running is built in. But developers building production applications still reach for the same sophisticated npm packages for the complex parts of their stack, because those packages are solving problems the platform has no ambition to solve.
The right frame for the hot take in this article's title is not that npm packages are unnecessary — it is that the specific category of packages filling platform gaps is shrinking, leaving behind the packages that are doing real work. A smaller npm ecosystem where the 3 million packages have been filtered down to the ones that earn their dependency weight is a healthier outcome than the status quo where millions of those packages exist as polyfills for features the platform has since shipped. The trend is moving slowly and will take years to materially reduce install counts for the affected packages, but the direction is correct.
The Inertia Problem: Why Obsolete Packages Keep Getting Downloaded
Understanding why packages like node-fetch, uuid, and object-assign maintain tens of millions of weekly downloads long after native equivalents have shipped requires understanding how dependency inertia works in large ecosystems. It is not primarily a developer choice to keep using the package — it is a consequence of how transitive dependencies propagate through the npm graph.
When a popular library that was built before native APIs existed includes one of these packages as a dependency, every project that uses that library installs the polyfill package transitively, even if the project itself is running on a Node.js version where the native implementation has been available for years. The library author may have moved on, the package may be in security-only maintenance, but the dependency declaration in its package.json ensures that millions of fresh installs continue to occur weekly. This is the mechanism by which object-assign — implementing Object.assign(), which has been in every JavaScript engine since 2015 — still accumulates tens of millions of weekly downloads a decade later.
The fix requires library maintainers to audit their own dependencies and drop polyfills as they raise their minimum Node.js version requirements. This is happening: the ecosystem's convergence on Node.js 18 LTS as the minimum supported version for new major releases of major libraries has enabled a wave of polyfill removal. When a library raises its minimum requirement to Node.js 18, it can drop node-fetch, FormData polyfills, and several other packages that were previously needed for compatibility. Each such release reduces transitive installs of the polyfill package across every project that updates to the new library version.
The transition is slower than the native API availability because of the lag between a runtime version being released, becoming LTS, and becoming the ecosystem-wide minimum. Node.js 18 became LTS in October 2022 and was the minimum requirement for many libraries only by 2024. The inertia period — from native API availability to widespread ecosystem cleanup — is typically two to three years. For node-fetch, that window is closing. For uuid, it is partially closed (crypto.randomUUID replaces v4 but not all variants). For lodash.clonedeep, it closed when structuredClone landed and libraries updated their minimums.
Explore npm package download trends and health scores at PkgPulse.
See also: AVA vs Jest and The Bun Effect: New Runtime vs npm Ecosystem, The ESM vs CJS Adoption Gap Across npm.
See the live comparison
View pnpm vs. npm on PkgPulse →