swc-loader: возможности и ограничения

swc-loader — загрузчик для Webpack, использующий компилятор SWC вместо Babel. SWC написан на языке Rust и ориентирован на максимально быструю трансформацию JavaScript и TypeScript-кода. Основная задача загрузчика — транспиляция современного синтаксиса в код, совместимый с целевыми браузерами или средами выполнения.

SWC особенно востребован в крупных проектах, где время сборки становится критичным фактором. В отличие от Babel, выполняющего преобразования через JavaScript-плагины, SWC использует нативный высокопроизводительный движок.

Основные области применения:

  • транспиляция ESNext;
  • поддержка TypeScript;
  • обработка JSX и TSX;
  • минификация;
  • ускорение development-сборок;
  • замена babel-loader в React-проектах;
  • ускорение CI/CD-сборок.

Установка

Минимальная установка включает Webpack, SWC и загрузчик:

npm install webpack webpack-cli swc-loader @swc/core --save-dev

Для React-проектов дополнительно может использоваться:

npm install react react-dom

Базовая настройка swc-loader

Простейшая конфигурация Webpack:

const path = require('path');

module.exports = {
    mode: 'development',

    entry: './src/index.js',

    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'swc-loader'
                }
            }
        ]
    }
};

В таком виде SWC уже способен транспилировать современный JavaScript.


Настройка через options

Большинство параметров задаются внутри jsc.

Пример:

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
        loader: 'swc-loader',
        options: {
            jsc: {
                target: 'es2018'
            }
        }
    }
}

Параметр target

target определяет уровень итогового JavaScript-кода.

Пример:

options: {
    jsc: {
        target: 'es5'
    }
}

Поддерживаемые варианты:

  • es3
  • es5
  • es2015
  • es2016
  • es2017
  • es2018
  • es2019
  • es2020
  • es2021
  • es2022

Чем ниже target, тем больше преобразований выполняется.


Поддержка TypeScript

SWC способен компилировать TypeScript без участия ts-loader.

Настройка:

{
    test: /\.ts$/,
    exclude: /node_modules/,
    use: {
        loader: 'swc-loader',
        options: {
            jsc: {
                parser: {
                    syntax: 'typescript'
                }
            }
        }
    }
}

Обработка TSX

Для React + TypeScript:

{
    test: /\.tsx$/,
    exclude: /node_modules/,
    use: {
        loader: 'swc-loader',
        options: {
            jsc: {
                parser: {
                    syntax: 'typescript',
                    tsx: true
                }
            }
        }
    }
}

JSX и React

SWC поддерживает JSX без Babel.

Конфигурация:

{
    test: /\.jsx$/,
    exclude: /node_modules/,
    use: {
        loader: 'swc-loader',
        options: {
            jsc: {
                parser: {
                    syntax: 'ecmascript',
                    jsx: true
                }
            }
        }
    }
}

React Automatic Runtime

Современный React использует automatic runtime.

Настройка:

options: {
    jsc: {
        parser: {
            syntax: 'ecmascript',
            jsx: true
        },
        transform: {
            react: {
                runtime: 'automatic'
            }
        }
    }
}

Classic Runtime

Старый режим React:

transform: {
    react: {
        runtime: 'classic'
    }
}

В этом режиме требуется импорт React:

import React from 'react';

Настройка development-режима React

SWC умеет автоматически добавлять development-возможности:

transform: {
    react: {
        development: true,
        refresh: true
    }
}

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

  • React Fast Refresh;
  • дополнительные проверки;
  • source maps;
  • улучшенные stack traces.

Поддержка декораторов

SWC поддерживает decorators.

Пример:

options: {
    jsc: {
        parser: {
            syntax: 'typescript',
            decorators: true
        },
        transform: {
            decoratorMetadata: true
        }
    }
}

Dynamic Import

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

import('./module.js');

Webpack автоматически создает отдельные чанки.


Поддержка Class Properties

Современные поля классов:

class User {
    name = 'Alex';
}

Настройка:

parser: {
    syntax: 'ecmascript',
    classProperty: true
}

Поддержка Private Fields

Приватные поля:

class User {
    #token = '123';
}

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


Поддержка Optional Chaining

Современный синтаксис:

user?.profile?.email

SWC преобразует его для старых окружений.


Nullish Coalescing

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

const value = data ?? 'default';

Минификация через SWC

SWC может заменять Terser.

Пример:

optimization: {
    minimize: true,
    minimizer: []
}

С использованием плагина:

npm install swc-minify-webpack-plugin --save-dev

Конфигурация:

const SwcMinifyWebpackPlugin = require('swc-minify-webpack-plugin');

optimization: {
    minimize: true,
    minimizer: [
        new SwcMinifyWebpackPlugin()
    ]
}

Source Maps

Настройка source maps:

devtool: 'source-map'

SWC корректно генерирует карты исходников.


Внешний файл .swcrc

Конфигурацию можно вынести:

{
    "jsc": {
        "target": "es2018",
        "parser": {
            "syntax": "ecmascript",
            "jsx": true
        }
    }
}

Webpack-конфигурация упрощается:

{
    loader: 'swc-loader'
}

Отличия от Babel

Скорость

Главное преимущество SWC — производительность.

На крупных проектах ускорение может достигать:

  • 3x;
  • 5x;
  • 10x;
  • иногда больше.

Особенно заметна разница:

  • при холодных сборках;
  • в CI;
  • в монорепозиториях;
  • при большом количестве TypeScript-файлов.

Архитектурные различия

Babel

  • написан на JavaScript;
  • использует JS AST;
  • огромная экосистема плагинов;
  • высокая гибкость.

SWC

  • написан на Rust;
  • нативное выполнение;
  • высокая скорость;
  • меньшая гибкость.

Ограничения экосистемы SWC

Несмотря на высокую скорость, экосистема SWC существенно меньше Babel.

Некоторые Babel-плагины:

  • отсутствуют;
  • несовместимы;
  • не имеют аналогов.

Это особенно важно в проектах с нестандартными трансформациями.


Ограничения плагинов

Babel обладает зрелой системой AST-плагинов.

SWC пока уступает в:

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

Сложные Babel-конвейеры часто невозможно перенести напрямую.


Ограничения TypeScript

SWC компилирует TypeScript, но не выполняет полноценную проверку типов.

То есть:

swc-loader !== tsc

Для type checking обычно используется:

tsc --noEmit

или:

fork-ts-checker-webpack-plugin

Отсутствие полного соответствия Babel

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

Возможны различия:

  • в decorators;
  • в metadata;
  • в helper-функциях;
  • в edge-case-сценариях;
  • в JSX transform.

При миграции крупных проектов это требует тестирования.


Совместимость с Babel

Возможна гибридная схема:

use: [
    {
        loader: 'babel-loader'
    },
    {
        loader: 'swc-loader'
    }
]

Однако подобная комбинация редко оправдана, так как:

  • усложняет pipeline;
  • увеличивает время сборки;
  • усложняет отладку.

Поддержка Jest

SWC поддерживает Jest через:

npm install @swc/jest --save-dev

Пример:

module.exports = {
    transform: {
        '^.+\\.(t|j)sx?$': '@swc/jest'
    }
};

Использование с Next.js

Современный Next.js по умолчанию использует SWC вместо Babel.

SWC отвечает за:

  • transpilation;
  • minification;
  • Fast Refresh;
  • React transforms.

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

NestJS также поддерживает SWC для ускорения компиляции backend-приложений.


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

SWC особенно эффективен в:

  • Turborepo;
  • Nx;
  • больших workspace-проектах.

Причины:

  • высокая скорость;
  • малое потребление памяти;
  • быстрые incremental rebuilds.

Производительность hot reload

В development-среде SWC заметно сокращает:

  • время rebuild;
  • запуск dev server;
  • задержки HMR.

Это особенно заметно в React-проектах с большим количеством компонентов.


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

Webpack cache отлично сочетается с SWC:

cache: {
    type: 'filesystem'
}

Вместе это дает существенное ускорение повторных сборок.


Сравнение swc-loader и esbuild-loader

SWC

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

  • более качественная совместимость с Babel;
  • лучшая поддержка React;
  • развитая трансформация TypeScript;
  • высокая совместимость с экосистемой.

Недостатки:

  • менее высокая скорость, чем esbuild;
  • ограниченная plugin-система.

esbuild

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

  • экстремальная скорость;
  • минимальное потребление ресурсов.

Недостатки:

  • меньше совместимости;
  • ограниченные трансформации;
  • менее зрелая React-интеграция.

Когда swc-loader особенно эффективен

Наиболее удачные сценарии:

  • большие React-приложения;
  • TypeScript-монорепозитории;
  • CI/CD;
  • development-сборки;
  • миграция с Babel ради ускорения;
  • проекты с десятками тысяч модулей.

Когда SWC может быть плохим выбором

Проблемные сценарии:

  • сложные кастомные Babel-плагины;
  • нестандартные AST-трансформации;
  • специфические experimental features;
  • проекты с глубокой зависимостью от Babel ecosystem;
  • legacy-конфигурации с большим количеством Babel presets.

Типичная production-конфигурация

const path = require('path');

module.exports = {
    mode: 'production',

    entry: './src/index.tsx',

    output: {
        filename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'dist'),
        clean: true
    },

    resolve: {
        extensions: ['.tsx', '.ts', '.js']
    },

    module: {
        rules: [
            {
                test: /\.[jt]sx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'swc-loader',
                    options: {
                        jsc: {
                            target: 'es2018',

                            parser: {
                                syntax: 'typescript',
                                tsx: true
                            },

                            transform: {
                                react: {
                                    runtime: 'automatic'
                                }
                            }
                        }
                    }
                }
            }
        ]
    },

    cache: {
        type: 'filesystem'
    }
};