|
| 1 | +# Platform-Native Solutions |
| 2 | + |
| 3 | +The lazy senior dev's first question is always: *does the platform already do this?* |
| 4 | + |
| 5 | +This document answers that question for the most common cases. Before reaching for a package, scan here. The platform ships with your app for free, doesn't break on updates, and was written by people whose job is exactly that problem. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## HTML Elements |
| 10 | + |
| 11 | +Things the browser already has as a form control. |
| 12 | + |
| 13 | +| You think you need | What the platform has | |
| 14 | +|---|---| |
| 15 | +| Date picker library | `<input type="date">` | |
| 16 | +| Time picker library | `<input type="time">` | |
| 17 | +| Color picker library | `<input type="color">` | |
| 18 | +| Range slider library | `<input type="range">` | |
| 19 | +| Progress bar component | `<progress value="70" max="100">` | |
| 20 | +| Meter/gauge component | `<meter value="0.7">` | |
| 21 | +| Modal/dialog library | `<dialog>` + `dialog.showModal()` | |
| 22 | +| Accordion/FAQ component | `<details><summary>Title</summary>…</details>` | |
| 23 | +| Tooltip library | `title` attribute + CSS `::before`/`::after` | |
| 24 | +| Searchable dropdown | `<input list="id"> <datalist id="id">` | |
| 25 | +| Auto-growing textarea | `field-sizing: content` (CSS) | |
| 26 | +| Sticky header | `position: sticky; top: 0` (CSS) | |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## CSS Capabilities |
| 31 | + |
| 32 | +Things developers reach for JavaScript to do. |
| 33 | + |
| 34 | +| You think you need JS for | What CSS has | |
| 35 | +|---|---| |
| 36 | +| Responsive font size | `font-size: clamp(1rem, 2.5vw, 2rem)` | |
| 37 | +| Fluid spacing | `padding: clamp(1rem, 5vw, 3rem)` | |
| 38 | +| Dark mode | `@media (prefers-color-scheme: dark)` | |
| 39 | +| Reduced motion | `@media (prefers-reduced-motion: reduce)` | |
| 40 | +| Responsive layout without breakpoints | `grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))` | |
| 41 | +| Component-level responsive design | `@container` queries | |
| 42 | +| Global design tokens / theming | CSS custom properties (`--color-primary: #7c3aed`) | |
| 43 | +| Smooth scroll | `scroll-behavior: smooth` | |
| 44 | +| Scroll-snap carousel | `scroll-snap-type: x mandatory` + `scroll-snap-align: start` | |
| 45 | +| Aspect ratio enforcement | `aspect-ratio: 16 / 9` | |
| 46 | +| Truncate text with ellipsis | `overflow: hidden; text-overflow: ellipsis; white-space: nowrap` | |
| 47 | +| Multi-line text clamp | `-webkit-line-clamp: 3` | |
| 48 | +| CSS cascade layers (style isolation) | `@layer base, components, utilities` | |
| 49 | +| Nested CSS selectors | Native CSS nesting (no preprocessor needed) | |
| 50 | +| `has()` parent selector | `:has(input:checked)` | |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## JavaScript / Browser APIs |
| 55 | + |
| 56 | +Libraries people install that the runtime already ships. |
| 57 | + |
| 58 | +| You think you need | What the platform has | |
| 59 | +|---|---| |
| 60 | +| `query-string` / `qs` | `new URLSearchParams(location.search)` | |
| 61 | +| `lodash.clonedeep` | `structuredClone(obj)` | |
| 62 | +| `lodash.groupby` | `Object.groupBy(arr, fn)` | |
| 63 | +| `lodash.debounce` | — see debounce one-liner below | |
| 64 | +| `numeral` / `accounting` | `new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })` | |
| 65 | +| `date-fns` format | `new Intl.DateTimeFormat("en-US", { dateStyle: "long" }).format(date)` | |
| 66 | +| `date-fns` relative time | `new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(-3, "day")` | |
| 67 | +| `plural` / `i18n` plurals | `new Intl.PluralRules("en-US").select(count)` | |
| 68 | +| `clipboard.js` | `navigator.clipboard.writeText(text)` | |
| 69 | +| `uuid` (v4) | `crypto.randomUUID()` | |
| 70 | +| Infinite scroll library | `new IntersectionObserver(cb).observe(sentinel)` | |
| 71 | +| Resize listener library | `new ResizeObserver(cb).observe(element)` | |
| 72 | +| DOM mutation watcher | `new MutationObserver(cb).observe(el, options)` | |
| 73 | +| `uuid-validate` | `/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)` | |
| 74 | +| `is-online` / `connectivity check` | `navigator.onLine` + `online`/`offline` events | |
| 75 | +| `sharesheet` library | `navigator.share({ title, text, url })` | |
| 76 | +| `store.js` / `localForage` (simple case) | `localStorage.setItem(key, JSON.stringify(val))` | |
| 77 | +| Abort fetch on timeout | `AbortSignal.timeout(5000)` passed to `fetch` | |
| 78 | +| Custom event bus | `new EventTarget()` / `dispatchEvent(new CustomEvent("x", { detail }))` | |
| 79 | + |
| 80 | +**Debounce one-liner** (no library): |
| 81 | +```js |
| 82 | +// ponytail: 3 lines beats a dependency |
| 83 | +let t; |
| 84 | +const debounce = (fn, ms) => (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; |
| 85 | +``` |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## Node.js Standard Library |
| 90 | + |
| 91 | +Packages that wrap Node built-ins. |
| 92 | + |
| 93 | +| You think you need | What Node has | |
| 94 | +|---|---| |
| 95 | +| `mkdirp` | `fs.mkdirSync(path, { recursive: true })` | |
| 96 | +| `rimraf` | `fs.rmSync(path, { recursive: true, force: true })` | |
| 97 | +| `make-dir` | `fs.mkdirSync(path, { recursive: true })` | |
| 98 | +| `slash` (win paths) | `path.posix` or `path.normalize()` | |
| 99 | +| `uuid` (v4) | `crypto.randomUUID()` | |
| 100 | +| `ms` (parse duration strings) | — keep `ms`, it's genuinely useful and tiny | |
| 101 | +| `is-stream` | `val instanceof stream.Readable` | |
| 102 | +| `object-assign` | `Object.assign()` / spread | |
| 103 | +| `array-uniq` | `[...new Set(arr)]` | |
| 104 | +| `array-flatten` | `arr.flat(Infinity)` | |
| 105 | +| `flat` | `arr.flat(depth)` | |
| 106 | +| `path-exists` | `fs.existsSync(path)` | |
| 107 | +| `load-json-file` | `JSON.parse(fs.readFileSync(path, "utf8"))` | |
| 108 | +| `write-json-file` | `fs.writeFileSync(path, JSON.stringify(obj, null, 2))` | |
| 109 | +| `pkg-dir` | `path.resolve(__dirname, "..")` / `import.meta.dirname` | |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## Python Standard Library |
| 114 | + |
| 115 | +Packages that wrap what Python already ships. |
| 116 | + |
| 117 | +| You think you need | What Python has | |
| 118 | +|---|---| |
| 119 | +| `python-dateutil` (basic parsing) | `datetime.fromisoformat()` (Python 3.7+) | |
| 120 | +| `pytz` | `zoneinfo.ZoneInfo("America/New_York")` (Python 3.9+) | |
| 121 | +| `attrs` (simple data classes) | `@dataclass` | |
| 122 | +| `six` | — drop it, Python 2 is gone | |
| 123 | +| `pathlib2` | `pathlib.Path` (built-in since Python 3.4) | |
| 124 | +| `enum34` | `enum.Enum` (built-in since Python 3.4) | |
| 125 | +| `typing_extensions` (common types) | `from __future__ import annotations` + built-in generics | |
| 126 | +| `simplejson` (basic use) | `json` (stdlib) | |
| 127 | +| `requests` (simple GET) | `urllib.request.urlopen(url)` — `requests` for anything real | |
| 128 | +| `click` (single command) | `argparse` (stdlib) | |
| 129 | +| `mergedeep` | `dict \| other_dict` (Python 3.9+) | |
| 130 | +| `more-itertools` (basic) | `itertools` (stdlib): `chain`, `islice`, `groupby`, `product` | |
| 131 | +| `toolz` (basic) | `functools`: `lru_cache`, `partial`, `reduce` | |
| 132 | +| `tabulate` (dev/debug only) | `pprint.pprint()` for quick inspection | |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +## Database |
| 137 | + |
| 138 | +Things the application layer implements that the database already does. |
| 139 | + |
| 140 | +| You think you need app code for | What the database has | |
| 141 | +|---|---| |
| 142 | +| Pagination offset/limit | `LIMIT 20 OFFSET 40` | |
| 143 | +| Running totals | `SUM(...) OVER (ORDER BY date)` (window function) | |
| 144 | +| Rank within group | `RANK() OVER (PARTITION BY category ORDER BY score DESC)` | |
| 145 | +| Pivot / cross-tab | `FILTER (WHERE ...)` + conditional aggregation | |
| 146 | +| Deduplication | `SELECT DISTINCT` / `ON CONFLICT DO NOTHING` | |
| 147 | +| Soft-delete filtering | Generated column + partial index | |
| 148 | +| Tree traversal | Recursive CTE (`WITH RECURSIVE`) | |
| 149 | +| Full-text search (basic) | `tsvector` / `MATCH AGAINST` / `FTS5` | |
| 150 | +| JSON storage + query | `jsonb` (Postgres) / `JSON_EXTRACT` (SQLite/MySQL) | |
| 151 | +| UUID generation | `gen_random_uuid()` (Postgres) / `UUID()` (MySQL) | |
| 152 | +| Timestamps on insert/update | `DEFAULT now()` + trigger or `ON UPDATE CURRENT_TIMESTAMP` | |
| 153 | +| Enforce uniqueness | `UNIQUE` constraint — not application-level checks | |
| 154 | +| Enforce referential integrity | `FOREIGN KEY` — not application-level checks | |
| 155 | +| Enforce value ranges | `CHECK (price > 0)` — not application-level validation | |
| 156 | + |
| 157 | +--- |
| 158 | + |
| 159 | +## The Pattern |
| 160 | + |
| 161 | +Across every layer, the pattern is the same: |
| 162 | + |
| 163 | +``` |
| 164 | +Platform team spends years solving the problem. |
| 165 | +Package author wraps it. |
| 166 | +You install the wrapper. |
| 167 | +The wrapper goes unmaintained. |
| 168 | +You debug the wrapper. |
| 169 | +``` |
| 170 | + |
| 171 | +Skip the wrapper. The platform ships with your app for free. |
| 172 | + |
| 173 | +When the native solution is genuinely insufficient (old browser support, edge cases it doesn't handle, ergonomics that matter at scale), the library earns its place. Install it then — not before. |
0 commit comments