Intl в JavaScript опирается на ICU (International
Components for Unicode) — набор данных, включающий правила
форматирования дат, чисел, валют, сортировки и языковые нормы для сотен
локалей. Каждая локаль содержит значительный объём метаданных: названия
месяцев, правила склонений, календарные особенности, символы валют,
порядки сортировки и множество культурных нюансов.
В контексте фронтенд-сборки это приводит к важной проблеме: локали становятся потенциальным источником раздувания итогового JavaScript-бандла.
Реализация Intl в движках (V8, JavaScriptCore,
SpiderMonkey) может использовать разные уровни ICU:
Node.js и браузеры отличаются по тому, какие локали включены и доступны. Например, Node.js может быть собран без полного ICU, и тогда:
new Intl.DateTimeFormat('ru-RU').format(new Date());
// может работать некорректно или fallback'иться на en-US
Для браузеров ситуация стабильнее, но при бандлинге возникает другая проблема: не сам Intl API увеличивает размер, а полифиллы и библиотеки поверх него.
Сам по себе Intl не попадает в бандл — он встроен в
рантайм. Однако экосистема часто добавляет:
@formatjs/intl-*intl-messageformatreact-intlmoment с локалямиdate-fns/locale/*luxon с набором локалейИменно здесь появляются десятки и сотни килобайт локализационных данных.
Пример проблемы:
import { format } from 'date-fns';
import ru from 'date-fns/locale/ru';
import en from 'date-fns/locale/en';
import de from 'date-fns/locale/de';
Даже если используется только одна локаль, часто подключаются все.
Tree shaking — удаление неиспользуемого кода при сборке (ESM + статический анализ импортов).
Для локалей это означает:
Проблема локалей в том, что они часто экспортируются как большие объектные карты:
export const locales = {
en,
ru,
de,
fr,
...
};
При таком подходе bundler видит использование locales
целиком и не может выкинуть лишнее.
В отличие от библиотек, Intl:
Поэтому:
new Intl.NumberFormat('ru-RU').format(12345.6);
не увеличивает размер бандла вообще.
Ключевая мысль: tree shaking применим не к Intl, а к обвязкам вокруг него.
export * from './locales';
или
import * as locales from './locales';
Такой код превращает локали в неделимый объект.
const allLocales = [en, ru, de, fr, es];
Даже если используется только ru, bundler обязан
сохранить весь массив.
const locale = locales[userLocale];
Если locales импортирован целиком, tree shaking
невозможен.
import ru from 'date-fns/locale/ru';
import en from 'date-fns/locale/en';
Bundler может удалить всё остальное.
Tree shaking работает только при:
import/export)package.json:{
"sideEffects": false
}
// locales/ru.js
export default { ... }
import ru from './locales/ru.js';
Вместо импорта “всего мира локалей” используется стратегия whitelist:
const SUPPORTED_LOCALES = ['en-US', 'ru-RU'];
function formatDate(date, locale) {
const safeLocale = SUPPORTED_LOCALES.includes(locale)
? locale
: 'en-US';
return new Intl.DateTimeFormat(safeLocale).format(date);
}
Здесь нет локалей в бандле — только строки.
Для библиотек, где локали всё же являются JS-модулями, используется ленивый импорт:
async function loadLocale(locale) {
switch (locale) {
case 'ru':
return import('date-fns/locale/ru');
case 'de':
return import('date-fns/locale/de');
default:
return import('date-fns/locale/en-US');
}
}
Такой подход переносит локали в code splitting, а не в основной бандл.
Webpack способен tree shake локали при:
Однако часто локали приходят через CommonJS, что ломает анализ.
Rollup точнее анализирует зависимости и лучше удаляет неиспользуемые локали при ESM.
Vite использует Rollup на этапе build, поэтому поведение аналогично, но dev-режим вообще не бандлит локали заранее.
Использование чистого Intl почти всегда
предпочтительно:
const nf = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'KZT'
});
nf.format(12345.67);
Здесь:
import moment from 'moment';
import 'moment/locale/ru';
import 'moment/locale/de';
import 'moment/locale/fr';
Moment включает локали даже при частичном импорте, что делает tree shaking фактически неэффективным.
import locales from './locales';
Любая ссылка на такой объект блокирует удаление неиспользуемых частей.
Tree shaking не работает при:
Intl вместо полифилловВ больших SPA и SSR-приложениях основная оптимизация локалей достигается не через сам Intl, а через:
Intl остаётся базовым слоем, на который накладываются более тяжёлые i18n-системы, и именно они определяют, будет ли tree shaking эффективным или нет.