Skip to content

Compiler features (parser & codegen)

This page is the feature inventory for the current React → Vue pipeline. Implementation lives mainly in src/parser/react.ts (parse JSX/TS into an IR) and src/codegen/vue.ts (print a Vue 3 SFC). Paths refer to the repository root.

For a shorter narrative, see React → Vue mapping. For scope limits, see limitations.


Parsing (src/parser/react.ts)

Input

  • Babel with plugins: typescript, jsx (sourceType: "module").
  • Files are treated as a single React component module (see limits for multi-export cases).

Finding the component

  • export default:
    • Inline function, arrow, or function expression.
    • export default Identifier resolves through bindings to:
      • React.forwardRef / forwardRef (first argument = component function).
      • React.memo / memo, Object.assign (first argument peeled recursively).
    • Identifier chains follow variable declarators until a function is found.
  • If no default export: export named — first export function Name, or export const Name = … where the init resolves like above.

Props (function first parameter)

  • If the first parameter is an ObjectPattern, simple prop keys (non-computed) are collected as prop names for defineProps / toRefs. Other shapes fall through to verbatim handling where applicable.

Component body → setup (parseSetupFromBlock)

Statements are walked in order ( return statements are skipped here; JSX for the main tree is taken separately—see below).

PatternIR / behavior
function declarationVerbatim source slice.
useState / React.useStateStateref(…) + optional functional setter helper when destructuring [x, setX].
useRef / React.useRefRefref(…) in codegen.
useMemo / React.useMemoMemo — stores real callback source for computed(…).
useEffect / React.useEffectEffect — callback + optional deps array source preserved.
const Name = () => <JSX /> or block with JSX returnSubcomponent — inner template extracted to IR for defineComponent + h() in codegen.
Other VariableDeclaration (multiple declarators, no init, unknown callee, etc.)Verbatim.
useEffect as expression statementHandled above; other expression statements → verbatim where not matched.
IfStatement, loops, arbitrary blocksVerbatim source slice.

Main JSX tree

  • From a block body: the last return whose argument is JSXElement or JSXFragment wins (earlier JSX returns are ignored for the main template—supports “guard” early returns).
  • From an arrow with expression body: direct JSX element or fragment becomes the template.

JSX → template IR (jsxToTemplateNode, jsxChildToNode, exprToTemplateNode)

  • Intrinsic vs component: tag names starting with lowercase ASCII and no dotDOM element; otherwise component (PascalCase, Foo.Bar, etc.).
  • Attributes:
    • onXxx on DOMevent bindings (click, change, …) for @ handlers in Vue.
    • onXxx on components → kept as props (React-style callback prop names).
    • Other props: string literals or expression source preserved.
  • {...spread}spreadExprs for v-bind in template / h().
  • Children: elements, fragments, text (trimmed), expression containers.
  • Expressions in children:
    • Ternaryconditional template node (v-if / v-else chain in codegen).
    • && short-circuitconditional (no else).
    • .map(...) on a member expression → list node with iterator source, param name, optional key from child element’s key attribute (then stripped from the inner element).
    • Other expressions → mustache expression nodes.

Code generation (src/codegen/vue.ts)

Output shape

  • Always a Vue 3 SFC: <script setup lang="ts"> + <template> (no separate style block).

Imports from vue

Emitted on demand based on what the component uses, e.g.:

ref, computed, watch, watchEffect, onMounted, toRefs, defineComponent, h, Fragment, …

Props

  • defineProps<{ prop?: unknown … }>() and const { … } = toRefs(props) when prop names were inferred from the destructured first argument.

Hooks → script

IR partEmitted Vue
Stateconst x = ref(…); optional setter: (v) => { x.value = typeof v === 'function' ? v(x.value) : v }
Ref (useRef)const x = ref(…)
Memoconst x = computed(<callback>) with ref-like identifiers rewritten to .value inside the callback (skipping string literals).
EffectNo deps / omitted → watchEffect(cb). Deps []onMounted(cb). Otherwise → watch(<deps>, cb, { flush: 'post' }) with deps rewritten: n.value in array → n; single-element [ref]ref for Vue’s watch API. Effect body: useState names rewritten to .value in verbatim text; stripReactNamespaceTypes applied.
VerbatimState names in object shorthand / bare identifiers → .value; stripReactNamespaceTypes (strip React.FC types, rewrite React.use*use*, useRefref, etc.).
SubcomponentdefineComponent + setup returning render using h() / Fragment for nested JSX-as-Vue.

Template emission

  • Fragments → adjacent roots (no wrapper).
  • Elements: attributes → Vue attrs; classNameclass or :class when the value is dynamic; style object → :style.
  • Boolean DOM attrs (disabled, checked, …): bound as :attr when the value looks like a single identifier (dynamic boolean).
  • Events: @event='handler' with single-quoted escaped content; TypeScript as assertions stripped in handler strings for valid attrs.
  • Spread: v-bind='expr'.
  • Components: :prop bindings; children recurse.
  • Conditionals: v-if on <template>, optional v-else.
  • Lists: <template v-for="(item, index) in iterable" :key="…"> — key from React key or index.

Refs in templates

  • useRef names: .current in expressions rewritten to .value (rewriteCurrent).
  • Heuristic: names ending in Ref also get .current.value.

Handler name stubs

  • For simple identifier handlers referenced in the template but not declared in setup, codegen emits function name() { … }:
    • Special case: one state ref + one such handlerfunction name() { stateName.value++ }.
    • Otherwise → /* TODO: implement */ placeholder.

Edge helpers

  • rewriteRefLikeInScriptExpr: adds .value for state/ref/memo/prop names in computed bodies, excluding inside string literals.
  • quoteVueDirectiveExpr: safe quoting for v-if conditions with quotes or <.
  • Initial ref() values: prop identifiers in useState(initial) rewritten to props.x where applicable; unknown bare identifiers may become a fallback comment (see tests / real inputs).

What is intentionally not done here

  • Import rewriting (React → Vue ecosystem).
  • Full multi-component and re-export modeling.
  • Guaranteed effect cleanup translation—often manual in Vue.

See limitations and roadmap.

Questions? parsajiravand@gmail.com · MIT License · BRANDING.md for name / fork policy · https://cross-framework.netlify.app/ · https://cross-framework-doc.netlify.app/