Особенности минификации в сравнении с terser

Минификация является одним из ключевых этапов оптимизации JavaScript-приложений. На протяжении многих лет стандартом де-факто для сжатия JavaScript-кода выступал Terser, представляющий собой форк проекта UglifyJS с поддержкой современных возможностей ECMAScript. Появление SWC привнесло иной подход к трансформации и минификации кода, основанный на высокопроизводительной реализации на языке Rust.

Хотя обе системы решают одинаковую задачу — уменьшение размера итогового бандла, — способы достижения результата существенно различаются.

Основа производительности

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

Характеристика SWC Terser
Язык реализации Rust JavaScript
Производительность Очень высокая Средняя
Многопоточность Поддерживается Ограничена
Использование памяти Эффективное Более высокое
Скорость обработки крупных проектов Высокая Ниже

Terser выполняется внутри среды Node.js и использует однопоточную модель исполнения JavaScript. SWC компилируется в машинный код и способен значительно быстрее выполнять синтаксический анализ, преобразование AST и генерацию выходного кода.

На небольших проектах разница может быть незаметна, однако при сборке крупных приложений, содержащих тысячи модулей, преимущество SWC становится весьма существенным.


Подход к построению AST

Обе системы работают через промежуточное представление программы — Abstract Syntax Tree (AST).

Общий процесс выглядит следующим образом:

  1. Парсинг исходного кода.
  2. Формирование AST.
  3. Анализ дерева.
  4. Оптимизация узлов.
  5. Генерация минимизированного кода.

Однако внутренние структуры различаются.

AST в Terser

Terser исторически развивался вокруг собственных JavaScript-структур данных. Большое количество объектов создается непосредственно в памяти V8.

Преимущества:

  • зрелость реализации;
  • высокая совместимость;
  • большое количество оптимизаций, накопленных годами.

Недостатки:

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

AST в SWC

SWC использует структуры данных языка Rust.

Особенности:

  • строгая типизация;
  • отсутствие лишних аллокаций памяти;
  • высокая скорость обхода дерева;
  • безопасная работа с памятью без garbage collector.

В результате многие операции над AST выполняются быстрее даже без применения дополнительных оптимизаций.


Скорость минификации

Одной из причин популярности SWC стала именно скорость.

Для типичного приложения на React или Next.js результаты обычно выглядят следующим образом:

Операция SWC Terser
Парсинг Быстрее Медленнее
Трансформация Быстрее Медленнее
Минификация Быстрее Медленнее
Генерация кода Быстрее Медленнее

Во многих реальных проектах ускорение может составлять от 3 до 20 раз в зависимости от:

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

Особенно заметна разница в режиме CI/CD, где сборка выполняется многократно.


Качество сжатия

Скорость не является единственным критерием оценки минификатора.

Важную роль играет размер итогового бандла.

Преимущество Terser

Terser долгое время развивался именно как инструмент агрессивной оптимизации кода.

Он содержит множество специализированных алгоритмов:

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

В некоторых случаях Terser способен получить немного меньший размер итогового файла.

Например:

function square(x) {
    return x * x;
}

console.log(square(5));

После серии внутренних оптимизаций Terser может произвести более агрессивное упрощение отдельных конструкций.

Подход SWC

SWC ориентирован на баланс между:

  • скоростью;
  • размером бандла;
  • корректностью преобразований.

Поэтому некоторые редкие и дорогостоящие оптимизации могут отсутствовать либо быть реализованы менее агрессивно.

На практике разница обычно составляет считанные проценты.

Для большинства приложений выигрыш в скорости сборки оказывается значительно важнее нескольких дополнительных байтов.


Удаление мертвого кода

Dead Code Elimination (DCE) представляет собой процесс удаления участков программы, которые никогда не выполняются.

Пример:

if (false) {
    console.log("debug");
}

После минификации:

Обе системы умеют выполнять подобную оптимизацию.

Terser

Terser обладает большим количеством эвристик для поиска неиспользуемых конструкций.

Поддерживаются:

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

SWC

SWC также реализует DCE, однако исторически его алгоритмы были менее агрессивными.

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


Переименование идентификаторов

Одной из важнейших техник минификации является mangling — сокращение имен переменных и функций.

Исходный код:

const calculateTotalPrice = (products) => {
    return products.reduce((sum, item) => sum + item.price, 0);
};

После минификации:

const a=a=>a.reduce((a,b)=>a+b.price,0);

Реализация в Terser

Terser использует развитую систему анализа областей видимости.

Возможности:

  • переименование локальных переменных;
  • переименование аргументов функций;
  • переименование свойств объектов;
  • настройка списков исключений.

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

{
    mangle: {
        reserved: ["React"]
    }
}

Реализация в SWC

SWC поддерживает аналогичный механизм.

Пример:

{
  "jsc": {
    "minify": {
      "mangle": true
    }
  }
}

В большинстве практических сценариев результат оказывается сопоставимым.


Сжатие литералов и выражений

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

Исходный код:

const value = true;

После обработки:

const value=!0;

Другой пример:

if (a === true) {
}

Преобразуется в:

if(a){}

Подобные микрооптимизации применяются как в Terser, так и в SWC.

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


Работа с современным JavaScript

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

Terser

Terser поддерживает:

  • ES2015;
  • ES2016;
  • ES2017;
  • ES2018;
  • ES2019;
  • ES2020+.

Поддержка новых возможностей обычно появляется после обновления парсера.

SWC

Поскольку SWC активно развивается как полноценный транспайлер, новые возможности ECMAScript интегрируются очень быстро.

Поддерживаются:

  • optional chaining;
  • nullish coalescing;
  • private fields;
  • decorators;
  • top-level await;
  • современные синтаксические расширения.

Это делает SWC особенно привлекательным для проектов, использующих новейшие возможности языка.


Интеграция с бандлерами

Выбор минификатора часто определяется экосистемой.

Webpack

Традиционная конфигурация:

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin()
        ]
    }
};

Использование SWC:

module.exports = {
    optimization: {
        minimize: true
    }
};

при подключении соответствующих SWC-инструментов.

Vite

Vite долгое время использовал Terser как дополнительный вариант минификации:

export default {
    build: {
        minify: "terser"
    }
};

Современные инструменты всё чаще переходят на более быстрые решения.

Next.js

Одним из наиболее заметных примеров стала миграция Next.js на SWC.

Ранее сборка выполнялась через Babel и Terser.

После перехода на SWC были получены:

  • заметное сокращение времени сборки;
  • снижение нагрузки на процессор;
  • ускорение hot reload;
  • уменьшение времени CI-сборок.

Настройка минификации в SWC

Базовая конфигурация:

{
  "jsc": {
    "minify": {
      "compress": true,
      "mangle": true
    }
  },
  "minify": true
}

Основные параметры:

Параметр Назначение
compress Сжатие выражений
mangle Переименование идентификаторов
minify Включение минификации
keepClassNames Сохранение имен классов
keepFnNames Сохранение имен функций

Пример:

{
  "jsc": {
    "minify": {
      "compress": true,
      "mangle": true,
      "keepFnNames": true
    }
  }
}

Сохранение имен функций и классов

Некоторые библиотеки используют имена функций во время выполнения.

Например:

class UserService {}
console.log(UserService.name);

При агрессивной минификации результат может измениться:

class a{}
console.log(a.name);

Это способно нарушить работу:

  • систем сериализации;
  • механизмов внедрения зависимостей;
  • библиотек рефлексии;
  • средств отладки.

Поэтому SWC позволяет сохранять исходные имена:

{
  "jsc": {
    "minify": {
      "keepClassNames": true,
      "keepFnNames": true
    }
  }
}

Аналогичные возможности присутствуют и в Terser.


Генерация Source Maps

Минифицированный код крайне сложно анализировать вручную.

Пример:

function a(b){return b*b}

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

Обе системы поддерживают генерацию Source Maps.

SWC:

{
  "sourceMaps": true
}

Terser:

{
    sourceMap: true
}

Использование карт исходников позволяет:

  • выполнять отладку продакшен-сборок;
  • анализировать ошибки в браузере;
  • восстанавливать исходные позиции строк и столбцов.

Особенности миграции с Terser на SWC

При переходе между инструментами необходимо учитывать несколько факторов.

Возможные различия в размере бандла

Иногда итоговый файл после SWC может быть немного больше.

Причины:

  • менее агрессивные оптимизации;
  • отличия в алгоритмах compress;
  • различия в обработке AST.

Чаще всего разница составляет менее нескольких процентов.

Различия в поведении нестандартного кода

Некоторые проекты используют конструкции, опирающиеся на особенности минификации.

Например:

  • чтение имени функции через .name;
  • динамическое создание классов;
  • анализ строкового представления функции через .toString().

Подобный код требует дополнительного тестирования после миграции.

Различия в конфигурации

Параметры SWC и Terser похожи концептуально, но отличаются по структуре.

Поэтому прямое копирование настроек обычно невозможно.


Когда предпочтителен Terser

Использование Terser остаётся оправданным в следующих случаях:

  • критически важен минимальный размер итогового бандла;
  • проект содержит специфические настройки compress;
  • инфраструктура уже построена вокруг Terser;
  • требуется максимальная совместимость со старыми конфигурациями.

Когда предпочтителен SWC

SWC оказывается особенно эффективным в ситуациях, где приоритетом являются скорость и масштабируемость:

  • крупные корпоративные приложения;
  • монорепозитории;
  • большие React-проекты;
  • проекты на Next.js;
  • частые CI/CD-сборки;
  • локальная разработка с постоянными пересборками.

С практической точки зрения SWC представляет собой современный высокопроизводительный инструмент, обеспечивающий качество минификации, близкое к Terser, при значительно более высокой скорости обработки кода. Именно поэтому многие современные сборочные системы постепенно переходят на SWC в качестве основного решения для трансформации и минификации JavaScript.