Appearance
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, orfunction expression. export default Identifierresolves 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.
- Inline
- If no default export:
export named— firstexport function Name, orexport const Name = …where the init resolves like above.
Props (function first parameter)
- If the first parameter is an
ObjectPattern, simplepropkeys (non-computed) are collected as prop names fordefineProps/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).
| Pattern | IR / behavior |
|---|---|
function declaration | Verbatim source slice. |
useState / React.useState | State — ref(…) + optional functional setter helper when destructuring [x, setX]. |
useRef / React.useRef | Ref — ref(…) in codegen. |
useMemo / React.useMemo | Memo — stores real callback source for computed(…). |
useEffect / React.useEffect | Effect — callback + optional deps array source preserved. |
const Name = () => <JSX /> or block with JSX return | Subcomponent — inner template extracted to IR for defineComponent + h() in codegen. |
Other VariableDeclaration (multiple declarators, no init, unknown callee, etc.) | Verbatim. |
useEffect as expression statement | Handled above; other expression statements → verbatim where not matched. |
IfStatement, loops, arbitrary blocks | Verbatim source slice. |
Main JSX tree
- From a block body: the last
returnwhose argument isJSXElementorJSXFragmentwins (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 dot → DOM element; otherwise component (PascalCase,
Foo.Bar, etc.). - Attributes:
onXxxon DOM → event bindings (click,change, …) for@handlers in Vue.onXxxon components → kept as props (React-style callback prop names).- Other props: string literals or expression source preserved.
{...spread}→spreadExprsforv-bindin template /h().- Children: elements, fragments, text (trimmed), expression containers.
- Expressions in children:
- Ternary → conditional template node (
v-if/v-elsechain in codegen). &&short-circuit → conditional (no else)..map(...)on a member expression → list node with iterator source, param name, optionalkeyfrom child element’skeyattribute (then stripped from the inner element).- Other expressions → mustache
expression nodes.
- Ternary → conditional template node (
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 … }>()andconst { … } = toRefs(props)when prop names were inferred from the destructured first argument.
Hooks → script
| IR part | Emitted Vue |
|---|---|
| State | const x = ref(…); optional setter: (v) => { x.value = typeof v === 'function' ? v(x.value) : v } |
Ref (useRef) | const x = ref(…) |
| Memo | const x = computed(<callback>) with ref-like identifiers rewritten to .value inside the callback (skipping string literals). |
| Effect | No 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. |
| Verbatim | State names in object shorthand / bare identifiers → .value; stripReactNamespaceTypes (strip React.FC types, rewrite React.use* → use*, useRef → ref, etc.). |
| Subcomponent | defineComponent + setup returning render using h() / Fragment for nested JSX-as-Vue. |
Template emission
- Fragments → adjacent roots (no wrapper).
- Elements: attributes → Vue attrs;
className→classor:classwhen the value is dynamic;styleobject →:style. - Boolean DOM attrs (
disabled,checked, …): bound as:attrwhen the value looks like a single identifier (dynamic boolean). - Events:
@event='handler'with single-quoted escaped content; TypeScriptasassertions stripped in handler strings for valid attrs. - Spread:
v-bind='expr'. - Components:
:propbindings; children recurse. - Conditionals:
v-ifon<template>, optionalv-else. - Lists:
<template v-for="(item, index) in iterable" :key="…">— key from Reactkeyorindex.
Refs in templates
useRefnames:.currentin expressions rewritten to.value(rewriteCurrent).- Heuristic: names ending in
Refalso 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 handler →
function name() { stateName.value++ }. - Otherwise →
/* TODO: implement */placeholder.
- Special case: one state ref + one such handler →
Edge helpers
rewriteRefLikeInScriptExpr: adds.valuefor state/ref/memo/prop names in computed bodies, excluding inside string literals.quoteVueDirectiveExpr: safe quoting forv-ifconditions with quotes or<.- Initial
ref()values: prop identifiers inuseState(initial)rewritten toprops.xwhere 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.