Skip to content
ExamsLibraryFor agentsConnect your agent
philosophyPhilosophy·5/12

Dependency discipline

Replicate simple utilities. Draw shapes in CSS. Prefer native browser APIs over libraries. The bundle is design.

Raw markdown On GitHub

A 5MB JS bundle is a design choice. So is a 50KB one. So is choosing moment.js over Intl.DateTimeFormat. So is reaching for axios when fetch exists.

The principle: every dependency you add is a tax — bundle size, security surface, version drift, license review, time-to-interactive. The bar for adding one should be higher than "it's convenient."

This is a philosophy node, not a tactical one. It applies before any CSS or motion decision.

Replicate simple utilities

Before adding lodash for one helper, consider whether you can write it in 5 lines:

// 'lodash/uniq'
const uniq = <T,>(arr: T[]): T[] => [...new Set(arr)];
 
// 'lodash/groupBy'
const groupBy = <T, K extends string>(arr: T[], key: (x: T) => K) =>
  arr.reduce((acc, x) => ({ ...acc, [key(x)]: [...(acc[key(x)] ?? []), x] }), {} as Record<K, T[]>);
 
// 'classnames' — useful, but trivial to write
const cx = (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(' ');

If you only use 2 functions from a 70KB library, write the 2 functions.

Draw simple shapes in CSS, not images

Before adding an SVG file or PNG for a shape:

/* Triangle */
.triangle {
  width: 0; height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-bottom: 16px solid var(--accent);
}
 
/* Circle */
.circle { width: 16px; height: 16px; border-radius: 50%; background: var(--accent); }
 
/* Donut */
.donut { width: 32px; height: 32px; border: 4px solid var(--accent); border-radius: 50%; }
 
/* Chevron via border + rotate */
.chevron {
  width: 8px; height: 8px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}

No HTTP request, no decoded image bytes, no alt text to maintain. The shape is in the stylesheet.

Prefer native browser APIs

The web platform has shipped a lot in the last decade. Many libraries that "everyone uses" are obsolete.

LibraryNative replacement
moment.js (300KB)Intl.DateTimeFormat, Intl.RelativeTimeFormat
axiosfetch() (with a thin wrapper for JSON parsing)
lodash (most uses)Array.from, .flat, .includes, .at, Object.fromEntries
classnamesclsx (~240 B, same API) — or the one-line filter(Boolean).join(' ') helper when every input is a plain string. Variant-shaped styling (size × intent × state) earns cva on top
uuidcrypto.randomUUID()
query-stringURLSearchParams
jQueryDOM API + closest(), matches(), querySelector()
intersection-observer-polyfillNative IntersectionObserver (universal since 2019)

Audit your package.json annually. Many production apps have 5+ MB of dependencies because someone added moment.js in 2017 and no one removed it.

When to add a dependency anyway

  • The library is large and complex to write correctly: cryptography (@noble/hashes), framework runtimes (React, Vue), date parsing edge cases (date-fns over reinventing).
  • The library has a security team maintaining it (@stripe/stripe-js, OAuth clients).
  • The library is small and focused with a clear single purpose (nanoid, clsx).
  • The team's velocity matters more than the bundle (early-stage product, internal tool).

These are real reasons. "It's convenient" is not.

When to apply

  • Before npm install <anything> — pause for 30 seconds. Can you write the 5 lines instead?
  • During PR review — flag new dependencies, ask whether the alternative is a 10-line utility.
  • At quarterly audits — npx bundle-phobia or webpack-bundle-analyzer on the bundle. Anything over 100KB is a budget item.

Gotcha

This rule applies in proportion to the project's expected lifetime and audience. A throwaway prototype can import _ from 'lodash' and ship in an afternoon. A production app shipping to mobile users across slow networks pays for that dependency every page load forever. Calibrate.

Sources

  • Ben DC — frontend-guidelines — "Favor native methods."
  • Vercel — bundle-size as a design dimension.
  • web.dev — "Tame third parties," bundle audits.
  • Emil Kowalski, skills (MIT, github.com/emilkowalski/skills, a81dbca): skills/pick-ui-library/SKILL.md (clsx for conditional classes, cva for real variants).
  • Related: transform-opacity-only and debugging-animations (the performance-is-design framing lives in those motion nodes), unseen-details-compound.

philosophydependenciesperformancerestraint