# How to Build and Run SWC Plugin Transformations with WebAssembly

> Learn to build and run SWC plugin transformations using WebAssembly. Scaffold, compile, and load WASM plugins efficiently with SWC's runtime for custom code transformations.

- Repository: [swc/swc](https://github.com/swc-project/swc)
- Tags: how-to-guide
- Published: 2026-06-15

---

**SWC executes custom transform plugins as WebAssembly binaries using a Wasmer-based runtime, where plugins are scaffolded with `swc plugin new`, compiled to `wasm32-unknown-unknown` or `wasm32-wasip1`, and loaded via `compile_wasm_plugins` before being executed through the abstract `Runtime` trait.**

SWC is a Rust-based compiler that supports extensible transformations through WebAssembly plugins. This architecture allows developers to write custom AST transforms in Rust, compile them to portable Wasm modules, and execute them safely within SWC's multi-threaded pipeline. Understanding how to build and run SWC plugin transformations with WebAssembly enables you to create reusable, high-performance code transformations that work across JavaScript and Rust environments.

## Architecture of the SWC Plugin System

WebAssembly support in SWC centers on the **Wasmer** runtime. The system uses three core components: the plugin compiler (`compile_wasm_plugins`), the runtime abstraction (`Runtime` trait), and the transform executor (`RustPlugins`).

When SWC initializes, it preloads Wasm bytes into a shared cache. During transformation, the `RustPlugins` Pass serializes the AST into `PluginSerializedBytes`, passes it to the Wasm module via the runtime, and deserializes the result back into a `Program` AST.

## Step 1: Scaffold a New Plugin Project

Use the CLI to generate a template project with the correct Cargo configuration.

```bash
swc plugin new plugins/my-plugin \
  --target-type wasm32-unknown-unknown \
  --name my-plugin

```

This command creates:
- [`Cargo.toml`](https://github.com/swc-project/swc/blob/main/Cargo.toml) configured as a `cdylib`
- [`.cargo/config.toml`](https://github.com/swc-project/swc/blob/main/.cargo/config.toml) with aliases for `wasm32-unknown-unknown` and `wasm32-wasip1`
- [`src/lib.rs`](https://github.com/swc-project/swc/blob/main/src/lib.rs) with a starter `#[plugin_transform]` function
- [`package.json`](https://github.com/swc-project/swc/blob/main/package.json) for npm distribution

## Step 2: Implement the Transform Logic

Edit [`src/lib.rs`](https://github.com/swc-project/swc/blob/main/src/lib.rs) to implement your AST manipulation using the `#[plugin_transform]` macro.

```rust
use swc_core::ecma::{
    ast::{Ident, Program},
    visit::{VisitMut, VisitMutWith},
};
use swc_core::plugin::{plugin_transform, proxies::TransformPluginProgramMetadata};

pub struct RenameVisitor;

impl VisitMut for RenameVisitor {
    fn visit_mut_ident(&mut self, i: &mut Ident) {
        i.sym = "__renamed".into();
    }
}

#[plugin_transform]
pub fn rename_all(program: Program, _metadata: TransformPluginProgramMetadata) -> Program {
    program.visit_mut_with(&mut RenameVisitor);
    program
}

```

The **macro** handles low-level pointer interop required by the Wasm host, allowing you to work with standard Rust types while the runtime manages memory safety across the host-guest boundary.

## Step 3: Compile to WebAssembly

Build the plugin using the generated cargo alias defined in [`.cargo/config.toml`](https://github.com/swc-project/swc/blob/main/.cargo/config.toml).

```bash
cd plugins/my-plugin
cargo build-wasm32 --release

```

This produces `target/wasm32-unknown-unknown/release/my_plugin.wasm`. For WASI support, use `cargo build-wasip1` instead.

## How SWC Loads and Executes Plugins at Runtime

Understanding the runtime internals helps debug performance and compatibility issues.

### Plugin Compilation and Caching

In [`crates/swc/src/plugin.rs`](https://github.com/swc-project/swc/blob/main/crates/swc/src/plugin.rs), the `compile_wasm_plugins` function resolves plugin paths, loads raw Wasm bytes, and stores them in **PLUGIN_MODULE_CACHE**. This cache prevents redundant compilation across multiple transformation calls.

### The Runtime Trait

The `Runtime` trait in [`crates/swc_plugin_runner/src/runtime.rs`](https://github.com/swc-project/swc/blob/main/crates/swc_plugin_runner/src/runtime.rs) abstracts the Wasm execution environment. The default Wasmer implementation provides:
- Module instantiation
- Memory allocation
- Serialized data transfer
- Transform invocation

### Execution Flow

`RustPlugins::apply_inner` performs the actual transformation:
1. Serializes the current `Program` AST into `PluginSerializedBytes`
2. Calls `create_plugin_transform_executor` with the cached module
3. The Wasmer runtime calls the exported `transform` function in the Wasm module
4. Deserializes the returned bytes back into a `Program` AST

## Running Transformations with Your Plugin

### Command Line Usage

Run the compiled Wasm plugin directly via the SWC CLI:

```bash
swc -p ./my_plugin.wasm input.js -o output.js

```

### JavaScript/Node.js API

Load and execute the plugin via `@swc/core` by passing the Wasm bytes to the experimental plugins array:

```javascript
const swc = require("@swc/core");
const fs = require("fs");

const pluginWasm = fs.readFileSync("./my_plugin.wasm");

const result = swc.transformSync("let x = 1;", {
  jsc: {
    experimental: {
      plugins: [
        [pluginWasm, {}] // Tuple of [wasm_bytes, config_json]
      ]
    }
  }
});

console.log(result.code); // "let __renamed = 1;"

```

### Direct Rust API

For Rust-based tooling, manually configure the plugin pipeline:

```rust
use swc_core::plugin::{PluginConfig, compile_wasm_plugins};
use swc_plugin_runner::wasmer::WasmerRuntime;

// Preload plugins into cache
let plugin_cfg = PluginConfig(
    "path/to/my_plugin.wasm".into(), 
    serde_json::json!({})
);
compile_wasm_plugins(None, &[plugin_cfg.clone()], &WasmerRuntime)?;

```

## Key Source Files

Reference these files when debugging or extending functionality:

- [[`crates/swc/src/plugin.rs`](https://github.com/swc-project/swc/blob/main/crates/swc/src/plugin.rs)](https://github.com/swc-project/swc/blob/main/crates/swc/src/plugin.rs) – Plugin loading and `RustPlugins` implementation
- [[`crates/swc_plugin_runner/src/runtime.rs`](https://github.com/swc-project/swc/blob/main/crates/swc_plugin_runner/src/runtime.rs)](https://github.com/swc-project/swc/blob/main/crates/swc_plugin_runner/src/runtime.rs) – `Runtime` trait and Wasmer integration
- [[`crates/swc_cli_impl/src/commands/plugin.rs`](https://github.com/swc-project/swc/blob/main/crates/swc_cli_impl/src/commands/plugin.rs)](https://github.com/swc-project/swc/blob/main/crates/swc_cli_impl/src/commands/plugin.rs) – Scaffolding logic for `swc plugin new`

## Summary

- **Scaffold** new plugins using `swc plugin new` to generate correct Cargo configuration and boilerplate in [`src/lib.rs`](https://github.com/swc-project/swc/blob/main/src/lib.rs)
- **Implement** transforms using the `#[plugin_transform]` macro, which handles Wasm host interop automatically
- **Compile** to `wasm32-unknown-unknown` or `wasm32-wasip1` using the provided cargo aliases
- **Cache** occurs via `compile_wasm_plugins` in [`crates/swc/src/plugin.rs`](https://github.com/swc-project/swc/blob/main/crates/swc/src/plugin.rs), storing modules in `PLUGIN_MODULE_CACHE`
- **Execute** through the `Runtime` trait in [`crates/swc_plugin_runner/src/runtime.rs`](https://github.com/swc-project/swc/blob/main/crates/swc_plugin_runner/src/runtime.rs), which uses Wasmer to instantiate and call the Wasm module
- **Serialize** the AST before passing to the plugin, then deserialize the transformed result back into the pipeline via `RustPlugins::apply_inner`

## Frequently Asked Questions

### What is the difference between `wasm32-unknown-unknown` and `wasm32-wasip1` targets?

The `wasm32-unknown-unknown` target produces a pure WebAssembly module without system dependencies, making it smaller and faster to load. The `wasm32-wasip1` target includes WASI syscalls for file system or clock access, which is necessary if your plugin needs system capabilities. Most SWC plugins use `wasm32-unknown-unknown` unless they specifically require I/O operations.

### How does SWC handle plugin errors and panics?

SWC's Wasmer runtime isolates each plugin in a separate WebAssembly instance. If a plugin panics or returns an error, the runtime catches the trap and returns a Rust `Result` error, preventing the host process from crashing. The error propagates through `RustPlugins::apply_inner` and can be handled by the calling transformation pipeline.

### Can I distribute SWC plugins via npm?

Yes. The scaffold command generates a [`package.json`](https://github.com/swc-project/swc/blob/main/package.json) configured to run the cargo build during `prepublishOnly`. You can publish the compiled `.wasm` file as a binary attachment, allowing JavaScript consumers to load it directly via `fs.readFileSync` and pass it to `@swc/core`'s experimental plugins array.

### Why does SWC use Wasmer instead of Wasmtime or other runtimes?

The `Runtime` trait in [`crates/swc_plugin_runner/src/runtime.rs`](https://github.com/swc-project/swc/blob/main/crates/swc_plugin_runner/src/runtime.rs) abstracts the underlying Wasm engine, but the default implementation uses Wasmer for its lightweight footprint and excellent Rust integration. This choice enables near-native performance while maintaining a stable ABI for plugin authors, though the trait design allows for future runtime swapping if needed.