Размер и производительность полифилов

Intl API в JavaScript опирается на ICU (International Components for Unicode), который предоставляет данные локалей: правила форматирования дат, чисел, валют, склонений, сравнений строк. В браузерах поддержка часто встроена, но в Node.js и в старых окружениях возникает необходимость полифилов или дополнительных пакетов локалей. Это приводит к ключевой инженерной проблеме: росту размера бандла и влиянию на производительность загрузки.


Структура зависимости Intl и источники разрастания

Базовые объекты Intl:

  • Intl.DateTimeFormat
  • Intl.NumberFormat
  • Intl.Collator
  • Intl.PluralRules
  • Intl.RelativeTimeFormat
  • Intl.ListFormat
  • Intl.DisplayNames

Каждый из них зависит не только от кода реализации, но и от огромного набора локализационных данных:

  • названия месяцев и дней для каждой локали
  • правила форматирования чисел (разделители, группировка)
  • валютные таблицы и символы
  • правила плюрализации (очень сложные и зависят от языка)
  • сортировочные правила (Collator tailoring)

Основная масса веса приходится не на JavaScript-код, а на JSON/CLDR-данные (Unicode Common Locale Data Repository).


Полнота ICU и варианты поставки

В среде Node.js и некоторых сборках существует несколько режимов ICU:

small-icu

Содержит ограниченный набор локалей (обычно английский + системные). Размер минимален, но поддержка локалей ограничена.

full-icu

Содержит полный набор локалей ICU. Размер увеличивается на десятки мегабайт.

system-icu

Использует ICU, установленный в системе. Поведение зависит от окружения, что снижает предсказуемость.

Разница между режимами критична: переход от small-icu к full-icu может увеличить бинарь Node.js и зависимые артефакты на десятки мегабайт.


Полифилы Intl в браузерных сборках

В браузерах Intl обычно присутствует, но часто возникают ситуации:

  • поддержка отсутствует (старые браузеры)
  • отсутствуют нужные локали
  • требуется одинаковое поведение во всех окружениях

Наиболее распространённые полифилы:

  • @formatjs/intl
  • intl-messageformat
  • intl-pluralrules
  • intl-relativetimeformat

Каждый пакет может включать:

  • ядро реализации
  • набор CLDR данных
  • адаптеры под конкретные API Intl

Размер полифилов и влияние на bundle

Главная проблема — локали.

Примерные категории роста:

Базовый полифил Intl

Небольшой кодовый слой: несколько десятков килобайт после минификации.

Добавление одной локали

Каждая локаль добавляет:

  • правила форматирования дат
  • числовые форматы
  • склонения

Рост: от сотен килобайт до нескольких мегабайт в зависимости от покрытия.

Полный набор локалей

При подключении всех языков CLDR:

  • десятки мегабайт данных
  • рост времени сборки
  • ухудшение cache hit ratio

Разделение кода и локалей

Ключевая стратегия оптимизации — разделение:

Lazy loading локалей

Локали загружаются динамически:

  • основная логика Intl-полифила
  • отдельные чанки для en, ru, de, zh

Это уменьшает initial bundle, но увеличивает сложность runtime.

Tree-shaking и частичные импорты

Современные сборщики позволяют импортировать только нужные части:

  • только NumberFormat
  • только DateTimeFormat
  • только конкретные локали

Однако эффективность tree-shaking ограничена, поскольку CLDR-данные часто не поддаются статическому анализу.


Производительность загрузки и парсинга

Рост размера полифилов влияет на три уровня:

1. Network latency

Увеличение payload приводит к:

  • росту TTFB-ощущения (в реальности — download time)
  • задержкам на мобильных сетях

2. Parsing time

JSON-локали требуют значительного времени парсинга:

  • большие JSON структуры
  • вложенные таблицы правил
  • создание runtime-объектов

3. Memory footprint

После загрузки:

  • локали удерживаются в памяти
  • создаются кеши форматирования

Runtime стоимость Intl и кэширование

Intl API сам по себе не является бесплатным по вычислениям.

Основные затраты:

Создание форматтеров

new Intl.DateTimeFormat() и аналогичные вызовы:

  • инициализируют цепочку правил
  • подгружают locale data
  • создают internal slots

Стоимость особенно заметна при частом создании объектов.

Оптимизация:

  • кеширование экземпляров форматтеров
  • переиспользование через factory

Форматирование значений

Каждый вызов:

  • парсит число/дату
  • применяет правила локали
  • собирает строку результата

На больших списках это становится заметной нагрузкой CPU.


Полифилы vs нативная реализация

Нативный Intl (в браузерах и full-icu Node.js) обычно:

  • реализован на C++/ICU
  • использует оптимизированные структуры данных
  • имеет предзагруженные таблицы

Полифилы:

  • работают в JS
  • используют более медленные структуры
  • часто дублируют данные между пакетами

Разница по производительности:

  • нативные реализации быстрее на порядок при массовых форматированиях
  • полифилы выигрывают только в совместимости

Оптимизация размера через выбор API

Разные части Intl имеют разный вес:

Лёгкие по данным:

  • Intl.NumberFormat (ограниченный набор локалей — умеренный рост)

Средние:

  • Intl.RelativeTimeFormat
  • Intl.PluralRules

Тяжёлые:

  • Intl.DateTimeFormat
  • Intl.Collator
  • Intl.DisplayNames

Collator особенно дорог из-за сложных правил сортировки Unicode.


Архитектурные стратегии снижения веса

Разделение по локалям

  • включение только одной базовой локали
  • догрузка остальных по требованию

Минимизация используемых API

  • отказ от DisplayNames, если не требуется
  • замена RelativeTimeFormat на упрощённые функции

Серверное форматирование

  • перенос Intl-логики на backend
  • передача готовых строк

Минус — потеря гибкости клиентского рендера.


Влияние на сборку и CI

Полифилы Intl влияют не только на runtime:

  • увеличение времени сборки webpack/rollup
  • рост memory usage при bundling
  • замедление incremental builds

Особенно заметно при:

  • monorepo
  • SSR-приложениях
  • multi-locale сайтах

CDN и кэширование локалей

CLDR-данные хорошо кэшируются:

  • static assets могут быть разделены по локалям
  • aggressive cache headers уменьшают повторные загрузки

Но существует проблема:

  • обновления CLDR требуют инвалидации кэша
  • несинхронность версий полифила и данных

Практическое поведение в больших приложениях

В реальных системах Intl-полифилы часто становятся скрытым источником:

  • увеличенного TTI (time to interactive)
  • скачков памяти на слабых устройствах
  • нестабильности bundle size между релизами

Особенно критично при:

  • поддержке 10+ локалей
  • использовании SSR + hydration
  • микрофронтендах с дублирующими Intl-зависимостями