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

参考文档:

相关推荐
新知图书2 小时前
Rust编程的作用域与所有权
开发语言·后端·rust
许野平18 小时前
Rust: Warp RESTful API 如何得到客户端IP?
tcp/ip·rust·restful·ip地址
许野平21 小时前
Rust:Result 和 Error
开发语言·后端·rust·error·result
Freestyle Coding1 天前
使用rust自制操作系统内核
c语言·汇编·microsoft·rust·操作系统
许野平2 天前
Rust 编译器使用的 C++ 编译器吗?
c++·rust
怪我冷i2 天前
Rust GUI框架Tauri V1 入门
rust
新知图书2 天前
Rust的常量
算法·机器学习·rust
白总Server2 天前
php语言基本语法
开发语言·ide·后端·golang·rust·github·php
凄凄迷人2 天前
前端基于Rust实现的Wasm进行图片压缩的技术文档
前端·rust·wasm·图片压缩
winddevil2 天前
[rCore学习笔记 027]地址空间
rust·嵌入式·rcore