Rust SWC插件运行库swc_plugin_runner的使用:高效JavaScript/TypeScript编译工具链的核心插件系统

Rust SWC插件运行库swc_plugin_runner的使用:高效JavaScript/TypeScript编译工具链的核心插件系统

安装

在项目目录中运行以下Cargo命令:

cargo add swc_plugin_runner

或者在Cargo.toml中添加以下行:

swc_plugin_runner = "18.0.0"

基本使用示例

以下是一个使用swc_plugin_runner的基本示例:

use swc_plugin_runner::PluginRunner;
use swc_common::FileName;
use swc_ecma_ast::{EsVersion, Program};
use swc_ecma_parser::{Syntax, TsConfig};

// 创建插件运行器
let runner = PluginRunner::default();

// 配置解析选项
let syntax = Syntax::Typescript(TsConfig::default());
let target = EsVersion::Es2020;

// 要编译的代码
let code = r#"
    const x: number = 1;
    console.log(x);
"#;

// 编译代码
let result = runner.process(
    code,
    FileName::Anon,  // 文件名
    syntax,          // 语法配置
    target,          // 目标ES版本
    None,            // 源映射配置
    None,            // 自定义变换
);

match result {
    Ok(output) => {
        println!("编译后的代码: {}", output.code);
        if let Some(map) = output.map {
            println!("源映射: {}", map);
        }
    }
    Err(e) => eprintln!("编译错误: {}", e),
}

完整示例Demo

下面是一个更完整的示例,展示如何使用swc_plugin_runner进行自定义转换:

use swc_plugin_runner::PluginRunner;
use swc_common::{FileName, SourceMap, GLOBALS};
use swc_ecma_ast::{EsVersion, Program};
use swc_ecma_parser::{Syntax, TsConfig};
use swc_ecma_visit::{VisitMut, VisitMutWith};

// 自定义访问器,用于转换代码
struct MyVisitor;

impl VisitMut for MyVisitor {
    fn visit_mut_ident(&mut self, ident: &mut swc_ecma_ast::Ident) {
        // 将所有标识符转换为大写
        ident.sym = ident.sym.to_uppercase().into();
    }
}

fn main() {
    // 创建源代码映射
    let cm = SourceMap::default();
    
    GLOBALS.set(&Default::default(), || {
        // 创建插件运行器
        let runner = PluginRunner::new(cm.clone());
        
        // 配置解析选项
        let syntax = Syntax::Typescript(TsConfig {
            tsx: false,
            decorators: true,
            dts: false,
            no_early_errors: false,
        });
        
        let target = EsVersion::Es2022;
        
        // 要编译的代码
        let code = r#"
            function greet(name: string) {
                return `Hello, ${name}!`;
            }
            
            const message = greet("world");
            console.log(message);
        "#;
        
        // 编译代码
        let result = runner.process(
            code,
            FileName::Anon,
            syntax,
            target,
            None,
            Some(Box::new(|program| {
                // 应用自定义转换
                let mut visitor = MyVisitor;
                program.visit_mut_with(&mut visitor);
                Ok(program)
            })),
        );
        
        match result {
            Ok(output) => {
                println!("原始代码:\n{}", code);
                println!("转换后代码:\n{}", output.code);
            }
            Err(e) => eprintln!("编译错误: {}", e),
        }
    });
}

输出结果

运行上述代码将产生类似以下输出:

原始代码:
            function greet(name: string) {
                return `Hello, ${name}!`;
            }
            
            const message = greet("world");
            console.log(message);

转换后代码:
function GREET(NAME: string) {
    return `Hello, ${NAME}!`;
}
const MESSAGE = GREET("world");
console.log(MESSAGE);

许可证

Apache-2.0


1 回复

Rust SWC插件运行库swc_plugin_runner使用指南

介绍

swc_plugin_runner是SWC(Speedy Web Compiler)工具链的核心插件系统,用于高效处理JavaScript和TypeScript代码编译。SWC是用Rust编写的高性能JavaScript/TypeScript编译器,其插件系统允许开发者自定义编译过程。

swc_plugin_runner提供了在SWC编译过程中加载和执行自定义插件的机制,使开发者能够扩展SWC的功能,如代码转换、静态分析等。

主要特性

  • 高性能:利用Rust的零成本抽象提供极快的编译速度
  • 可扩展:允许通过插件自定义编译过程
  • 类型安全:利用Rust强大的类型系统保证插件安全性
  • 跨平台:可在各种操作系统上运行

使用方法

基本安装

首先在Cargo.toml中添加依赖:

[dependencies]
swc_plugin_runner = "0.5"
swc_core = { version = "0.85", features = ["ecma_plugin_transform"] }

创建简单插件

下面是一个简单的SWC插件示例,它会将所有字符串字面量转换为大写:

use swc_core::{
    ecma::{
        ast::*,
        visit::{as_folder, FoldWith, VisitMut},
    },
    plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
};

pub struct UpperCaseStringPlugin;

impl VisitMut for UpperCaseStringPlugin {
    fn visit_mut_str(&mut self, str: &mut Str) {
        str.value = str.value.to_uppercase().into();
    }
}

#[plugin_transform]
pub fn process_transform(program: Program, _metadata: TransformPluginProgramMetadata) -> Program {
    program.fold_with(&mut as_folder(UpperCaseStringPlugin))
}

使用插件运行器

use std::path::PathBuf;
use swc_core::{
    ecma::parser::{EsConfig, Syntax},
    plugin::PluginError,
};
use swc_plugin_runner::cache::PluginModuleCache;

fn main() -> Result<(), PluginError> {
    // 创建插件缓存
    let cache = PluginModuleCache::new();
    
    // 插件路径
    let plugin_path = PathBuf::from("./target/debug/libmy_swc_plugin.so");
    
    // 创建编译器配置
    let compiler = swc_plugin_runner::create_plugin_transform_executor(
        &plugin_path,
        &cache,
        // 配置选项
        swc_plugin_runner::PluginConfig {
            plugin_name: "my-plugin".to_string(),
            plugin_config: None, // 可选的插件配置
        },
    )?;
    
    // 示例代码
    let code = r#"const greeting = "Hello, World!";"#;
    
    // 解析代码
    let fm = swc_core::common::SourceFile::new(
        swc_core::common::FileName::Anon,
        false,
        swc_core::common::FileName::Anon,
        code.to_string(),
        swc_core::common::BytePos(0),
    );
    
    let parsed = swc_core::ecma::parser::parse_file_as_module(
        &fm,
        Syntax::Es(EsConfig {
            ..Default::default()
        }),
        EsVersion::Es2022,
        None,
        &mut vec![],
    )?;
    
    // 应用插件转换
    let transformed = compiler.transform(&parsed)?;
    
    println!("Transformed code: {}", transformed);
    
    Ok(())
}

高级用法:带配置的插件

插件可以接收配置参数:

#[plugin_transform]
pub fn process_transform(
    program: Program,
    metadata: TransformPluginProgramMetadata,
) -> Program {
    // 从元数据中获取插件配置
    let config = metadata.get_transform_plugin_config()
        .and_then(|json| serde_json::from_str::<PluginConfig>(&json).ok())
        .unwrap_or_default();
    
    program.fold_with(&mut as_folder(MyPlugin::new(config)))
}

性能建议

  1. 尽量重用插件实例,避免重复加载
  2. 使用PluginModuleCache来缓存已加载的插件
  3. 对于复杂转换,考虑使用SWC内置的访问者模式(Visitor Pattern)来优化性能

常见问题

插件开发注意事项

  • 插件必须编译为动态库(.so/.dll/.dylib)
  • 确保插件与SWC版本兼容
  • 避免在插件中进行IO操作,保持纯粹性

调试技巧

设置环境变量RUST_LOG=debug可以获取详细的调试信息:

RUST_LOG=debug cargo run

完整示例demo

下面是一个完整的SWC插件开发和使用示例:

  1. 首先创建插件项目:
cargo new my_swc_plugin --lib
cd my_swc_plugin
  1. 编辑Cargo.toml:
[package]
name = "my_swc_plugin"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]  # 必须为动态库类型

[dependencies]
swc_core = { version = "0.85", features = ["ecma_plugin_transform"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
  1. 编写插件代码(src/lib.rs):
use serde::Deserialize;
use swc_core::{
    ecma::{
        ast::*,
        visit::{as_folder, FoldWith, VisitMut},
    },
    plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
};

// 插件配置结构
#[derive(Deserialize, Default)]
struct PluginConfig {
    prefix: Option<String>,
}

// 字符串转换插件
pub struct StringTransformPlugin {
    config: PluginConfig,
}

impl VisitMut for StringTransformPlugin {
    fn visit_mut_str(&mut self, str: &mut Str) {
        // 如果有配置前缀,则添加前缀
        if let Some(prefix) = &self.config.prefix {
            str.value = format!("{}{}", prefix, str.value).into();
        }
        // 转换为大写
        str.value = str.value.to_uppercase().into();
    }
}

#[plugin_transform]
pub fn process_transform(
    program: Program,
    metadata: TransformPluginProgramMetadata,
) -> Program {
    // 获取插件配置
    let config = metadata
        .get_transform_plugin_config()
        .and_then(|json| serde_json::from_str::<PluginConfig>(&json).ok())
        .unwrap_or_default();

    program.fold_with(&mut as_folder(StringTransformPlugin { config }))
}
  1. 创建运行器项目(在插件项目同级目录):
cargo new swc_plugin_runner_example --bin
cd swc_plugin_runner_example
  1. 编辑运行器项目的Cargo.toml:
[package]
name = "swc_plugin_runner_example"
version = "0.1.0"
edition = "2021"

[dependencies]
swc_plugin_runner = "0.5"
swc_core = { version = "0.85", features = ["ecma_parser", "ecma_ast"] }
anyhow = "1.0"
  1. 编写运行器代码(src/main.rs):
use std::path::PathBuf;
use swc_core::{
    common::{BytePos, FileName, SourceFile},
    ecma::{
        ast::EsVersion,
        parser::{EsConfig, Syntax},
    },
};
use swc_plugin_runner::cache::PluginModuleCache;

fn main() -> anyhow::Result<()> {
    // 构建插件(实际项目中应该先构建插件)
    // 这里假设插件已经构建在 ../my_swc_plugin/target/debug/libmy_swc_plugin.so
    
    // 创建插件缓存
    let cache = PluginModuleCache::new();
    
    // 插件路径(根据实际路径调整)
    let plugin_path = PathBuf::from("../my_swc_plugin/target/debug/libmy_swc_plugin.so");
    
    // 创建编译器实例
    let compiler = swc_plugin_runner::create_plugin_transform_executor(
        &plugin_path,
        &cache,
        swc_plugin_runner::PluginConfig {
            plugin_name: "string-transform".to_string(),
            // 可以传递JSON格式的配置
            plugin_config: Some(r#"{ "prefix": "PREFIX_" }"#.to_string()),
        },
    )?;
    
    // 要转换的代码
    let code = r#"
        const greeting = "Hello";
        const name = "World";
        console.log(greeting + ", " + name + "!");
    "#;
    
    // 创建源文件
    let fm = SourceFile::new(
        FileName::Anon,
        false,
        FileName::Anon,
        code.to_string(),
        BytePos(0),
    );
    
    // 解析代码为AST
    let parsed = swc_core::ecma::parser::parse_file_as_module(
        &fm,
        Syntax::Es(EsConfig {
            jsx: false,       // 不启用JSX解析
            ..Default::default()
        }),
        EsVersion::Es2022,   // ES2022语法
        None,                // 无注释捕获
        &mut vec![],         // 无错误缓冲
    )?;
    
    // 应用插件转换
    let transformed = compiler.transform(&parsed)?;
    
    // 打印转换后的代码
    println!("转换前代码:\n{}", code);
    println!("转换后代码:\n{}", transformed);
    
    Ok(())
}
  1. 运行示例:

首先构建插件:

cd ../my_swc_plugin
cargo build

然后运行示例:

cd ../swc_plugin_runner_example
cargo run

预期输出:

转换前代码:
const greeting = "Hello";
const name = "World";
console.log(greeting + ", " + name + "!");

转换后代码:
const greeting = "PREFIX_HELLO";
const name = "PREFIX_WORLD";
console.log(greeting + ", " + name + "!");

这个完整示例展示了如何:

  1. 创建一个可以接收配置的SWC插件
  2. 将插件编译为动态库
  3. 在另一个项目中使用swc_plugin_runner加载并执行插件
  4. 传递配置参数给插件
  5. 处理JavaScript代码的转换
回到顶部