Сжатие кода: compress

Этап compress в SWC отвечает за семантическое упрощение и сокращение JavaScript-кода на уровне абстрактного синтаксического дерева (AST). В отличие от модуля mangle, который изменяет только идентификаторы, compress анализирует структуру программы, устраняет лишние конструкции, упрощает выражения и выполняет агрессивные оптимизации без изменения поведения программы.

Основная цель — уменьшение размера итогового кода и повышение его эффективности за счёт удаления избыточной логики и статически вычислимых выражений.

Ключевые операции этапа включают:

  • удаление недостижимого кода (dead code elimination)
  • свёртку константных выражений (constant folding)
  • упрощение логических выражений
  • инлайнинг простых конструкций
  • устранение побочных неиспользуемых выражений
  • оптимизацию условных операторов

Место compress в архитектуре SWC

Внутренний пайплайн SWC можно представить как последовательность преобразований AST:

  1. Парсинг исходного кода в AST
  2. Преобразования и трансформации (transforms)
  3. Оптимизация compress
  4. Манглинг идентификаторов mangle
  5. Генерация кода (codegen)

Этап compress работает на промежуточном AST, когда структура программы уже нормализована, но имена переменных ещё не изменены. Это важно, поскольку оптимизации зависят от семантики и требуют читаемых ссылок на идентификаторы.


Константные вычисления и свёртка выражений

Одной из ключевых задач compress является вычисление выражений на этапе компиляции.

Примеры преобразований:

  • 2 + 35
  • 10 * 1001000
  • “a” + “b”“ab”
  • true && falsefalse

В AST это реализуется как проход по узлам выражений с попыткой определить их статическую вычислимость. Если оба операнда выражения являются литералами, SWC выполняет вычисление и заменяет поддерево на новый литерал.

Особое внимание уделяется безопасным операциям: деление на ноль, переполнения чисел IEEE-754 и побочные эффекты функций не оптимизируются без явной гарантии отсутствия side effects.


Удаление недостижимого кода

Dead Code Elimination (DCE) — одна из самых агрессивных оптимизаций.

Типичные случаи:

if (false) {
  doSomething();
}

После compress:

Также удаляются:

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

Особый случай — работа с модулями ES:

import { a } from "lib";

console.log(1);

Если a нигде не используется, импорт может быть удалён.


Упрощение логических выражений

Этап compress активно сокращает булеву алгебру:

  • !!xBoolean(x)
  • x && truex
  • x && falsefalse
  • x || falsex
  • x || truetrue

Также происходит нормализация условных выражений:

if (a === true)

может быть преобразовано в:

if (a)

если типовая информация и контекст позволяют исключить неоднозначность.


Инлайнинг и упрощение выражений

SWC выполняет ограниченный инлайнинг выражений, когда это безопасно:

const x = 10;
const y = x + 5;

становится:

const y = 15;

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

Также упрощаются конструкции:

  • тернарные операторы
  • последовательности выражений (comma operator)
  • цепочки логических операторов

Работа с побочными эффектами

Ключевой аспект compress — анализ side effects.

Функция считается безопасной для удаления или инлайна, если:

  • она не изменяет внешнее состояние
  • не вызывает I/O
  • не обращается к глобальным объектам

Пример:

pureFunction();

Если pureFunction определена как чистая (или помечена через pure_funcs), вызов может быть удалён:

Конфигурация позволяет явно указывать такие функции:

{
  "jsc": {
    "minify": {
      "compress": {
        "pure_funcs": ["console.log"]
      }
    }
  }
}

Обработка условных конструкций

compress преобразует сложные ветвления в более компактные формы.

Пример:

if (a) {
  b();
} else {
  c();
}

При известном значении a на этапе компиляции:

a ? b() : c();

Если a статически истинно:

b();

Если ложно:

c();

Оптимизация циклов

Некоторые оптимизации затрагивают циклы:

  • удаление пустых циклов
  • упрощение условий выхода
  • свёртка констант в условиях

Пример:

while (false) {
  work();
}

исчезает полностью.


Взаимодействие compress и mangle

Хотя compress и mangle часто используются вместе, их роли разделены:

  • compress работает с логикой и структурой кода
  • mangle работает с именами идентификаторов

Порядок критичен: сначала выполняется compress, затем mangle. Это позволяет оптимизатору видеть семантически значимые имена до их сокращения.


Конфигурация compress в SWC

Настройка выполняется через .swcrc:

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    },
    "minify": {
      "compress": {
        "unused": true,
        "dead_code": true,
        "conditionals": true,
        "evaluate": true,
        "booleans": true,
        "drop_console": false,
        "drop_debugger": true,
        "keep_fargs": false,
        "pure_getters": true,
        "join_vars": true
      },
      "mangle": true
    }
  }
}

Ключевые параметры:

  • unused — удаление неиспользуемых переменных
  • dead_code — удаление недостижимых блоков
  • conditionals — упрощение условных выражений
  • evaluate — вычисление константных выражений
  • booleans — упрощение булевой логики
  • drop_console — удаление console-вызовов
  • drop_debugger — удаление debugger-инструкций
  • join_vars — объединение объявлений переменных

Анализ побочных эффектов выражений

Оптимизатор строит модель побочных эффектов для каждого узла AST. Любое выражение классифицируется как:

  • чистое (pure)
  • потенциально чистое
  • имеющее побочные эффекты

Это позволяет безопасно удалять конструкции вида:

a();
b();

если a() признана чистой и её результат не используется.


Упрощение последовательностей выражений

Кома-оператор и цепочки выражений оптимизируются:

a(), b(), c();

может быть преобразовано в:

c();

если a() и b() не имеют побочных эффектов.


Обработка объектов и массивов

compress способен сворачивать структуры:

const obj = {
  a: 1,
  b: 2
};

Если свойства не изменяются, возможны оптимизации доступа и удаление неиспользуемых полей.

Для массивов:

const arr = [1, 2, 3];

могут применяться оптимизации при частичном использовании элементов.


Ограничения оптимизации

Несмотря на агрессивность, compress не выполняет:

  • межмодульную глубокую оптимизацию без дополнительного анализа
  • полную интерпретацию динамического кода (eval, with)
  • сложные доказательства эквивалентности функций
  • оптимизацию при неопределённых side effects

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


Типичные сценарии применения

Этап compress наиболее эффективен в следующих случаях:

  • клиентские веб-приложения с большим бандлом
  • библиотеки, распространяемые через npm
  • серверный код, собираемый в единый файл
  • подготовка production-сборок с минимальным размером

Наибольший эффект достигается при сочетании:

  • tree shaking на уровне сборщика
  • compress в SWC
  • mangle идентификаторов