compilation provides the optimal balance: it preserves Vue's developer intent while outputting production-ready React code that behaves identically to hand-written components.
Core Solution
Translating defineOptions() requires a compiler that understands Vue's SFC structure, parses macro invocations, and generates idiomatic React output. The process relies on Abstract Syntax Tree (AST) transformation, metadata extraction, and deterministic naming resolution.
Step 1: AST Parsing and Macro Detection
The compiler begins by parsing the <script setup> block. It traverses the AST looking for CallExpression nodes matching defineOptions. Once located, the compiler extracts the object literal argument and isolates recognized properties.
// Vue Input
<script setup lang="ts">
defineOptions({
name: 'InventoryTracker',
inheritAttrs: false,
});
</script>
The parser identifies name as a string literal and inheritAttrs as a boolean flag. These values are stored in a metadata registry for the current component scope.
Step 2: Naming Resolution and Function Generation
React components are defined by function declarations. The compiler maps the extracted name value directly to the function identifier. This eliminates the need for runtime displayName assignments or external naming libraries.
// Compiled React Output
const InventoryTracker = () => {
return null;
};
export default InventoryTracker;
The compiler assigns the extracted name to the function declaration. If the name field is absent, the compiler falls back to the file basename or a user-provided comment directive. This ensures consistent component identity across the application.
Step 3: Handling Unsupported Fields
Vue's defineOptions() accepts fields that have no React equivalent, such as inheritAttrs, compilerOptions, or custom plugin hooks. The compiler evaluates each field against a compatibility matrix. Recognized fields are mapped; unrecognized fields trigger a build-time warning and are stripped from the output.
// Vue Input with unsupported field
<script setup lang="ts">
defineOptions({
name: 'ReportGenerator',
inheritAttrs: false,
});
</script>
// Compiled React Output
const ReportGenerator = () => {
return null;
};
export default ReportGenerator;
// ⚠️ Compile-time warning: 'inheritAttrs' has no React equivalent. Attribute spreading must be handled manually.
The compiler does not emit runtime checks for unsupported fields. Instead, it logs warnings during the build phase, allowing developers to address architectural gaps before deployment. This approach prevents silent failures and keeps the production bundle free of dead code.
When defineOptions() is omitted or stripped, the compiler supports explicit naming via special comments. This directive is parsed during the lexical analysis phase and overrides default naming heuristics.
// Vue Input with comment directive
<script setup lang="ts">
// @vr-name: AnalyticsPanel
</script>
// Compiled React Output
const AnalyticsPanel = () => {
return null;
};
export default AnalyticsPanel;
The comment parser extracts the identifier after @vr-name: and assigns it to the generated function. This provides a lightweight override mechanism for legacy files or dynamically generated components where macro syntax is unavailable.
Architecture Rationale
- Static over Runtime: Metadata resolution occurs during compilation. This eliminates proxy wrappers, reduces bundle size, and preserves native React DevTools integration.
- Deterministic Naming: Function identifiers are derived directly from extracted metadata or explicit directives. This ensures consistent stack traces and simplifies debugging.
- Fail-Fast Warnings: Unsupported fields trigger build-time diagnostics rather than runtime fallbacks. This surfaces migration gaps early and prevents production inconsistencies.
- Zero Runtime Dependency: The output contains no compiler-specific imports or adapter layers. The generated React code is functionally identical to manually written components.
Pitfall Guide
1. Assuming inheritAttrs Translates Directly
Explanation: Vue's inheritAttrs: false controls how non-prop attributes are applied to the root element. React has no equivalent mechanism; all attributes are passed explicitly via props.
Fix: Manually spread ...props or destructure known attributes. Use React's composition pattern to forward refs and attributes explicitly.
2. Relying on Runtime Wrappers for Component Identity
Explanation: Wrapping components in HOCs or proxy functions to preserve Vue naming obscures React DevTools and adds execution overhead.
Fix: Use static compilation to map name directly to function identifiers. Reserve runtime wrappers only for cross-cutting concerns like logging or error boundaries.
3. Ignoring TypeScript Type Stripping
Explanation: Vue macros often carry type definitions that leak into the compiled output, causing TypeScript compilation errors in React projects.
Fix: Configure the compiler to strip Vue-specific type annotations during the transformation phase. Use @ts-expect-error or type guards only when absolutely necessary.
Explanation: Relying heavily on // @vr-name: directives creates fragile codebases where naming logic is scattered across comments rather than centralized in component definitions.
Fix: Prefer explicit defineOptions({ name }) for new components. Use comment directives only for legacy files or auto-generated code where macro syntax is unavailable.
5. Neglecting Build-Time Warnings
Explanation: Silent stripping of unsupported fields can mask architectural mismatches, leading to runtime behavior differences between Vue and React.
Fix: Enable strict mode in the compiler configuration. Treat warnings as errors in CI pipelines to ensure all migration gaps are addressed before deployment.
6. Mixing Reactivity Models During Migration
Explanation: Attempting to translate Vue's ref() or reactive() directly into React's useState() within the same compilation pass often results in inconsistent state management patterns.
Fix: Isolate reactivity migration to a separate phase. Compile structural metadata first, then refactor state management using React's official patterns.
7. Assuming All Macros Are Supported
Explanation: defineOptions() is just one of several Vue 3 macros. Others like defineModel, defineSlots, or defineEmits require different transformation strategies.
Fix: Consult the compiler's compatibility matrix before migration. Implement fallback patterns or manual rewrites for unsupported macros rather than forcing static mapping.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Small codebase (<50 components) | Manual rewrite | Full control, zero migration debt | High initial effort, low long-term cost |
| Medium codebase (50-300 components) | Static compilation | Preserves intent, outputs idiomatic React | Moderate setup, fast migration velocity |
| Large codebase (>300 components) | Incremental static compilation + runtime fallbacks | Balances speed with production safety | Higher initial config, scalable over time |
| Legacy Vue 2 codebase | Runtime adapter → Static compilation | Gradual modernization path | Medium effort, requires phased rollout |
| Performance-critical applications | Static compilation only | Eliminates runtime overhead, native DevTools | Low runtime cost, requires strict CI checks |
Configuration Template
// vureact.config.ts
import { defineConfig } from '@vureact/compiler';
export default defineConfig({
input: 'src/**/*.vue',
output: 'dist/react',
strict: true,
metadata: {
extractName: true,
warnOnUnsupported: true,
fallbackNaming: 'filename',
commentDirective: '@vr-name',
},
typescript: {
stripVueTypes: true,
preserveInterfaces: false,
},
warnings: {
inheritAttrs: 'warn',
customOptions: 'ignore',
compilerHints: 'strip',
},
sourcemaps: true,
minify: false,
});
Quick Start Guide
- Install the compiler: Add the cross-framework compilation package to your project dependencies and initialize the configuration file.
- Scan for macros: Run the compiler in dry-run mode to identify all
defineOptions() calls and unsupported fields across your codebase.
- Configure strict mode: Enable warning emission and TypeScript stripping in the config to catch migration gaps early.
- Execute compilation: Run the build command to transform Vue SFCs into React components. Verify output structure and DevTools visibility.
- Validate behavior: Run unit and integration tests to confirm attribute forwarding, state management, and component identity match expectations. Iterate on warnings until the pipeline is clean.