swc介绍
swc(speedy web compiler) 是用Rust编写的高性能JavaScript/TypeScript编译器。其核心功能与 Babel 类似,支持编译、打包、代码压缩等功能,也具备插件能力。
swc对比babel优势
- 开发语言的优势
swc 是用 Rust 语言开发的,而 babel 是用 JavaScript 语言开发的。这意味着 swc 可以利用 Rust 的性能优势,如无 GC(垃圾回收机制)、系统级语言、多核 CPU 支持等,而 babel 则受限于 JavaScript 的性能问题,如 GC、单线程等。
- AST 树透传
swc 在编译代码时并不需要将 AST 树透传下去,而是直接在内存中进行转换。这样可以避免多次遍历 AST 树,提高编译效率。而 babel 则需要将 AST 树传递给每一个插件,这样会增加编译时间和内存消耗
- 编译后的代码更快
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_module
和visit_mut_catch_clause
,visit_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使用
- 准备一个空项目,执行pnpm init进行初始化
- 安装 @swc/cli @swc/core,
pnpm i -D @swc/cli @swc/core
- 安装刚刚发布的swc-plugin
pnpm i swc-plugin
- 修改.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/,
},
],
},
- 新建一个
source.js
javascript
try{
}catch(err){
console.log(err);
}
- 使用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);
}