Головна / Статті

Бюджети продуктивності для JavaScript: дотримання обмежень пакетів у CI

Дізнайтеся, чому JavaScript коштує набагато дорожче, ніж час його завантаження, як визначити реалістичний бюджет продуктивності та як змусити webpack відхилити процеси компіляції, які його перевищують.

Видавець: REACTAPP.TOP7 хв читання1503 слів

Джерела: The Cost of JavaScript — Performance budgets and why your bundle size is driving users away

Сторінка, яка працює миттєво на ноутбуці розробника, може зависати на кілька секунд на телефоні справжнього клієнта. Різниця рідко полягає лише у швидкості мережі: вона зумовлена роботою процесора, необхідною для обробки кожного кілобайта JavaScript, коли він потрапляє на простий пристрій. У цьому посібнику пояснюється, звідки береться цей навантаження, як перетворити його на чіткий бюджет продуктивності та як налаштувати процес створення продукту так, щоб він відхиляв зміни, які порушують цей бюджет, ще до того, як вони потраплять у продакшн.

Різниця між комп’ютером розробника та пристроєм користувача

Більшість команд, які працюють з фронтендом, створюють та тестують код на обладнанні, яке зовсім не відповідає реальним умовам: сучасні ноутбуки, проводові чи оптоволоконні з’єднання та сервер розробки, що працює на localhost. За таких умов майже будь-який пакет коду здається швидким, тому розмір пакету зазвичай сприймається як щось, що можна оптимізувати пізніше, а не як обмеження проектування.

Справжній трафік виглядає інакше. Значна кількість відвідувачів приходить за допомогою смартфонів Android середнього класу, нестабільного 4G чи переповнених публічних мереж Wi-Fi. Уявіть собі панель керування, яка вимагає багато даних та має негайно реагувати у локальному браузері, проте під час тестування на старіших iPhone інтерактивні елементи керування потребують близько трьох секунд, щоб стати придатними до використання. У коді немає жодних проблем; пристрій просто потребує набагато більше часу для обробки того самого скрипту.

Ця затримка має фінансові наслідки. Додатковий, неоптимізований JavaScript поступово відкладає момент, коли користувач може взаємодіяти з інтерфейсом, і це найбільш відчутно у критичних процесах, таких як входження або додавання товару до кошика. Коли ці кроки виконуються повільно, люди залишають їх, тому надмірний розмір пакету може тихо відштовхнути користувачів.

Чому JavaScript коштує дорожче, ніж час його завантаження

Протягом багатьох років затримка в мережі була основною перешкодою, і робота над продуктивністю зосереджувалася на зменшенні розміру зображень та покращенні способів передачі даних, таких як HTTP/2. Ці проблеми все ще мають значення, але для JavaScript сьогодні більшою проблемою є те, що відбувається після отримання байтів.

Скрипт не може бути використаний одразу після завершення завантаження. Браузер мусить його проаналізувати, скомпілювати та виконати, і більша частина цих операцій відбувається на основному потоці — тому самому, який відповідає за обробку дотиків, кліків та відображення контенту. Цілком можливо, що пакет розміром 400 КБ завантажиться швидко, але сторінка все одно буде здаватися нереактивною, оскільки пристрій зайнятий обробкою коду замість реагування на вхідні дані.

У телефоні середнього класу цикл аналізу, компіляції та виконання може суттєво погіршити показник Interaction to Next Paint (INP) — метрику, яка вимірює швидкість реакції сторінки на дії користувача. Цей показник безпосередньо впливає на бали Core Web Vitals, а низькі показники цих метрик зазвичай супроводжуються меншою утримуваністю користувачів та гіршими бізнес-результатами. Тому будь-які серйозні зусилля щодо покращення цих показників мають включати скорочення кількості JavaScript-коду, який мобільні пристрої повинні аналізувати та виконувати, а не лише зменшення розміру завантажуваних файлів.

Що таке бюджет продуктивності та як його встановити

Бюджет продуктивності JavaScript — це чіткий ліміт на кількість коду, яку може містити сторінка, маршрут чи фрагмент. Технічно це просто число, але його справжня цінність полягає у організаційному аспекті: це домовленість, яку підтримує вся команда. Замість того, щоб виявляти проблеми після релізу та поспішно їх виправляти, бюджет діє як захисний елемент, який запобігає їхньому включенню з самого початку.

Практична порада: бюджет перетворює питання продуктивності з чогось, на що потрібно реагувати, на обмеження, навколо якого потрібно проектувати рішення, так само як це робиться з вимогами доступності чи безпеки.

Встановлення такого ліміту починається з визначення конкретних, вимірюваних значень. Широко поширеною метою є збереження розміру JavaScript-коду критичного шляху на будь-якій сторінці приблизно нижче 170 КБ у стиснутому вигляді — це значення обрано так, щоб середній мобільний пристрій міг швидко досягти стану взаємодії. Ви повинні перевірити будь-яке таке число з урахуванням вашої аудиторії, але принцип залишається незмінним: як тільки ліміт встановлений, кожна нова залежність чи функція мусить відразу обґрунтувати свою вагу.

Кілька практичних порад допомагають краще керувати бюджетом:

  • Визначайте бюджет на кожен шлях чи точку входу, а не лише для всього додатку, щоб об’ємні адміністративні розділи не поглинали ліміти для головної сторінки.
  • Вимірюйте завжди однаково (у стиснутому та нестиснутому вигляді), щоб цифри залишалися порівнянними між різними версіями додатку.
  • Переглядайте ліміт, коли змінюється склад вашої аудиторії чи пристроїв, замість того, щоб мовчки підвищувати його при кожній невдачі під час створення версії.

Автоматизація перевірки захисних механізмів у CI/CD

Бюджети, які залежать від того, чи хтось пам’ятатиме про їх перевірку, довго не існують. Додається функція, рев’юер зосереджується на логіці, ніхто не переглядає звіт про пакети, і відбувається регресія. Єдиний ефективний підхід — вбудувати перевірку безпосередньо у процес, щоб кожна зміна автоматично аналізувалася.

Примітка: оскільки перевірка виконується у CI, кожен pull request перевіряється щодо бюджету, і нікому не потрібно пам’ятати про це.

Інструменти для компіляції, такі як webpack, можуть зробити це за вас: налаштуйте їх так, щоб надто великі результати обробки розглядалися як помилка компіляції, а не як попередження. Тоді зворотний зв’язок надходить миттєво, пов’язаний із самою зміною, яка його спричинила. Якщо запит на об’єднання вносить бібліотеку, яка генерує частину даних понад встановлений ліміт, процес компіляції показує помилку, і автору доводиться її виправити перед об’єднанням коду.

У наведеній нижче конфігурації використовується опція performance від webpack. Встановлення значення hints на 'error' перетворює попередження щодо розміру на помилки, які заважають процесу будування. Параметр maxAssetSize обмежує розмір кожного окремого файлу, який генерується (тут 250 КіБ), тоді як maxEntrypointSize обмежує загальний розмір активів, завантажених для однієї точки входу (тут 500 КіБ); обидва значення вказуються у байтах. Функція assetFilter визначає, які файли будуть оброблятися, у цьому випадку ігноруючи карти джерела та JSON-файли, щоб уникнути хибних спрацьовувань.

module.exports = {
  // ... other webpack configuration ...

  performance: {
    hints: 'error',
// Emit an error whenever a bundle exceeds the size limits
    maxAssetSize: 250 * 1024,
// Maximum size in bytes for any generated asset (e.g., 250 KiB)
    maxEntrypointSize: 500 * 1024,
// Maximum size in bytes for an entrypoint (e.g., 500 KiB)
    assetFilter: function(assetFilename) {

// Optionally exclude certain assets (e.g., source maps) from performance hints
      return !/\.(map|json)$/.test(assetFilename);
    }
  }
};

Пам’ятайте, що webpack порівнює ці ліміти з розмірами файлів після обробки, які зазвичай не є розмірами у стиснутому вигляді, які завантажують користувачі. Якщо ваш бюджет визначений у стиснутих кілобайтах, перерахуйте його у нерозмитнені розміри для цієї перевірки або поєднайте його з окремим кроком, який вимірює розмір файлу у форматі gzip чи Brotli.

Стратегії, які дозволяють тримати розмір пакетів у межах бюджету

Бюджет та автоматизована перевірка повідомляють вас про те, коли ви перевищуєте ліміт; щоб залишатися в межах нього, потрібні ретельні інженерні зусилля. Найефективніші методи добре відомі та найкраще працюють у поєднанні.

Розділення коду за маршрутами та функціями

Розділення коду полягає у розбитті одного монолітичного пакета на менші частини, які завантажуються за потребою. Поширеним прикладом є важкий панель керування адміністратором у корпоративному додатку: завдяки завантаженню її за допомогою динамічних операторів import() її код отримується лише тоді, коли адміністратор фактично переходить на відповідний маршрут. Звичайні користувачі ніколи її не завантажують, що дозволяє скоротити початковий об’єм даних на сотні кілобайт.

Використовуйте процес «трясіння дерева», щоб прибрати непотрібний код

Процес «трясіння дерева» усуває експорти, які ніколи не використовуються, тож невикористані частини ваших власних модулів та бібліотек не потрапляють у кінцевий продукт. Він найкраще працює з синтаксисом ES-модулів та залежностями, опублікованими у форматі, придатному для такої обробки, тому варто перевірити, як створюється пакет, перш ніж покладатися на нього.

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

Відкладіть те, що не потрібно на першому екрані

Відкладене завантаження компонентів, які не є критичними для початкового вигляду, таких як діалогові вікна, елементи під контентом чи рідко використовувані панелі, відкладає їх обробку та виконання до моменту, коли вони стають необхідними. У поєднанні з розділенням коду та оптимізацією це забезпечує вільність основної смуги обробки протягом перших секунд, коли користувачі найчастіше взаємодіють з програмою.

Підсумок

  • Дорогим етапом роботи JavaScript на мобільних пристроях є його обробка, компіляція та виконання на основній смузі обробки, що безпосередньо впливає на INP та інші показники Core Web Vitals.
  • Бюджет продуктивності — це угода команди, виражена у числовому вигляді, бажано за кожним маршрутом, та вимірювана послідовно.
  • Забезпечення дотримання бюджету в CI, наприклад за допомогою performance.hints: 'error' у webpack, дозволяє виявити регресії у пул-запитах, які їх спричиняють.
  • Розділення коду, оптимізація структури та затримане завантаження — це способи дотримуватися бюджету після його встановлення; тестуйте на реальних пристроях, щоб переконатися, що ці показники призводять до швидшої роботи.