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

参考文档:

相关推荐
hikktn6 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
睡觉谁叫~~~6 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
音徽编程6 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
梦想画家19 小时前
快速解锁Rust Slice特性
开发语言·rust·slice
良技漫谈20 小时前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
monkey_meng1 天前
【Rust实现命令模式】
开发语言·设计模式·rust·命令模式
Dontla1 天前
《Rust语言圣经》Rust教程笔记17:2.Rust基础入门(2.6模式匹配)2.6.2解构Rust Option<T>
笔记·算法·rust
Source.Liu1 天前
【用Rust写CAD】前言
开发语言·rust
良技漫谈2 天前
Rust移动开发:Rust在Android端集成使用介绍
android·程序人生·rust·kotlin·学习方法
大鲤余2 天前
rust 中if let、match -》 options和Result枚举类型
开发语言·后端·rust