ReluxScript
A language that compiles to both Babel (JavaScript) and SWC (Rust) plugins from a single source.
Overview
ReluxScript is a domain-specific language for writing AST transformation plugins. Write your plugin once in ReluxScript, and compile it to:
- Babel plugin (JavaScript) - for Node.js/npm ecosystem
- SWC plugin (Rust) - for high-performance native builds
Installation
Usage
Check a file for errors
Build a plugin
# Build for both targets (default)
# Build for Babel only
# Build for SWC only
Debug commands
# Tokenize (view lexer output)
# Parse (view AST)
Language Syntax
ReluxScript uses a Rust-like syntax optimized for AST transformations.
Basic Plugin Structure
/// My transformation plugin
plugin MyPlugin {
/// Visit function declarations
pub fn visit_function_declaration(node: &FunctionDeclaration) {
let name = node.id.name.clone();
if name.starts_with("use") {
// Transform hooks...
}
}
}
Structs
struct Component {
name: Str,
props: Vec<Prop>,
hooks: Vec<Str>,
}
struct Prop {
name: Str,
prop_type: Str,
}
Pattern Matching with matches!
pub fn visit_call_expression(node: &CallExpression) {
if matches!(node.callee, Identifier) {
let name = node.callee.name.clone();
// ...
}
}
Traverse Blocks
Traverse into child nodes with nested visitors:
pub fn visit_function_declaration(node: &FunctionDeclaration) {
let mut hooks: Vec<Str> = vec![];
traverse(node.body) {
fn visit_call_expression(call: &CallExpression) {
if matches!(call.callee, Identifier) {
if call.callee.name.starts_with("use") {
hooks.push(call.callee.name.clone());
}
}
}
}
}
Collections
// Vec
let mut items: Vec<Str> = vec![];
items.push("hello");
let joined = items.join(", ");
// HashMap
let mut map: HashMap<Str, Str> = HashMap::new();
map.insert("key", "value");
if map.contains_key(&"key") {
let value = map.get(&"key");
}
// HashSet
let mut set: HashSet<Str> = HashSet::new();
set.insert("item");
if set.contains(&"item") {
// ...
}
String Formatting
let name = "World";
let greeting = format!("Hello, {}!", name);
// Multi-line
let code = format!(
"public class {} {{\n // body\n}}",
class_name
);
// Concatenation
let path = base + "/" + &filename;
// String builder
let mut s = String::new();
s.push_str("line 1\n");
s.push_str("line 2\n");
File I/O
use fs;
fn save_output(content: &Str, path: &Str) {
fs::create_dir_all(&"output").unwrap();
fs::write(path, content).unwrap();
}
fn load_config(path: &Str) -> Str {
return fs::read_to_string(path).unwrap();
}
JSON Serialization
use json;
#[derive(Serialize)]
struct Config {
name: Str,
version: Str,
}
fn save_config(config: &Config) {
let json_str = json::to_string_pretty(config).unwrap();
fs::write(&"config.json", &json_str).unwrap();
}
AST Node Types
ReluxScript includes mappings for all Babel/SWC AST nodes. Common ones:
Expressions
IdentifierCallExpressionMemberExpressionArrayExpressionObjectExpressionArrowFunctionExpressionJSXElement
Statements
FunctionDeclarationVariableDeclarationVariableDeclaratorBlockStatementReturnStatementIfStatement
TypeScript
TSInterfaceDeclarationTSTypeAnnotationTSPropertySignature
Patterns
ArrayPatternObjectPattern
Generated Output
Babel Output
module.exports = ;
SWC Output
use ;
;
Project Structure
reluxscript/
├── src/
│ ├── lib.rs # Library exports
│ ├── main.rs # CLI entry point
│ ├── lexer/ # Tokenizer
│ ├── parser/ # Parser and AST definitions
│ ├── semantic/ # Type checking, resolution, lowering
│ ├── codegen/ # Babel and SWC code generators
│ └── mapping/ # AST node/field mappings
├── examples/ # Example ReluxScript plugins
├── tests/
│ └── enhancements/ # Feature test cases
└── Cargo.toml
Examples
See the examples/ directory for sample plugins:
build_member_path.rsc- Helper function for building member expressionstsx_parser_test/- Extract interfaces and hooks from TSX
Documentation
License
MIT