Минификация является одним из ключевых этапов оптимизации JavaScript-приложений. На протяжении многих лет стандартом де-факто для сжатия JavaScript-кода выступал Terser, представляющий собой форк проекта UglifyJS с поддержкой современных возможностей ECMAScript. Появление SWC привнесло иной подход к трансформации и минификации кода, основанный на высокопроизводительной реализации на языке Rust.
Хотя обе системы решают одинаковую задачу — уменьшение размера итогового бандла, — способы достижения результата существенно различаются.
Главное отличие заключается в используемых технологиях:
| Характеристика | SWC | Terser |
|---|---|---|
| Язык реализации | Rust | JavaScript |
| Производительность | Очень высокая | Средняя |
| Многопоточность | Поддерживается | Ограничена |
| Использование памяти | Эффективное | Более высокое |
| Скорость обработки крупных проектов | Высокая | Ниже |
Terser выполняется внутри среды Node.js и использует однопоточную модель исполнения JavaScript. SWC компилируется в машинный код и способен значительно быстрее выполнять синтаксический анализ, преобразование AST и генерацию выходного кода.
На небольших проектах разница может быть незаметна, однако при сборке крупных приложений, содержащих тысячи модулей, преимущество SWC становится весьма существенным.
Обе системы работают через промежуточное представление программы — Abstract Syntax Tree (AST).
Общий процесс выглядит следующим образом:
Однако внутренние структуры различаются.
Terser исторически развивался вокруг собственных JavaScript-структур данных. Большое количество объектов создается непосредственно в памяти V8.
Преимущества:
Недостатки:
SWC использует структуры данных языка Rust.
Особенности:
В результате многие операции над AST выполняются быстрее даже без применения дополнительных оптимизаций.
Одной из причин популярности SWC стала именно скорость.
Для типичного приложения на React или Next.js результаты обычно выглядят следующим образом:
| Операция | SWC | Terser |
|---|---|---|
| Парсинг | Быстрее | Медленнее |
| Трансформация | Быстрее | Медленнее |
| Минификация | Быстрее | Медленнее |
| Генерация кода | Быстрее | Медленнее |
Во многих реальных проектах ускорение может составлять от 3 до 20 раз в зависимости от:
Особенно заметна разница в режиме CI/CD, где сборка выполняется многократно.
Скорость не является единственным критерием оценки минификатора.
Важную роль играет размер итогового бандла.
Terser долгое время развивался именно как инструмент агрессивной оптимизации кода.
Он содержит множество специализированных алгоритмов:
В некоторых случаях Terser способен получить немного меньший размер итогового файла.
Например:
function square(x) {
return x * x;
}
console.log(square(5));
После серии внутренних оптимизаций Terser может произвести более агрессивное упрощение отдельных конструкций.
SWC ориентирован на баланс между:
Поэтому некоторые редкие и дорогостоящие оптимизации могут отсутствовать либо быть реализованы менее агрессивно.
На практике разница обычно составляет считанные проценты.
Для большинства приложений выигрыш в скорости сборки оказывается значительно важнее нескольких дополнительных байтов.
Dead Code Elimination (DCE) представляет собой процесс удаления участков программы, которые никогда не выполняются.
Пример:
if (false) {
console.log("debug");
}
После минификации:
Обе системы умеют выполнять подобную оптимизацию.
Terser обладает большим количеством эвристик для поиска неиспользуемых конструкций.
Поддерживаются:
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 использует развитую систему анализа областей видимости.
Возможности:
Пример настройки:
{
mangle: {
reserved: ["React"]
}
}
SWC поддерживает аналогичный механизм.
Пример:
{
"jsc": {
"minify": {
"mangle": true
}
}
}
В большинстве практических сценариев результат оказывается сопоставимым.
Минификаторы активно преобразуют выражения к более компактному виду.
Исходный код:
const value = true;
После обработки:
const value=!0;
Другой пример:
if (a === true) {
}
Преобразуется в:
if(a){}
Подобные микрооптимизации применяются как в Terser, так и в SWC.
Разница обычно проявляется не в самих преобразованиях, а в количестве случаев, которые способен распознать конкретный алгоритм.
Поддержка новых возможностей языка играет важную роль для современных проектов.
Terser поддерживает:
Поддержка новых возможностей обычно появляется после обновления парсера.
Поскольку SWC активно развивается как полноценный транспайлер, новые возможности ECMAScript интегрируются очень быстро.
Поддерживаются:
Это делает SWC особенно привлекательным для проектов, использующих новейшие возможности языка.
Выбор минификатора часто определяется экосистемой.
Традиционная конфигурация:
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserPlugin()
]
}
};
Использование SWC:
module.exports = {
optimization: {
minimize: true
}
};
при подключении соответствующих SWC-инструментов.
Vite долгое время использовал Terser как дополнительный вариант минификации:
export default {
build: {
minify: "terser"
}
};
Современные инструменты всё чаще переходят на более быстрые решения.
Одним из наиболее заметных примеров стала миграция Next.js на SWC.
Ранее сборка выполнялась через Babel и Terser.
После перехода на 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.
Минифицированный код крайне сложно анализировать вручную.
Пример:
function a(b){return b*b}
Без карты исходников определить первоначальную структуру программы практически невозможно.
Обе системы поддерживают генерацию Source Maps.
SWC:
{
"sourceMaps": true
}
Terser:
{
sourceMap: true
}
Использование карт исходников позволяет:
При переходе между инструментами необходимо учитывать несколько факторов.
Иногда итоговый файл после SWC может быть немного больше.
Причины:
Чаще всего разница составляет менее нескольких процентов.
Некоторые проекты используют конструкции, опирающиеся на особенности минификации.
Например:
.name;
.toString().
Подобный код требует дополнительного тестирования после миграции.
Параметры SWC и Terser похожи концептуально, но отличаются по структуре.
Поэтому прямое копирование настроек обычно невозможно.
Использование Terser остаётся оправданным в следующих случаях:
SWC оказывается особенно эффективным в ситуациях, где приоритетом являются скорость и масштабируемость:
С практической точки зрения SWC представляет собой современный высокопроизводительный инструмент, обеспечивающий качество минификации, близкое к Terser, при значительно более высокой скорости обработки кода. Именно поэтому многие современные сборочные системы постепенно переходят на SWC в качестве основного решения для трансформации и минификации JavaScript.