实现一个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);
}

参考文档:

相关推荐
唐 城3 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
从善若水5 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
gerrylon0075 小时前
rust学习: 有用的命令
rust
brrdg_sefg13 小时前
Rust 在前端基建中的使用
前端·rust·状态模式
m0_7482309414 小时前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
SomeB1oody21 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody21 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
itas1092 天前
Rust调用C动态库
c语言·rust·bindgen·bindings·rust c绑定
SomeB1oody2 天前
【Rust自学】5.1. 定义并实例化struct
开发语言·后端·rust
m0_748236112 天前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust