Tree shaking

В контексте современной JavaScript-разработки tree shaking становится ключевым механизмом оптимизации сборки, особенно при работе с библиотеками интернационализации, такими как Globalize. Основная задача tree shaking — исключение неиспользуемого кода из финального бандла, что критично для библиотек, работающих с большими наборами локализационных данных.

Globalize построена поверх CLDR (Unicode Common Locale Data Repository), что изначально предполагает работу с массивами данных, включающими числовые форматы, даты, правила плюрализации, валюты и другие региональные особенности. Без корректной архитектуры импортов это приводит к избыточному увеличению размера сборки.


Модульная архитектура и предпосылки tree shaking

Tree shaking возможен только при использовании ESM (ECMAScript Modules). В Globalize это означает:

  • отказ от require() в пользу import
  • использование точечных импортов функций
  • загрузку CLDR-данных по частям
  • отсутствие побочных эффектов в модулях

Современные сборщики (Webpack, Rollup, Vite, esbuild) анализируют граф зависимостей и удаляют неиспользуемые экспортируемые сущности, если соблюдены условия:

  • модули являются “pure”
  • используется статический импорт
  • отсутствуют динамические side effects

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


Проблема монолитных импортов

Наиболее частая ошибка при использовании Globalize — импорт всей библиотеки целиком:

import Globalize from "globalize";

Такой подход:

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

Tree shaking в этом случае либо невозможен, либо крайне ограничен, поскольку сборщик не может определить, какие части действительно используются.


Точечные импорты и их влияние на сборку

Оптимальная стратегия заключается в использовании модульных импортов:

import Globalize from "globalize/dist/globalize";
import numberFormatter from "globalize/dist/globalize/number-formatter";
import dateFormatter from "globalize/dist/globalize/date-formatter";

Однако более современный подход — использование API-ориентированных импортов:

import Globalize from "globalize";

Globalize.load(
  require("cldr-numbers-full/main/ru/numbers"),
  require("cldr-dates-full/main/ru/ca-gregorian")
);

При этом важно понимать: даже если код выглядит модульным, tree shaking зависит от структуры пакета globalize и наличия корректных sideEffects метаданных в package.json.


Роль CLDR и влияние на tree shaking

CLDR-данные — основной источник “тяжести” в Globalize. Они включают:

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

Tree shaking не может удалить CLDR-данные автоматически, если они импортируются глобально:

import "cldr-data";

Такой импорт приводит к полной загрузке набора данных, даже если используется только одна локаль.

Правильный подход — точечная загрузка:

import likelySubtags from "cldr-core/supplemental/likelySubtags";
import plurals from "cldr-core/supplemental/plurals";
import ruNumbers from "cldr-numbers-full/main/ru/numbers";

Side effects и package.json

Для корректной работы tree shaking критично наличие или отсутствие флага sideEffects:

{
  "sideEffects": false
}

Если библиотека или её части имеют побочные эффекты, сборщик не сможет безопасно удалить неиспользуемый код.

В Globalize некоторые модули могут:

  • регистрировать глобальные форматы
  • модифицировать внутренние реестры
  • инициализировать CLDR-кэш

Это означает, что агрессивный tree shaking может привести к ошибкам выполнения при неправильной конфигурации сборщика.


Влияние динамических импортов

Tree shaking теряет эффективность при использовании динамического импорта:

const loadLocale = async (locale) => {
  const cldrData = await import(`cldr-numbers-full/main/${locale}/numbers`);
  Globalize.load(cldrData.default);
};

Проблема заключается в том, что:

  • путь становится недетерминированным
  • сборщик не может предсказать используемые модули
  • увеличивается вероятность загрузки лишних данных

Однако динамический импорт может использоваться для lazy-loading локалей, что частично компенсирует рост бандла.


Webpack и оптимизация Globalize

Webpack требует дополнительной настройки для эффективного tree shaking:

mode: production

module.exports = {
  mode: "production"
};

optimization

optimization: {
  usedExports: true,
  sideEffects: true
}

babel-loader

Важно отключать преобразования, которые ломают ESM:

  • избегать transform-modules-commonjs
  • сохранять import/export

Rollup и более точный tree shaking

Rollup обеспечивает более агрессивное удаление неиспользуемого кода. При работе с Globalize это особенно заметно:

  • удаляются неиспользуемые форматтеры
  • исключаются неиспользуемые CLDR-модули
  • сохраняется только реально используемый функционал

Пример конфигурации:

export default {
  input: "src/index.js",
  output: {
    file: "dist/bundle.js",
    format: "esm"
  },
  treeshake: {
    moduleSideEffects: false
  }
};

Практика минимизации бандла

Эффективная стратегия работы с Globalize в условиях tree shaking включает несколько уровней оптимизации:

1. Импорт только используемых форматтеров

import Globalize from "globalize";
import "globalize/number";

2. Локальная загрузка CLDR

Globalize.load(
  require("cldr-core/supplemental/likelySubtags"),
  require("cldr-numbers-full/main/ru/numbers")
);

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

Каждая локаль должна загружаться отдельно:

  • ru → отдельно
  • en → отдельно
  • de → отдельно

Это позволяет избежать включения всех локалей в финальный бандл.


Частые причины деградации tree shaking

  1. Использование CommonJS вместо ESM
  2. Глобальные импорты CLDR
  3. Динамические require
  4. Отсутствие "sideEffects": false
  5. Импорт всей библиотеки через barrel-файлы
  6. Подключение локалей без разделения по языкам

Поведение tree shaking при использовании форматирования

Функциональность Globalize делится на независимые зоны:

  • числа
  • даты
  • валюты
  • plural rules
  • relative time

Если используется только формат чисел:

Globalize.formatNumber(12345);

и при этом импортированы только числовые модули, tree shaking способен исключить:

  • date-formatter
  • currency-formatter
  • plural-engine
  • relative-time

При корректной конфигурации итоговый бандл может содержать только минимально необходимую часть runtime.


Особенности взаимодействия с bundler-экосистемой

Tree shaking в Globalize не является внутренним механизмом библиотеки. Он полностью зависит от:

  • статического анализа кода
  • структуры импортов
  • формата модулей
  • конфигурации сборщика

Поэтому одинаковый код может давать разные результаты в зависимости от среды:

  • Webpack: частично оптимизирует через usedExports
  • Rollup: максимально агрессивная оптимизация
  • Vite: использует esbuild + Rollup на продакшене

Риски чрезмерной оптимизации

Агрессивный tree shaking может привести к:

  • отсутствию необходимых CLDR-данных в runtime
  • ошибкам форматирования дат
  • неправильным plural rules
  • сбоям при переключении локалей

Особенно это проявляется при:

  • SSR (server-side rendering)
  • гидратации на клиенте
  • ленивой загрузке локалей

Баланс между размером бандла и полнотой данных становится ключевым архитектурным решением при использовании Globalize в крупных приложениях.