Open your package.json and look at the small stuff. Not React or your
framework, the little helpers: a deep clone here, a UUID there, a date library
you pulled in to print "3 hours ago."
A lot of those are now built into the browser (and modern Node). The platform
quietly grew up while we kept installing the same habits. Here are seven you can
probably delete today, with the before and after.
1. Deep clone: structuredClone()
The old reflex is JSON.parse(JSON.stringify(obj)), or reaching for
lodash.cloneDeep. The JSON trick silently corrupts your data: it turns Date
objects into strings, drops undefined, and throws on anything cyclic.
// before
import cloneDeep from 'lodash.clonedeep';
const copy = cloneDeep(state);
// after, built in
const copy = structuredClone(state);
structuredClone handles Date, Map, Set, typed arrays, and cyclic
references correctly. It cannot clone functions or DOM nodes, which is usually
what you want anyway.
2. UUIDs: crypto.randomUUID()
The uuid package is one of the most downloaded on npm. For a v4 UUID you no
longer need it.
// before
import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// after, built in
const id = crypto.randomUUID();
One gotcha: crypto.randomUUID() only runs in a secure context (HTTPS or
localhost). In Node it lives on the same global from node:crypto.
3. Dates, numbers, and "time ago": Intl
Most of the time we import moment or date-fns just to format something. Intl
does formatting natively, localized, with zero dependencies.
// currency, localized
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(1299.5);
// "$1,299.50"
// "3 hours ago" without a library
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-3, 'hour'); // "3 hours ago"
// readable dates
new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium' }).format(new Date());
If you are doing heavy date math (adding months, parsing weird formats), a
library still earns its place, and Temporal is on the way. But for display,
Intl is almost always enough.
4. HTTP requests: fetch (with a real timeout)
fetch is everywhere now, including Node 18 and up. The two reasons people kept
axios were timeouts and error handling, and both have native answers.
// before
import axios from 'axios';
const { data } = await axios.get(url, { timeout: 5000 });
// after, built in
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
The one thing to remember: fetch does not reject on 404 or 500, so check
res.ok yourself. If you lean on interceptors, axios is still a fair choice.
5. Modals: the <dialog> element
Accessible modals used to mean a library or a pile of ARIA and focus-trapping
code. The <dialog> element gives you the backdrop, focus trapping, and
Escape to close for free.
<dialog id="confirm">
<form method="dialog">
<p>Delete this file?</p>
<button value="cancel">Cancel</button>
<button value="ok">Delete</button>
</form>
</dialog>
document.getElementById('confirm').showModal();
showModal() traps focus and renders the ::backdrop. Submitting the form
closes the dialog and hands you the button's value.
6. Styling a parent: CSS :has()
We used to add JavaScript just to toggle a class on a parent when something
inside changed. :has() is the parent selector we asked for over a decade.
/* highlight a card that contains a checked box */
.card:has(input:checked) { outline: 2px solid green; }
/* style a form that has an invalid field */
form:has(:invalid) .submit { opacity: 0.5; }
That is real logic, in CSS, with no event listeners to wire up or tear down.
7. Tooltips and menus: the Popover API
Popovers meant z-index wars and outside-click handlers. The Popover API puts the
element in the top layer, gives you light dismiss (click outside or Escape), and
needs no JavaScript for the basic case.
<button popovertarget="menu">Options</button>
<div id="menu" popover>
<button>Rename</button>
<button>Delete</button>
</div>
This is the newest one on the list, so check support for your audience and treat
it as progressive enhancement. The other six are broadly safe in modern
browsers today.
How to know it is safe to delete
Before you rip anything out: search the feature on caniuse.com
or check whether it is Baseline for your target
browsers. For a library you already ship in production, there is no prize for
removing it in a hurry. The real win is for new code: reach for the platform
first, and only add a dependency when the platform genuinely falls short.
Your node_modules gets lighter, your bundle gets smaller, and you own less
supply chain risk for free.
Which of these did you not know was native yet? And what would you add as number
eight?
Top comments (0)