实现一个swc-plugin

swc介绍

swc(speedy web compiler) 是用Rust编写的高性能JavaScript/TypeScript编译器。其核心功能与 Babel 类似,支持编译、打包、代码压缩等功能,也具备插件能力。

swc对比babel优势

  1. 开发语言的优势

swc 是用 Rust 语言开发的,而 babel 是用 JavaScript 语言开发的。这意味着 swc 可以利用 Rust 的性能优势,如无 GC(垃圾回收机制)、系统级语言、多核 CPU 支持等,而 babel 则受限于 JavaScript 的性能问题,如 GC、单线程等。

  1. AST 树透传

swc 在编译代码时并不需要将 AST 树透传下去,而是直接在内存中进行转换。这样可以避免多次遍历 AST 树,提高编译效率。而 babel 则需要将 AST 树传递给每一个插件,这样会增加编译时间和内存消耗

  1. 编译后的代码更快

swc 可以将 JavaScript 代码编译为 WebAssembly,这样可以提高代码的执行速度。而 babel 则只能编译为 JavaScript 代码,无法利用 WebAssembly 的优势。

swc-plugin开发

创建一个swc项目

首先用cargo安装swc_cli

cargo install swc_cli

通过swc生成项目模版

sql 复制代码
swc plugin new --target-type wasm32-wasi my-first-plugin
rustup target add wasm32-wasi

实现swc-plugin

lib.rs

rust 复制代码
use swc_core::ecma::{
    ast::Program,
    transforms::testing::test_inline,
    visit::{as_folder, FoldWith, VisitMut},
};
use swc_core::plugin::{plugin_transform, proxies::TransformPluginProgramMetadata};
pub struct TransformVisitor;
// 我们需要为TransformVisitor实现VisitMut特征
// 当遍历到对应的ast时,这些方法将会被调用
impl VisitMut for TransformVisitor {
}
// 项目入口
#[plugin_transform]
pub fn process_transform(program: Program, _metadata: TransformPluginProgramMetadata) -> Program {
    program.fold_with(&mut as_folder(TransformVisitor))
}
// 测试用例
test_inline!(
    Default::default(),
    |_| as_folder(TransformVisitor),
    boo,
    // Input codes
    r#"console.log("transform");"#,
    // Output codes after transformed with plugin
    r#"console.log("transform");"#
);

需求拆分

测试驱动开发

了解了上述流程图中的需求后,我们可以采用TTD的开发模式,先编写些测试用例

ini 复制代码
// case1: 没有try...catch...类型节点
test_inline!(
    Default::default(),
    |_| as_folder(CatchClauseVisitor),
    catch_test,
    // Input codes
    r#"
    let a = 1;
    "#,
    // Output codes after transformed with plugin
    r#"
    let a = 1;
    "#
);
// case2: 未导入包名
test_inline!(
    Default::default(),
    |_| as_folder(CatchClauseVisitor),
    catch_test2,
   // Input codes
    r#"try{
        let a = 1;
    }catch(error){
        
    }"#,
    // Output codes after transformed with plugin
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
    }catch(error){
        weirwoodErrorReport(error)
    }"#
);
// case3: 导入包名但未导入该方法
test_inline!(
    Default::default(),
    |_| as_folder(CatchClauseVisitor),
    catch_test3,
    // Input codes
   r#"
    import {initMonitor} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
    }catch(err){
    }
    "#,
    // Output codes after transformed with plugin
    r#"
    import {initMonitor, weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
    }catch(err){
        weirwoodErrorReport(err)
    }
    "#
);
// case4: 已导入包名及方法
test_inline!(
    Default::default(),
    |_| as_folder(CatchClauseVisitor),
    catch_test4,
    // Input codes
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
        let c = 2;
    }catch(error){
        let b = 2;
    }"#,
    // Output codes after transformed with plugin
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
        let c = 2;
    }catch(error){
        let b = 2;
        weirwoodErrorReport(error);
    }"#
);
// case5: 导入包名和方法,且已在catch中使用
test_inline!(
    Default::default(),
    |_| as_folder(CatchClauseVisitor),
    catch_test5,
    // Input codes
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
        let c = 2;
    }catch(error){
        let b = 2;
        weirwoodErrorReport(error);
    }"#,
    // Output codes after transformed with plugin
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
        let c = 2;
    }catch(error){
        let b = 2;
        weirwoodErrorReport(error);
    }"#
);
// case6: try...catch...嵌套情况
test_inline!(
    Default::default(),
    |_| as_folder(CatchClauseVisitor),
    catch_test6,
   // Input codes
    r#"
    try{
        let a = 1;
        let c = 2;
        try {
            let d = 3;
        }catch(err){
            console.log(err);
        }
    }catch(error){
        let b = 2;
    }"#,
    // Output codes after transformed with plugin
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
        let c = 2;
        try {
            let d = 3;
        }catch(err){
            weirwoodErrorReport(err);
            console.log(err);
        }
    }catch(error){
        weirwoodErrorReport(error);
        let b = 2;
    }"#
);

实现Visitor遍历ast

所有的ast节点类型如下:rustdoc.swc.rs/swc_ecma_vi...

可通过如下查看代码转化后的ast节点类型:

我们主要使用visit_mut_modulevisit_mut_catch_clausevisit_mut_module用来处理import的导入,visit_mut_catch_clause用来捕获try...catch...语句

代码暂时无法公开

分离测试模块

目前我们的测试用例同逻辑代码都是一起写在lib.rs下,通常一个复杂的swc-plugin都是由好几个不同的模块组合而成,所以将测试用例与逻辑代码分离是十分重要的,并测试用例有按模块划分的能力。

我们在src下新建一个tests目录,用来存放测试用例。在tests目录下新建common/mod.rs文件 用来实现通用的测试方法,uv_test.rs文件实现本功能的测试用例,mod.rs用来组织各模块的测试用例

目录结构如下:

ruby 复制代码
// common/mod.rs
#[macro_export]
macro_rules! to {
    ($name:ident, $from:expr, $to:expr) => {
        swc_core::ecma::transforms::testing::test_inline!(
            Default::default(),
            |_| swc_core::ecma::visit::as_folder($crate::CatchClauseVisitor::new()),
            $name,
            $from,
            $to
        );
    };
}
ini 复制代码
// uv_test.rs
use crate::to;
to!(
    catch_test_1,
    // Input codes
    r#"
    import {initMonitor} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
    }catch(err){
    }
    "#,
    // Output codes after transformed with plugin
    r#"
    import {initMonitor, weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
    }catch(err){
        weirwoodErrorReport(err)
    }
    "#
);
to!(
    catch_test_2,
    // Input codes
    r#"try{
        let a = 1;
    }catch(error){
        
    }"#,
    // Output codes after transformed with plugin
    r#"
    import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
    try{
        let a = 1;
    }catch(error){
        weirwoodErrorReport(error)
    }"#
);
lua 复制代码
// mod.rs
mod common;
mod uv_test;

发布swc-plugin

swc-plugin的发布和Rust library发布不同,不需要走crates.io,发布流程和npm包的发布一样

npm login
npm publish

swc-plugin使用

  1. 准备一个空项目,执行pnpm init进行初始化
  2. 安装 @swc/cli @swc/core,pnpm i -D @swc/cli @swc/core
  3. 安装刚刚发布的swc-plugin pnpm i swc-plugin
  4. 修改.swcrc或者swc-loader中的配置
css 复制代码
// .swcrc
{
    "$schema": "https://json.schemastore.org/swcrc",
    "jsc": {
      "parser": {
        "syntax": "ecmascript"
      },
      "target": "es2015",
      "experimental": {
        "plugins": [
            ["swc-plugin",{}],
        ]
      }
    },
    "minify": false
  }
  
  // webpack.config.js 
  // swc-loader
  module: {
        rules: [
            {
                test: /.js$/,
                use: [
                    {
                        loader: 'swc-loader',
                        options: {
                            jsc: {
                                parser: { syntax: 'ecmascript' },
                                experimental: {
                                    plugins:  ["swc-plugin",{}],
                                },
                            },
                        },
                    }
                ],
                exclude: /node_modules/,
            },
        ],
    },
  1. 新建一个source.js
javascript 复制代码
try{
}catch(err){
    console.log(err);
}
  1. 使用swc进行编译npx swc source.js -o output.js
javascript 复制代码
// output.js
 import {weirwoodErrorReport} from "@common/utils/basicAbility/monitor";
try {} catch (err) {
    weirwoodErrorReport(err);
    console.log(err);
}

参考文档:

相关推荐
前端与小赵20 小时前
什么是Sass,有什么特点
前端·rust·sass
一个小坑货21 小时前
Rust基础
开发语言·后端·rust
Object~1 天前
【第九课】Rust中泛型和特质
开发语言·后端·rust
码农飞飞1 天前
详解Rust结构体struct用法
开发语言·数据结构·后端·rust·成员函数·方法·结构体
Dontla2 天前
Rust derive macro(Rust #[derive])Rust派生宏
开发语言·后端·rust
fqbqrr2 天前
2411rust,编译时自动检查配置
rust
梦想画家2 天前
用Rust中byteorder包高效处理字节序列
rust·序列化·byteorder·文件编码
beifengtz3 天前
【Rust调用Windows API】读取系统CPU利用率
windows·rust·windows api
梦想画家3 天前
精通Rust系统教程-过程宏入门
rust·元编程·rust宏
fqbqrr3 天前
2411rust,1.81,1.82
rust