Як аудит залежностей із Baseline допомагає відправляти менше JavaScript

Матеріал розбирає підхід Jad Joubran із Smashing Magazine до аудиту залежностей у JavaScript‑проєктах за допомогою Baseline. Ідея проста: браузери вже вміють багато того, для чого колись ставили бібліотеки, тож частину кілобайтів можна безпечно «повернути платформі».

Що таке Baseline і навіщо він у цьому підході

Baseline — це ініціатива WebDX Community Group, яка описує, наскільки безпечно використовувати конкретну можливість веб‑платформи у сучасних браузерах (Chrome, Edge, Firefox, Safari). Кожна фіча має один зі статусів:

  • Limited availability — ще не у всіх рушіях; без фолбеків покладатися не можна.
  • Baseline Newly available — уже є в усіх основних рушіях, але ще не пройшло 30 місяців; на старіших девайсах може не працювати.
  • Baseline Widely available — пройшло 30 місяців з появи в усіх рушіях; вважається безпечним для широкого використання.

Саме різниця між Newly та Widely визначає, чи бібліотеку можна просто видалити, чи потрібно:

  • перевірити свою аудиторію (аналітика, browserslist),
  • або додати фіче‑чек і фолбек.

Статус будь-якої можливості можна подивитися на webstatus.dev або на MDN (бейдж Baseline на сторінці фічі), а також програмно через пакет web-features.

Рамка рішень: три питання перед видаленням будь-якої бібліотеки

Автор пропонує не «зривати пластир» відразу, а проганяти кожну потенційну заміну через невелику рамку з трьох питань:

  1. Чи безпечна нативна фіча для моєї аудиторії?
    Якщо вона Baseline Widely available — майже завжди «так». Якщо Newly available — дивитися на свою статистику браузерів: внутрішній B2B‑дешборд і публічний сайт з довгим «хвостом» старих Android — це різні умови.
  2. Яка фактична «ціна» заміни?
    Іноді для нативної фічі ще потрібен поліфіл. Якщо поліфіл важчий за поточну бібліотеку, ви лише збільшуєте бандл (як у випадку з Temporal).
  3. Чи реально платформа покриває ваш кейс?
    Бібліотеки часто роблять більше, ніж виглядає на перший погляд. Наприклад, axios — це не тільки fetch з JSON, а ще й інтерцептори, тайм‑аути, ретраї, обробка помилок. Потрібно звірити, що саме ви використовуєте.

Усі подальші «кластери» бібліотек у матеріалі — це по суті багаторазове застосування цих трьох питань до різних типів залежностей.

Кластер 1: інтернаціоналізація — найбільший «легкий» виграш

У сфері форматування дат, чисел і списків браузер уже дає багато готових рішень через простір імен Intl. Частину популярних бібліотек можна просто прибрати:

  • timeago.jsIntl.RelativeTimeFormat (Widely)
  • pluralizeIntl.PluralRules (Widely)
  • numeralIntl.NumberFormat (Widely)
  • humanize-durationIntl.DurationFormat (Newly)
  • «ручні» джойни списків → Intl.ListFormat (Widely)

Відносний час замість timeago.js

Бібліотека timeago.js конвертує мітку часу у фразу на кшталт «3 hours ago». Це саме вміє Intl.RelativeTimeFormat — і вже Widely available.

const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
rtf.format(-1, "day"); // "yesterday"
rtf.format(3, "hour"); // "in 3 hours"
rtf.format(-2, "week"); // "2 weeks ago"

Єдина додаткова частина, яку дає timeago.js, — автоматичний вибір одиниці («секунди», «хвилини», «дні»). У нативному підході це кілька рядків арифметики, які легко сховати в утиліту — після цього потреба у самій бібліотеці зникає.

Числа, валюта та списки

Intl.NumberFormat закриває більшість завдань форматування чисел: тисячні розділювачі, валюта, відсотки, компактний запис.

new Intl.NumberFormat("en-US").format(1234567.89);
// "1,234,567.89"

new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
  1234.5,
);
// "$1,234.50"
new Intl.NumberFormat("en", { notation: "compact" }).format(1200000);
// "1.2M"

Intl.ListFormat (Widely) акуратно вирішує задачу перетворення масиву в речення з потрібними комами й «and»/«та» — без ручних умов по довжині масиву.

const lf = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
lf.format(["Alice", "Bob", "Carol"]);
// "Alice, Bob, and Carol"

Обмеження Intl.DurationFormat

Intl.DurationFormat дозволяє форматувати тривалість («1 hour, 30 minutes») і функціонально замінює humanize-duration:

const df = new Intl.DurationFormat("en", { style: "long" });
df.format({ hours: 1, minutes: 30 });
// "1 hour, 30 minutes"

Але на момент опису цей API — Baseline Newly available. Для широкої публічної аудиторії це ще «питання №1»: потрібно або перевірити, який відсоток користувачів на сучасних браузерах, або додати фолбек. Для внутрішніх інструментів на сучасних браузерах — уже сміливо можна використовувати.

У сумі набір «дато‑числових» бібліотек у типовому проєкті легко набігає до ~14 KB (мінімізовано й gzip), більша частина яких сьогодні закривається нативними Intl‑API.

Кластер 2: HTTP‑клієнти та межі fetch

Другий кластер — мережеві бібліотеки, насамперед axios і superagent. Для більшості запитів достатньо fetch + AbortController, які вже Widely available.

Приклад коду в оригіналі

Ціна заміни тут — одна додаткова операція (res.json()) й потреба явно обробляти помилки.

Тайм‑аути та скасування

axios надає timeout-опцію, для fetch аналог — AbortSignal.timeout():

Приклад коду в оригіналі

Де fetch ще не дорівнює axios

За рамкою «питання №3» автор підкреслює конкретні прогалини:

  • HTTP‑помилки не відхиляють проміс. fetch резолвиться й для 404/500; потрібно перевіряти res.ok вручну. axios кидає помилку для всіх не‑2xx.
  • Немає інтерцепторів. Якщо ви підсаджуєте токени, логіку оновлення токена чи глобальну обробку 401 в інтерцептор — доведеться обгорнути fetch власним класом/функцією.
  • Немає вбудованих ретраїв. Плагіни до axios роблять ретраї з коробки; із fetch це вже ваша логіка.
  • Немає підтримки прогресу завантаження файлів. Для завантаження з прогрес‑баром досі часто вигідніше залишити бібліотеку.

Автор статті згадує, що в навчальних проєктах (наприклад, Learn JavaScript) команда розв’язує завдання інтерцепторів власною обгорткою над fetch і успішно застосовує це для великої кількості користувачів. Це приклад, а не універсальна рекомендація.

Практичний висновок: якщо ви використовуєте HTTP‑клієнт тільки для простих GET/POST — заміна на тонкий шар над fetch дає економію приблизно 17–19 KB gzip. Якщо активно покладаєтесь на інтерцептори, прогрес та інші можливості, варто або залишити бібліотеку, або уважно оцінити обсяг коду для власної реалізації.

Кластер 3: UI‑примітиви — діалоги, поповери, тултіпи

Третій великий напрям — мінімальні UI‑бібліотеки: модальні діалоги, тултіпи, поповери, фокус‑трап, блокування скролу фону. Автор показує, як їх заміняють:

  • <dialog> + CSS для блокування скролу,
  • Popover API,
  • CSS anchor positioning.

Модальні вікна на <dialog>

Багато кілобайтів коду в модальних бібліотеках йде на доступність: фокус у модалі, закриття по Escape, повернення фокусу назад, топ‑лейєр, backdrop. Усе це дає нативний елемент <dialog> (Widely available):

Приклад коду в оригіналі

Метод showModal() забезпечує фокус‑трап і робить інший контент сторінки неактивним для навігації. Додатково є псевдоелемент ::backdrop для стилізації підкладки.

Окремо бібліотеки часто додають блокування скролу body. Нині це вирішується однією декларацією CSS:

Приклад коду в оригіналі

Важлива деталь: використовується :modal, а не [open], щоб не блокувати скрол до того, як діалог стане справді модальним.

Popover API та anchor positioning замість тултіп‑бібліотек

Для легких поповерів (меню, підказки, невеликі панелі) Popover API дає:

  • перемикання стану по кліку без JS,
  • закриття по кліку поза елементом,
  • обробку Escape,
  • рендеринг у top layer.

Приклад коду в оригіналі

Цей API — Baseline Newly available, тож для публічних проєктів варто перевірити аудиторію або додати фолбек.

Друге завдання тултіп‑бібліотек — позиціонування відносно тригера, з урахуванням обрізання вьюпортом. Цю роль тепер бере на себе CSS anchor positioning, де тригер оголошується як «якір», а поповер прив’язується до нього через position-anchor і position-area. Ця можливість теж Newly available, і тут ще важливіше закласти простий фолбек.

У сумі модальні, тултіпні та скрол‑блокувальні бібліотеки легко додають ~24 KB gzip, які в сучасних браузерах можна замінити нативними можливостями з кращими дефолтами з точки зору доступності.

Кластер 4: точкові утиліти Lodash, які вже є в платформі

Навіть якщо проєкт давно не тягне весь lodash, окремі його модулі — lodash.clonedeep, lodash.groupby та інші — досі часто сидять у package.json. Частина таких утиліт тепер має прямі відповідники в стандартній бібліотеці.

Групування масивів: Object.groupBy

lodash.groupby перетворює масив об’єктів на об’єкт, де ключі — значення якоїсь властивості. Аналог — Object.groupBy:

Приклад коду в оригіналі

Є і Map.groupBy для випадків, коли потрібен саме Map. Обидві фічі Baseline Newly available, тож для широкої аудиторії потрібно зважити підтримку старих браузерів.

Глибоке клонування: structuredClone

lodash.clonedeep дає глибоку копію об’єкта. Сьогодні цю роль відіграє structuredClone (Widely available):

Приклад коду в оригіналі

structuredClone коректно працює з Date, Map, Set, ArrayBuffer та циклічними посиланнями. Обмеження важливі для «питання №3»: він не клонує функції, DOM‑вузли та зберігає не всі прототипи класів. Для «чистих даних» це повноцінна заміна, але для об’єктів зі складною поведінкою — не завжди.

Операції над множинами: методи Set

Для задач типу union/intersection/difference більше не потрібні утиліти Lodash: Set отримав відповідні методи (Newly available):

Приклад коду в оригіналі

До набору входять також symmetricDifference, isSubsetOf, isSupersetOf, isDisjointFrom. Це знімає потребу в окремих дрібних пакетах на кшталт «set‑operations‑helpers».

Водночас деякі частини Lodash усе ще мають сенс: наприклад, debounce і throttle не мають нативних аналогів, і точкове підключення lodash.debounce цілком виправдане.

Кластер 5: Temporal як приклад, коли не варто поспішати

На відміну від попередніх кластерів, з Temporal автор демонструє ситуацію, коли рамка трьох питань підказує «почекати».

Temporal — новий стандарт для роботи з датами й часом у JavaScript (ES2026), що суттєво кращий за Date з точки зору моделі даних і роботи з часовими зонами. Firefox і Chrome вже його реалізували, Safari — на підході, але:

  • Temporal ще не Baseline, має статус limited availability;
  • щоб використовувати Temporal всюди, потрібен поліфіл.

Офіційний поліфіл @js-temporal/polyfill важить приблизно 44 KB gzip, «полегшений» без BigInt — ~19 KB. Для порівняння, популярні бібліотеки дат типу dayjs займають близько 3 KB gzip.

Якщо замінити dayjs на Temporal із поліфілом прямо зараз, бандл збільшиться — тобто «питання №2» (ціна заміни) тут провалюється. Навіть попри те, що «питання №3» (функціональність) — явно на користь Temporal.

Рекомендація автора: залишити легку бібліотеку дат, поки Temporal не стане Baseline. Після цього можна буде підключати поліфіл умовно (для старих браузерів) або зовсім від нього відмовитися.

Як самостійно провести аудит свого package.json

Нарешті, автор формулює покрокову процедуру аудиту, яку можна повторювати раз на квартал.

Крок 1. Витягнути продакшен‑залежності

Спершу потрібно подивитися, що насправді їде в продакшен:

Приклад коду в оригіналі

Крок 2. Оцінити «вагу» кожної залежності

  • Для швидкої оцінки окремого пакета — bundlephobia.com (мінімізований і gzip‑розмір).
  • Для реальної картини у вашому бандлі — аналізатор на зразок npx source-map-explorer або npx vite-bundle-visualizer.

Крок 3. Знайти нативну заміну й перевірити Baseline

Для кожної «важкої» або очевидно утилітарної бібліотеки:

  • з’ясувати, чи існує нині нативний еквівалент;
  • подивитися його Baseline‑статус через webstatus.dev або MDN.

Крок 4. Прогнати через три запитання

Далі для кожної кандидатки на видалення відповісти на три ключові питання (аудиторія, вартість, покриття кейсу). Більшість рішень впираються у:

  • статус фічі + ваш browserslist (питання 1),
  • фактичний розмір бібліотеки/поліфіла (питання 2),
  • реальний набір можливостей, які ви використовуєте (питання 3).

Крок 5. М’які заміни через progressive enhancement

Для Widely available‑фіч — можна просто замінити бібліотеку на нативний API. Для Newly available варто або впевнитися, що аудиторія — на сучасних браузерах, або зробити фолбек через фіче‑чек:

Приклад коду в оригіналі

Це дозволяє відправляти менше коду тим, хто вже підтримує потрібні можливості, не ламаючи досвід для решти.

Підсумок підходу

Якщо скласти «кластери» разом, для типової середньої SPA автор оцінює потенційну економію в діапазоні 60–90 KB gzip:

  • ≈14 KB — інтернаціоналізація,
  • ≈17 KB — HTTP‑клієнт,
  • ≈24 KB — UI‑примітиви (модалки, тултіп‑бібліотеки, фокус‑трап, блокування скролу),
  • ≈8 KB і більше — Lodash‑утиліти (залежить від того, скільки функцій ви тягнете).

Ці цифри не універсальні — залежать від конкретних пакетів і способу імпорту. Але методика стабільна: раз на деякий час переглядати package.json, звірятися з Baseline і видаляти бібліотеки, за які платформа вже відповіла.

Практичний старт: обрати один із кластерів (наприклад, інтернаціоналізацію чи UI‑примітиви), подивитися власні залежності й перевірити, що з них уже можна замінити на нативні можливості без шкоди для вашої конкретної аудиторії.


Джерело: Jad Joubran — Smashing Magazine