Tree shaking и dead code elimination

Inferno изначально проектировался как высокопроизводительный UI-фреймворк с минимальным runtime-весом. Существенная часть этой философии реализуется не только внутри самого ядра, но и за счёт корректной интеграции с современными инструментами сборки. Tree shaking и dead code elimination становятся ключевыми механизмами уменьшения финального бандла без потери функциональности.

Inferno распространяется в виде ES-модулей, что принципиально важно. Именно статическая структура import / export позволяет сборщику анализировать зависимости на этапе компиляции и определять, какие части кода реально используются.

Tree shaking: статический анализ графа зависимостей

Tree shaking — это процесс удаления неиспользуемых экспортов на основе анализа графа модулей. В контексте Inferno это особенно эффективно из-за мелкозернистого разбиения API.

Пример структуры импорта:

import { render } from 'inferno';
import Component from 'inferno-component';

Если используется только функция render, сборщик может отбросить остальные экспорты из пакета inferno, при условии, что:

  • используется ES-модульная версия пакета;
  • отсутствуют побочные эффекты при импорте;
  • сборщик настроен на tree shaking (Rollup, Webpack в production-режиме, Vite).

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

Именованные экспорты против namespace-импортов

Критически важный момент — способ импорта. Namespace-импорты (import * as Inferno from 'inferno') существенно ухудшают эффективность tree shaking, так как сборщик вынужден считать весь namespace потенциально используемым.

Предпочтительный стиль:

import { createVNode } from 'inferno';

Нежелательный стиль:

import * as Inferno from 'inferno';
Inferno.createVNode(...);

Во втором случае вся публичная поверхность модуля становится «живой», даже если фактически используется одна функция.

Dead Code Elimination как следующий этап

Tree shaking определяет, какие экспорты не нужны. Dead code elimination (DCE) удаляет код, который стал недостижимым после этого анализа. В Inferno это особенно заметно при использовании feature-флагов и условной логики, зависящей от compile-time констант.

Пример:

if (process.env.NODE_ENV !== 'production') {
  validateProps(props);
}

При корректной настройке сборщика process.env.NODE_ENV подменяется строковым литералом. Минификатор (Terser, ESBuild) видит заведомо ложное условие и полностью удаляет ветку с validateProps.

Inferno активно использует подобные паттерны для отладки, проверок типов и предупреждений, которые полностью исчезают из production-сборки.

Побочные эффекты и поле sideEffects

Для корректной работы tree shaking сборщик должен знать, какие файлы могут иметь побочные эффекты. В package.json Inferno указывает:

{
  "sideEffects": false
}

Это прямое заявление о том, что импорт любого файла не приводит к выполнению значимого кода, если его экспорты не используются. Такое объявление позволяет Webpack удалять даже целые файлы, если они не задействованы.

Если бы Inferno регистрировал глобальные обработчики, модифицировал прототипы или выполнял инициализацию при импорте, это было бы невозможно.

Мелкая декомпозиция API

Inferno избегает монолитных объектов и классов. Вместо этого API разбито на небольшие функции и модули:

  • inferno
  • inferno-component
  • inferno-create-element
  • inferno-hydrate

Каждый пакет можно подключать независимо. Это означает, что приложение, использующее только render и функциональные компоненты, не тянет код, связанный с классами, жизненным циклом и совместимостью с React-подобным API.

Пример минимального использования:

import { render } from 'inferno';
import { createElement } from 'inferno-create-element';

Ни Component, ни lifecycle-хуки, ни вспомогательные утилиты не попадают в итоговый бандл.

Влияние JSX и фабрик элементов

Использование JSX не мешает tree shaking, если правильно настроен transpiler. При компиляции JSX в вызовы createVNode или createElement сборщик видит обычные функции.

Ключевой момент — отсутствие автоматического импорта всего Inferno. Предпочтительная настройка:

/** @jsx createVNode */
import { createVNode } from 'inferno';

В этом случае используется ровно одна функция, и никакой лишний код не подтягивается.

Автоматические runtime-импорты, аналогичные старому React JSX-runtime, ухудшают контроль над итоговым размером.

Условные экспорты и разные сборки

Inferno поставляется с несколькими вариантами сборки: development и production. Выбор осуществляется через поле exports и условные импорты, поддерживаемые современными сборщиками.

Production-версия:

  • не содержит проверок;
  • не включает предупреждения;
  • оптимизирована под агрессивный DCE.

Development-версия:

  • содержит дополнительные ветки кода;
  • предоставляет подробные сообщения об ошибках.

Сборщик выбирает нужную реализацию автоматически, что усиливает эффект dead code elimination без участия разработчика приложения.

Взаимодействие с минификаторами

Tree shaking не удаляет код сам по себе, он лишь помечает его как неиспользуемый. Физическое удаление выполняет минификатор.

Inferno хорошо сочетается с:

  • Terser — за счёт предсказуемой структуры кода;
  • ESBuild — благодаря простым условиям и отсутствию динамики;
  • SWC — из-за чистых ES-модулей.

Особенно эффективно удаляются:

  • неиспользуемые lifecycle-хуки;
  • ветки с проверками окружения;
  • вспомогательные функции для отладки.

Типичные ошибки, мешающие оптимизации

Несколько распространённых паттернов полностью блокируют tree shaking:

  • динамический require;
  • вычисляемые имена экспортов;
  • прокидывание всего модуля как объекта;
  • модификация импортированных сущностей.

Inferno сознательно не использует подобные приёмы в публичном API, что делает его код «прозрачным» для статического анализа.

Практический эффект на размер бандла

В минимальной конфигурации Inferno-приложение может занимать менее 10 КБ gzip. Это достигается не магией, а строгим следованием правилам, необходимым для tree shaking и dead code elimination:

  • отсутствие побочных эффектов;
  • чистые функции;
  • условный код, зависящий от compile-time констант;
  • отказ от глобального состояния.

Эти же принципы должны соблюдаться и в коде приложения, чтобы потенциал Inferno был реализован полностью.