VuReact is a compiler toolchain for migrating from Vue to React — and for writing React with Vue 3 syntax. It can compile Vue 3 code into standard, maintainable React, helping you generate equivalent React components from Vue source.
In this guide, we will walk through a complete Vue 3 project to React project migration with VuReact. By the end, you will understand:
- What rules make an SFC compile reliably
- What the output directory looks like after compilation
- How the generated TSX relates to the original SFC
- How the compiler automatically analyzes React hook dependencies
1. Create a Vite + Vue project
Start with a standard Vue 3 + TypeScript project:
npx create-vite@latest vue-app --template vue-ts
When prompted with Install with npm and start now?, choose No.
You should end up with a project structure similar to this:
vue-app/
public/
src/
assets/
components/
HelloWorld.vue
App.vue
main.ts
style.css
index.html
package.json
tsconfig.json
vite.config.ts
...
2. Install VuReact
Move into the project and install dependencies:
cd vue-app
npm install
Then install the VuReact compiler core:
npm install -D @vureact/compiler-core
3. Configure VuReact
Create vureact.config.ts in the vue-app root:
// vue-app/vureact.config.ts
import { defineConfig } from '@vureact/compiler-core';
export default defineConfig({
// Input path: Vue files to compile. Single files like 'xxx.vue' are allowed.
input: './src',
// Exclude Vue entry files to avoid semantic conflicts.
exclude: ['src/main.ts'],
output: {
// Workspace directory for build artifacts and cache.
workspace: '.vureact',
// Output directory for the generated React project.
outDir: 'react-app',
// Automatically bootstrap a Vite + React environment.
bootstrapVite: true,
},
});
Except for exclude, all options can use their defaults.
4. Write a Vue component
Replace the original HelloWorld.vue with a counter component:
<!-- src/components/HelloWorld.vue -->
<template>
<section class="counter-card">
<h1>{{ props.title }}</h1>
<h2>
<span class="vureact">VuReact</span>
<span class="vue">Vue</span>
<span class="react">React</span>
({{ count }})
</h2>
<p>{{ title }}</p>
<button @click="increment">+1</button>
<button @click="methods.decrease">-1</button>
</section>
</template>
<script setup lang="ts">
// @vr-name: HelloWorld
import { computed, ref, watch } from 'vue';
// You can also use defineOptions({ name: 'HelloWorld' })
const props = defineProps<{ title?: string }>();
const emits = defineEmits<{
(e: 'update', value: number): void;
}>();
const step = ref(1);
const count = ref(0);
const title = computed(() => `Step: ${step.value}`);
const increment = () => {
count.value += step.value;
emits('update', count.value);
};
const methods = {
decrease() {
count.value -= step.value;
emits('update', count.value);
},
};
watch(count, (newVal) => {
step.value = Math.floor(newVal / 10) || 1;
});
</script>
<style scoped>
.counter-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 12px;
.vureact {
color: #9932cc;
}
.vue {
color: #42b883;
}
.react {
color: #61dafb;
}
}
</style>
Then update App.vue:
<!-- src/App.vue -->
<script setup lang="ts">
// @vr-name: App
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>
<HelloWorld
title="Counter component"
@update="(v) => { console.log(v) }"
/>
</template>
5. Compile to React
You can compile in either of these ways:
# Full or incremental compilation
npx vureact build
# Watch mode
npx vureact watch
Or add scripts to package.json:
"scripts": {
"vr:build": "vureact build",
"vr:watch": "vureact watch"
}
Then run:
npm run vr:build
After compilation, the terminal will print relevant build information.
6. Inspect the output
The generated files are placed under .vureact/react-app, for example:
vue-app/
.vureact/
cache/
react-app/
src/
components/
HelloWorld.tsx
HelloWorld-[hash].css
App.tsx
index.css
main.tsx
style.css
package.json
tsconfig.json
vite.config.ts
...
src/
vureact.config.js
7. Run the React app
cd .vureact/react-app
npm run install
npm run dev
If the styling looks slightly different from the Vue version, that is usually because Vite injects a default index.css into main.tsx. Adjusting that file is enough in most cases.
If you run into issues, check the FAQ in the documentation.
8. Compare the generated result
Here is a simplified version of the output. The exact hashes and helper names may differ depending on the generated artifact:
import { useComputed, useVRef, useWatch } from '@vureact/runtime-core';
import { memo, useCallback, useMemo } from 'react';
import './HelloWorld-ebf8d8dc.css';
export type IHelloWorldProps = {
title?: string;
} & {
onUpdate?: (value: number) => void;
};
const HelloWorld = memo((props: IHelloWorldProps) => {
const step = useVRef(1);
const count = useVRef(0);
const title = useComputed(() => `Step: ${step.value}`);
const increment = useCallback(() => {
count.value += step.value;
props.onUpdate?.(count.value);
}, [count.value, step.value, props.onUpdate]);
const methods = useMemo(
() => ({
decrease() {
count.value -= step.value;
props.onUpdate?.(count.value);
},
}),
[count.value, step.value, props.onUpdate],
);
useWatch(count, (newVal) => {
step.value = Math.floor(newVal / 10) || 1;
});
return (
<section className="counter-card" data-css-ebf8d8dc>
<h1 data-css-ebf8d8dc>{props.title}</h1>
<h2 data-css-ebf8d8dc>
<span className="vureact" data-css-ebf8d8dc>VuReact</span>
<span className="vue" data-css-ebf8d8dc>Vue</span>
<span className="react" data-css-ebf8d8dc>React</span>
({count.value})
</h2>
<p data-css-ebf8d8dc>{title.value}</p>
<button onClick={increment} data-css-ebf8d8dc>
+1
</button>
<button onClick={methods.decrease} data-css-ebf8d8dc>
-1
</button>
</section>
);
});
export default HelloWorld;
.counter-card[data-css-ebf8d8dc] {
border: 1px solid #ddd;
border-radius: 8px;
padding: 12px;
background: #fafafa;
}
.counter-card[data-css-ebf8d8dc] .vureact[data-css-ebf8d8dc] {
color: #9932cc;
}
.counter-card[data-css-ebf8d8dc] .vue[data-css-ebf8d8dc] {
color: #42b883;
}
.counter-card[data-css-ebf8d8dc] .react[data-css-ebf8d8dc] {
color: #61dafb;
}
Key things to notice
-
@vr-name: Counterdefines the component name. -
definePropsanddefineEmitsare converted into TypeScript props types. - Non-UI components are wrapped in
memoby default. -
ref,computed, andwatchbecome runtime adapter APIs such asuseVRef,useComputed, anduseWatch. - Template event handlers become React-style
onClickhandlers. - Top-level arrow functions are analyzed and may become
useCallback. - Top-level variable declarations are analyzed and may become
useMemo. - Old
reforcomputedvalues in JSX are rewritten with.value. - Scoped styles generate hashed CSS files and matching scope attributes on DOM elements.
FAQ
For common issues during usage, see:
Summary
By following the steps above, you have completed a full migration flow from a Vue SFC project to a React project.
At a high level:
- Create a standard Vue + TypeScript project
- Install
@vureact/compiler-core - Configure
vureact.config.ts - Write SFCs using the expected conventions
- Run the compiler from the CLI
- Start the generated React project and verify the output
VuReact can handle the following core transformations:
- Vue template syntax to React JSX, including
v-if,v-slot,v-model, and<slot> - Composition API to React Hooks, such as
reftouseVRefandcomputedtouseComputed - Reactive dependency analysis to automatic
useCallbackanduseMemodependency arrays - Component communication to props typing and event callback mapping
- Style handling to scoped CSS, CSS Modules, and preprocessor compilation
If you want to test the real demos:
- Customer support collaboration backend: https://codesandbox.io/p/github/vureact-js/example-customer-support-hub/master?import=true
- CRM admin backend: https://codesandbox.io/p/github/vureact-js/example-crm-admin-backend/master
Top comments (0)