Плагин SWC в экосистеме JavaScript представляет собой расширение
трансформационного пайплайна компилятора, написанное на Rust и собранное
в WebAssembly. При публикации в npm такой плагин становится
распределяемым модулем, который может быть подключён через конфигурацию
@swc/core и использован в сборках,
аналогичных Babel-плагинам, но с иной моделью исполнения — без
интерпретируемого AST-плагин API в JavaScript.
Ключевая особенность SWC-плагинов заключается в том, что их логика выполняется не в Node.js напрямую, а внутри WASM-рантайма SWC, что накладывает строгие требования к структуре пакета и способу его сборки.
Типичный проект плагина содержит несколько обязательных слоёв:
Пример структуры:
swc-plugin-example/
├── Cargo.toml
├── src/
│ └── lib.rs
├── pkg/
│ ├── swc_plugin_bg.wasm
│ ├── swc_plugin.js
│ └── package.json
├── index.js
├── package.json
└── README.md
Папка pkg формируется автоматически инструментами сборки
WASM и содержит артефакты, которые будут опубликованы в npm.
SWC-плагин требует Rust toolchain с поддержкой
wasm32-unknown-unknown:
rustup target add wasm32-unknown-unknown
Дополнительно используется набор макросов SWC:
[dependencies]
swc_core = { version = "0.x", features = ["ecma_plugin_transform"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Ключевая зависимость — swc_core, которая предоставляет
AST-типы и инфраструктуру плагинов.
Основная логика плагина реализуется через функцию трансформации модуля:
use swc_core::ecma::ast::*;
use swc_core::plugin::plugin_transform;
pub fn process_transform(program: Program) -> Program {
match program {
Program::Module(module) => {
let transformed = Module {
body: module.body,
..module
};
Program::Module(transformed)
}
other => other,
}
}
Функция помечается макросом #[plugin_transform], который
генерирует WASM-совместимый интерфейс.
Внутри этой функции можно:
Сборка осуществляется через wasm-pack или
cargo с целевым таргетом:
cargo build --release --target wasm32-unknown-unknown
Однако SWC требует дополнительной упаковки через @swc/plugin-wasm-pack
или аналогичный pipeline.
Результатом становится директория pkg, содержащая:
.wasm бинарник
package.json
Для интеграции с Node.js создаётся слой экспорта:
const plugin = require("./pkg/swc_plugin.js");
module.exports = plugin.default;
Этот файл становится точкой входа npm-пакета и проксирует WASM-реализацию.
Критически важная часть публикации — корректное описание пакета:
{
"name": "swc-plugin-example",
"version": "1.0.0",
"main": "index.js",
"files": [
"pkg",
"index.js"
],
"keywords": [
"swc",
"plugin",
"transform",
"wasm"
],
"license": "MIT",
"type": "commonjs"
}
Важно включать только необходимые артефакты, иначе размер пакета резко увеличивается из-за WASM бинарника.
После публикации в npm плагин подключается через конфигурацию SWC:
{
"jsc": {
"experimental": {
"plugins": [
["swc-plugin-example", {}]
]
}
}
}
Второй аргумент массива представляет параметры плагина, сериализуемые в JSON и передаваемые в Rust-runtime.
SWC позволяет передавать конфигурацию в плагин через сериализацию:
Rust-сторона:
use serde::Deserialize;
#[derive(Deserialize)]
struct PluginOptions {
enable_feature: bool,
}
Использование:
pub fn process_transform(program: Program, config: PluginOptions) -> Program {
if config.enable_feature {
// трансформация
}
program
}
JS-конфигурация:
["swc-plugin-example", { "enable_feature": true }]
SWC-плагины чувствительны к версии @swc/core. Несовместимость ABI может
приводить к падению загрузки WASM.
Практика версионирования:
swc_core версии в Cargo.toml
@swc/core
{
"peerDependencies": {
"@swc/core": "^1.3.0"
}
}
WASM-файлы могут занимать значительный объём, поэтому применяются техники оптимизации:
wasm-opt (Binaryen)
lto = true в Cargo
[profile.release]
lto = true
opt-level = "z"
Дополнительно исключаются исходники Rust из npm публикации через
.npmignore или поле files.
Перед публикацией требуется сборка WASM артефактов:
npm run build
Далее публикация:
npm publish --access public
Критически важно:
pkg/
main entry
В автоматизированных пайплайнах обычно используется GitHub Actions:
name: publish
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Add WASM target
run: rustup target add wasm32-unknown-unknown
- name: Build
run: cargo build --release --target wasm32-unknown-unknown
- name: Publish
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
При обновлении @swc/core без пересборки WASM
возникает ошибка загрузки плагина.
Если pkg не включён в files, npm публикует
пустую или неработоспособную сборку.
SWC ABI между Rust и JS строго фиксирован. Любое расхождение приводит к runtime panic.
Если main указывает не на JS-обёртку, а на WASM файл
напрямую, загрузка плагина невозможна.
SWC загружает плагины через WASM-линковку, передавая:
Плагин возвращает модифицированный AST, который далее проходит стадию генерации кода.
Модель исполнения строго функциональная: отсутствует доступ к Node.js API, файловой системе или сети, что делает плагины детерминированными и изолированными.
На практике используются несколько архитектурных подходов:
Композиция логики предпочтительнее монолитных трансформеров, так как SWC выполняет плагины в ограниченном runtime.
Отладка осуществляется через:
console_error_panic_hook
web_sys (ограниченно)
swc_ecma_parser локально
Пример включения panic hook:
console_error_panic_hook::set_once();
Это позволяет получать стек вызовов при падении WASM.
Тестирование выполняется через Rust unit tests:
#[test]
fn test_transform() {
let input = "const a = 1;";
let output = transform(input);
assert_eq!(output.contains("const a"), true);
}
Также применяется snapshot testing AST:
При использовании monorepo структура усложняется:
Важно избегать дублирования swc_core версий между пакетами.
Жизненный цикл SWC npm-плагина включает последовательность: