TL;DR: The browser caught up while you weren't looking. As of 2026, all 13 of these APIs are Baseline — supported across every major browser — so you can finally delete moment, lodash, axios, uuid, numeral, clipboard.js and more. Running tally at the bottom: ~115 KB gzipped, gone. Copy-paste code for each.
You keep reaching for a package out of muscle memory. But "Baseline" now means these APIs work everywhere that matters — so the dependency you're about to install is dead weight. Here's what you can delete today, with the bundle savings for each.
1. structuredClone() → delete lodash.clonedeep (−5 KB)
JSON.parse(JSON.stringify(x)) silently mangles Date, Map, Set, and undefined. The platform has a real deep clone:
const state = {
user: { name: "Ada" },
visited: new Set([1, 2, 3]),
updatedAt: new Date(),
};
const copy = structuredClone(state);
copy.user.name = "Grace";
state.user.name; // "Ada" — untouched
copy.visited instanceof Set; // true, real Set preserved
Handles Maps, Sets, Dates, typed arrays, and circular refs. (Caveats in Gotchas.)
2. crypto.randomUUID() → delete uuid (−1 KB)
const id = crypto.randomUUID();
// "1e7c8b2a-5f3d-4a1b-9c2e-8d6f0a3b1c4d"
Spec-compliant v4, cryptographically random. Only needs a secure context (HTTPS or localhost) — which you already have.
3. Intl.RelativeTimeFormat + a 6-line helper → delete moment (−72 KB)
This is the big one. Moment does compute the delta and pick the unit for you — the native API doesn't, so pair it with a tiny helper (this is the part everyone trips on):
const DIVISIONS = [
{ amount: 60, unit: "second" },
{ amount: 60, unit: "minute" },
{ amount: 24, unit: "hour" },
{ amount: 7, unit: "day" },
{ amount: 4.34524, unit: "week" },
{ amount: 12, unit: "month" },
{ amount: Infinity, unit: "year" },
];
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
function timeAgo(date) {
let duration = (date - Date.now()) / 1000; // seconds; negative = past
for (const { amount, unit } of DIVISIONS) {
if (Math.abs(duration) < amount) return rtf.format(Math.round(duration), unit);
duration /= amount;
}
}
timeAgo(new Date(Date.now() - 3 * 60_000)); // "3 minutes ago"
numeric: "auto" gives you "yesterday" for free, localized in every language the browser ships — no locale bundles.
4. Intl.NumberFormat → delete numeral.js (−3.6 KB)
Currency, units, and compact notation, all localized:
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(1234.5);
// "$1,234.50"
new Intl.NumberFormat("en", { style: "unit", unit: "kilometer-per-hour" }).format(88);
// "88 km/h"
new Intl.NumberFormat("en", { notation: "compact" }).format(1_200_000);
// "1.2M"
5. URLSearchParams → delete query-string (−4 KB)
const params = new URLSearchParams({ page: "2", sort: "new" });
params.append("tag", "webdev");
params.toString(); // "page=2&sort=new&tag=webdev"
const q = new URLSearchParams(location.search);
q.get("page"); // "2", correctly decoded
Handles encoding and repeated keys. (It does not do qs-style nested objects — see Gotchas.)
6. navigator.clipboard → delete clipboard.js (−2.5 KB)
await navigator.clipboard.writeText("Copied!");
One line, returns a Promise. Secure context required, and it must run from a user gesture (a click handler).
7. <dialog> → delete your modal library (−5 KB)
Focus trap, backdrop, Esc-to-close, focus restore — all native:
<dialog id="confirm">
<form method="dialog">
<p>Delete this item?</p>
<button value="cancel">Cancel</button>
<button value="ok">Delete</button>
</form>
</dialog>
const dialog = document.getElementById("confirm");
dialog.showModal(); // focus trap + ::backdrop + Esc, all free
dialog.addEventListener("close", () => console.log(dialog.returnValue)); // "ok" | "cancel"
8. IntersectionObserver → delete lazy-load / infinite-scroll libs (−4 KB)
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) if (entry.isIntersecting) loadMoreItems();
}, { rootMargin: "200px" }); // fire before it's visible
observer.observe(document.querySelector("#sentinel"));
Same API powers lazy images, infinite scroll, and scroll animations — off the main thread.
9. ResizeObserver → delete element-resize libraries (−2 KB)
Element-level resize reactions without polling or window.resize hacks:
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
entry.target.classList.toggle("is-narrow", entry.contentRect.width < 400);
}
});
ro.observe(document.querySelector(".card"));
For pure styling, CSS container queries now cover a lot of this too.
10. AbortController → delete axios cancel tokens (−13 KB)
fetch covers most of what people import axios for — including cancellation and timeouts:
// Cancel a stale request
const controller = new AbortController();
fetch("/search?q=native", { signal: controller.signal });
controller.abort();
// Timeout in one line — no wrapper library
const res = await fetch("/slow", { signal: AbortSignal.timeout(5000) });
Lightning round: 3 more one-liners (−4 KB total)
Object.groupBy → lodash.groupBy:
const byType = Object.groupBy(files, (f) => f.type); // { image: [...], pdf: [...] }
EventTarget + CustomEvent → mitt / tiny emitter libs:
const bus = new EventTarget();
bus.addEventListener("login", (e) => console.log(e.detail.user));
bus.dispatchEvent(new CustomEvent("login", { detail: { user: "Ada" } }));
matchMedia → media-query libs:
const mq = matchMedia("(min-width: 768px)");
mq.addEventListener("change", (e) => console.log(e.matches ? "desktop" : "mobile"));
Gotchas: when NOT to reach for the native API
Native isn't a drop-in every time. The honest caveats:
-
Intl.RelativeTimeFormatdoesn't do the math. It formats a value+unit you give it — it won't compute "how long ago" or auto-pick the unit. That's why #3 ships a helper. If you have dozens of relative-time formats with fuzzy rules, a 2KB lib may still earn its place. -
URLSearchParamsis flat. Noqs-style nested/bracket parsing (?filter[status]=open&tags[]=a). Deep query objects → keepqs. -
structuredClonedrops the un-cloneable. Functions, DOM nodes, class prototypes (you get a plain object back), and getters/setters don't survive. It's for data, not live instances. -
Object.groupByandAbortSignal.any()are the newest here — Baseline, but only since 2024 (Safari 17.4+, recent Chrome/Firefox). If you support older browsers, feature-detect or polyfill these two specifically. - Clipboard & UUID need a secure context; clipboard also needs a user gesture. Fine in prod, annoying on plain-HTTP dev boxes.
The tally
| Deleted | ~KB gzipped |
|---|---|
| moment * | 72 |
| axios | 13 |
| lodash (clonedeep + groupBy) | 7 |
| modal lib | 5 |
| lazy-load lib | 4 |
| query-string | 4 |
| numeral.js | 3.6 |
| clipboard.js | 2.5 |
| resize lib | 2 |
| emitter + media-query libs | 2 |
| uuid | 1 |
| Total | ~115 KB |
* 72 KB = moment's default all-locales bundle; ~18 KB core-only. Even at core-only, it's a dependency you no longer need.
All KB figures are gzipped, sourced from bundlephobia.com (Aug 2026) — audit any of them yourself.
That's ~115 KB of gzipped JavaScript your users no longer download — and 11 fewer things in npm audit. Before your next npm install, spend 60 seconds asking the 2026 question: did the platform already ship this at Baseline? More often than not now, it did.
Which of these are you deleting first — and which lib do you think still earns its spot in package.json in 2026? Drop it in the comments.
Top comments (0)