DEV Community

Cover image for Vue to React with VuReact: A Fast Start Guide
Ryan John
Ryan John

Posted on • Originally published at vureact.top

Vue to React with VuReact: A Fast Start Guide

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:

  1. What rules make an SFC compile reliably
  2. What the output directory looks like after compilation
  3. How the generated TSX relates to the original SFC
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
  ...
Enter fullscreen mode Exit fullscreen mode

2. Install VuReact

Move into the project and install dependencies:

cd vue-app
npm install
Enter fullscreen mode Exit fullscreen mode

Then install the VuReact compiler core:

npm install -D @vureact/compiler-core
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

5. Compile to React

You can compile in either of these ways:

# Full or incremental compilation
npx vureact build

# Watch mode
npx vureact watch
Enter fullscreen mode Exit fullscreen mode

Or add scripts to package.json:

"scripts": {
  "vr:build": "vureact build",
  "vr:watch": "vureact watch"
}
Enter fullscreen mode Exit fullscreen mode

Then run:

npm run vr:build
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

7. Run the React app

cd .vureact/react-app
npm run install
npm run dev
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
.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;
}
Enter fullscreen mode Exit fullscreen mode

Key things to notice

  1. @vr-name: Counter defines the component name.
  2. defineProps and defineEmits are converted into TypeScript props types.
  3. Non-UI components are wrapped in memo by default.
  4. ref, computed, and watch become runtime adapter APIs such as useVRef, useComputed, and useWatch.
  5. Template event handlers become React-style onClick handlers.
  6. Top-level arrow functions are analyzed and may become useCallback.
  7. Top-level variable declarations are analyzed and may become useMemo.
  8. Old ref or computed values in JSX are rewritten with .value.
  9. 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:

  1. Create a standard Vue + TypeScript project
  2. Install @vureact/compiler-core
  3. Configure vureact.config.ts
  4. Write SFCs using the expected conventions
  5. Run the compiler from the CLI
  6. 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 ref to useVRef and computed to useComputed
  • Reactive dependency analysis to automatic useCallback and useMemo dependency 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:

Top comments (0)