The babel-plugin-minimact has a clear modular structure that should be mirrored in the RustScript conversion.
babel-plugin-minimact/
├── index.cjs # Main plugin entry point (Babel visitor)
└── src/
├── processComponent.cjs # Component/hook processing orchestrator
├── utils/
│ ├── helpers.cjs # ✅ CONVERTED → helpers.rsc
│ ├── hexPath.cjs # ✅ CONVERTED → hex_path.rsc
│ ├── pathAssignment.cjs # TODO: Convert
│ └── styleConverter.cjs # ✅ CONVERTED → style_converter.rsc
├── types/
│ └── typeConversion.cjs # ✅ CONVERTED → type_conversion.rsc
├── analyzers/
│ ├── analyzePluginUsage.cjs
│ ├── classification.cjs # ✅ CONVERTED → classification.rsc
│ ├── dependencies.cjs # ✅ CONVERTED → dependencies.rsc
│ ├── detection.cjs # ✅ CONVERTED → detection.rsc
│ ├── hookAnalyzer.cjs # ✅ CONVERTED → hook_analyzer.rsc
│ ├── hookDetector.cjs # ✅ CONVERTED → hook_detector.rsc
│ ├── hookImports.cjs # ✅ CONVERTED → hook_imports.rsc
│ ├── propTypeInference.cjs # ✅ CONVERTED → prop_type_inference.rsc
│ └── timelineAnalyzer.cjs # ✅ CONVERTED → timeline_analyzer.rsc
├── extractors/
│ ├── hooks.cjs
│ ├── localVariables.cjs
│ ├── templates.cjs
│ ├── loopTemplates.cjs
│ ├── structuralTemplates.cjs
│ ├── conditionalElementTemplates/ # ✅ Many files CONVERTED
│ └── expressionTemplates/ # ✅ Many files CONVERTED
└── generators/
├── csharpFile.cjs
├── hookClassGenerator.cjs
└── expressions/ # ✅ Many files CONVERTED
└── calls/ # ✅ CONVERTED → string_methods.rsc, etc.
Purpose: Babel plugin entry point - sets up visitor pattern
-
Plugin Initialization (
prehook)- Save original source code (before JSX transformation)
- Used for
.tsx.keysfile generation
-
Program Enter
- Initialize component array
- Collect top-level helper functions
-
Function Visitor
- Call
processComponent()for each function - Detect components vs regular functions
- Call
-
Program Exit
- Generate
.tsx.keysfile (hex paths added to JSX) - Generate C# files
- Generate template JSON
- Generate hooks JSON
- Generate structural changes JSON
- Generate
// This would be in a plugin (not writer) in RustScript
plugin MinimactBabelPlugin {
struct State {
components: Vec<ComponentInfo>,
original_code: Option<Str>,
hex_path_gen: HexPathGenerator,
}
fn init() -> State {
State {
components: vec![],
original_code: None,
hex_path_gen: HexPathGenerator::default(),
}
}
// Pre-hook equivalent
fn visit_program_enter(prog: &mut Program, ctx: &Context) {
// Save original code
self.original_code = Some(ctx.source_code.clone());
}
fn visit_function_declaration(func: &mut FunctionDeclaration, ctx: &Context) {
// Process component
if let Some(component) = process_component(func, &self) {
self.components.push(component);
}
}
fn visit_program_exit(prog: &mut Program, ctx: &Context) {
// Generate all output files
for component in &self.components {
generate_csharp_file(component);
generate_template_json(component);
generate_hooks_json(component);
}
}
}
Purpose: Main component processing logic
-
Component Name Detection (line 35)
const componentName = getComponentName(path);
- Uses
helpers.cjs→ ✅ Usehelpers.rsc
- Uses
-
Custom Hook Detection (line 40)
if (isCustomHook(path)) { return processCustomHook(path, state); }
- Uses
analyzers/hookDetector.cjs→ ✅ Usehook_detector.rsc
- Uses
-
Component Name Validation (line 45)
if (componentName[0] !== componentName[0].toUpperCase()) return;
- Should use
is_component_name()from helpers.rsc
- Should use
-
Component Initialization (lines 63-90)
const component = { name: componentName, props: [], useState: [], useClientState: [], useStateX: [], useEffect: [], useRef: [], // ... many more fields };
-
Imported Hooks Analysis (line 53)
const importedHooks = analyzeImportedHooks(state.file.path, state);
- Uses
analyzers/hookImports.cjs→ ✅ Usehook_imports.rsc
- Uses
-
External Imports Tracking (lines 93-121)
- Track lodash, dayjs, etc. for client-side computation
- Needed for hybrid rendering classification
-
Props Extraction (lines 123-160)
- Extract from function parameters
- TypeScript type annotation → C# type
- Uses
types/typeConversion.cjs→ ✅ Usetype_conversion.rsc
-
Hook Extraction (lines 168-226)
- Extract
useState,useEffect,useRef, custom hooks - Extract local variables
- Extract helper functions
- Extract render body (JSX return statement)
- Extract
-
Prop Type Inference (line 230)
inferPropTypes(component, body);
- Uses
analyzers/propTypeInference.cjs→ ✅ Useprop_type_inference.rsc
- Uses
-
Hex Path Assignment (lines 234-246)
const pathGen = new HexPathGenerator(); assignPathsToJSX(component.renderBody, '', pathGen, t, null, null, structuralChanges, isHotReload);
- Uses
utils/hexPath.cjs→ ✅ Usehex_path.rsc - Uses
utils/pathAssignment.cjs→ TODO: Convert
- Uses
-
Template Extraction (lines 254-269)
extractTemplates(component); extractAttributeTemplates(component); extractLoopTemplates(component); extractStructuralTemplates(component); extractConditionalElementTemplates(component); extractExpressionTemplates(component);
- Uses various
extractors/modules → ✅ Many CONVERTED
- Uses various
-
Plugin Usage Analysis (line 272)
component.pluginUsages = analyzePluginUsage(component.renderBody);
- Uses
analyzers/analyzePluginUsage.cjs→ ✅ Useanalyze_plugin_usage.rsc
- Uses
-
Timeline Analysis (line 277)
const timelineTemplates = analyzeTimeline(component);
- Uses
analyzers/timelineAnalyzer.cjs→ ✅ Usetimeline_analyzer.rsc
- Uses
-
JSX Replacement (line 286)
path.node.body.body = [t.returnStatement(t.nullLiteral())];
- Replace JSX with
nullafter all extraction
- Replace JSX with
-
Component Storage (line 289)
state.file.minimactComponents.push(component);
use "../rustscript-plugin-minimact/utils/helpers.rsc" {
escape_csharp_string,
is_component_name,
get_component_name
};
use "../rustscript-plugin-minimact/utils/hex_path.rsc" { HexPathGenerator };
use "../rustscript-plugin-minimact/types/type_conversion.rsc" {
infer_type,
ts_type_to_csharp_type
};
use "../rustscript-plugin-minimact/analyzers/classification.rsc" { classify_node };
use "../rustscript-plugin-minimact/analyzers/detection.rsc" {
has_spread_props,
has_dynamic_children
};
use "../rustscript-plugin-minimact/analyzers/hook_detector.rsc" { is_custom_hook };
use "../rustscript-plugin-minimact/analyzers/hook_imports.rsc" { analyze_imported_hooks };
use "../rustscript-plugin-minimact/analyzers/prop_type_inference.rsc" { infer_prop_types };
use "../rustscript-plugin-minimact/analyzers/timeline_analyzer.rsc" { analyze_timeline };
fn process_component(path: &FunctionPath, state: &mut State) -> Option<ComponentInfo> {
// 1. Get component name
let component_name = get_component_name(path)?;
// 2. Check if it's a custom hook
if is_custom_hook(path) {
return process_custom_hook(path, state);
}
// 3. Validate component name (PascalCase)
if !is_component_name(&component_name) {
return None;
}
// 4. Initialize component struct
let mut component = ComponentInfo::new(component_name);
// 5. Analyze imported hooks
let imported_hooks = analyze_imported_hooks(state.file.path, state);
component.imported_hook_metadata = imported_hooks;
// 6. Track external imports (lodash, dayjs, etc.)
track_external_imports(state.file.path, &mut component);
// 7. Extract props from parameters
extract_props_from_params(path.node.params, &mut component);
// 8. Extract hooks, local variables, helper functions
extract_component_body(path, &mut component);
// 9. Infer prop types from usage
infer_prop_types(&mut component, path.node.body);
// 10. Assign hex paths to JSX
let mut path_gen = HexPathGenerator::default();
let structural_changes = assign_paths_to_jsx(
&mut component.render_body,
"",
&mut path_gen,
state.is_hot_reload
);
component.structural_changes = structural_changes;
// 11. Extract templates
extract_templates(&mut component);
extract_attribute_templates(&mut component);
extract_loop_templates(&mut component);
extract_structural_templates(&mut component);
extract_conditional_element_templates(&mut component);
extract_expression_templates(&mut component);
// 12. Analyze plugin usage
component.plugin_usages = analyze_plugin_usage(&component.render_body);
// 13. Analyze timeline
component.timeline_templates = analyze_timeline(&component);
// 14. Replace JSX with null (after extraction)
replace_jsx_with_null(path);
Some(component)
}
| Original File | Converted File | Test File | Status |
|---|---|---|---|
utils/helpers.cjs |
helpers.rsc |
test_helpers.rsc |
✅ PASSING |
utils/hexPath.cjs |
hex_path.rsc |
test_hex_path.rsc |
✅ PASSING |
utils/styleConverter.cjs |
style_converter.rsc |
test_style_converter_integration.rsc |
✅ PASSING |
types/typeConversion.cjs |
type_conversion.rsc |
test_type_conversion_integration.rsc |
✅ PASSING |
analyzers/classification.cjs |
classification.rsc |
test_classification.rsc |
✅ PASSING |
analyzers/detection.cjs |
detection.rsc |
test_detection.rsc |
✅ PASSING |
analyzers/hookDetector.cjs |
hook_detector.rsc |
test_hook_detector.rsc |
✅ PASSING |
extractors/.../isSimpleExpression.cjs |
is_simple_expression.rsc |
test_is_simple_expression.rsc |
✅ PASSING |
extractors/.../buildMemberPath.cjs |
build_member_path.rsc |
test_build_member_path.rsc |
✅ PASSING |
generators/.../handleStringMethods.cjs |
string_methods.rsc |
test_string_methods.rsc |
✅ PASSING |
-
utils/pathAssignment.cjs- Assigns hex paths to JSX tree- Critical for hot reload
- Used in processComponent.cjs line 246
-
extractors/hooks.cjs- Extracts useState, useEffect, etc.- Core hook extraction logic
-
extractors/localVariables.cjs- Extracts local variables- Needed for component state tracking
-
extractors/templates.cjs- Main template extraction- Core prediction system
-
generators/csharpFile.cjs- Generates C# files- Final output generation
-
❌ Duplicates logic instead of using converted helpers
- Has its own
is_pascal_case()instead of usingis_component_name() - Has its own
infer_csharp_type()instead of usinginfer_type() - Has its own
expr_to_csharp()that doesn't escape strings
- Has its own
-
❌ Missing critical steps from processComponent.cjs:
- No imported hooks analysis
- No external imports tracking
- No prop type inference
- No hex path assignment
- No template extraction (multiple phases)
- No plugin usage analysis
- No timeline analysis
-
❌ Incomplete C# generation
- Only generates skeleton class
- Doesn't generate VNode tree from JSX
- Missing method generation
- Missing field generation
Replace minimact_full.rsc with modular structure:
minimact-transpiler/
├── main.rsc # Plugin entry point (like index.cjs)
├── process_component.rsc # Component processor (like processComponent.cjs)
└── (use all existing helper modules from rustscript-plugin-minimact/)
-
✅ DONE: Convert helper utilities (helpers, hex_path, type_conversion, etc.)
- 11/11 tests passing
-
IN PROGRESS: Create comprehensive integration
- Mirror processComponent.cjs flow
- Use all converted helpers
-
TODO: Convert remaining critical files
- pathAssignment.cjs
- hooks.cjs (extractor)
- templates.cjs
- csharpFile.cjs (generator)
-
TODO: Create full plugin equivalent
- RustScript plugin that mirrors index.cjs
- Calls process_component
- Generates all output files
The babel-plugin-minimact has a well-structured, modular architecture. The RustScript conversion should:
✅ Mirror this structure - Don't reinvent the wheel ✅ Reuse converted helpers - All tested and working ✅ Follow the same flow - processComponent.cjs is the blueprint ✅ Generate dual targets - Babel + SWC from one codebase
The current minimact_full.rsc is a starting point but needs significant refactoring to match the proven architecture of processComponent.cjs.