一个编译器,它将 React 兼容的代码转换为不使用虚拟 DOM 的 VanillaJS
Vidact is a compiler for React-shaped components. You write function components, JSX, and hooks exactly the way you would in a React application. Instead of shipping React, Vidact compiles each component into plain DOM operations: create these elements once, and when this piece of state changes, update this text node.
There is no Virtual DOM, no reconciler, and no React runtime in your bundle.
[!WARNING] Vidact is in beta. APIs are stabilizing and the supported subset of React is growing. Pin
@vidact/runtime,@vidact/vite, and@vidact/startto matching versions and run your test suite against the versions you ship.
Components run once, when they mount. The compiler reads each function ahead of time, works out which parts of the output depend on which values, and emits a fixed list of small updaters. A state write runs only the updaters that read that state. The component function is never called again.
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
)
}
Compiled by Vidact, this builds a , a , and an `` once, then keeps one updater that rewrites the text node whenever count changes.
Why Vidact?
memo to keep things fast.What Vidact is not. Vidact is not a React renderer and does not run React. There is no element tree, no Fiber, and no React DevTools. Class components, React.Children, and libraries that reach into React internals are not supported. See the React compatibility matrix for the full contract.
The fastest way to start is the project generator:
npx vidact my-app
It asks for a template and leaves you with a project you can run:
| Template | What you get |
|---|---|
spa |
Vite, the Vidact compiler plugin, and a client-rendered entry point |
start |
Vidact Start with file routes, loaders, server rendering, and hydration |
nitro |
The same full-stack app served by Nitro, with a preset for every major host |
Install the runtime, the Vite plugin, and the React-shaped types. react and react-dom are missing from this list on purpose: your source imports from react, and the Vite plugin resolves those imports to the compiled runtime.
pnpm add @vidact/runtime
pnpm add -D @vidact/vite @vidact/react-types @types/react typescript vite
Add the plugin to Vite. It compiles every .tsx file in the project.
// vite.config.ts
import { vidact } from '@vidact/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vidact()],
})
Tell TypeScript to leave JSX alone and to type it with Vidact's React-shaped declarations:
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "@vidact/react-types",
"types": ["@vidact/react-types", "vite/client"]
}
}
Mount a component. mountCompiled takes the component itself rather than a JSX element, because a compiled component is a factory that builds its own DOM.
// src/main.ts
import { mountCompiled } from '@vidact/runtime'
import { Counter } from './Counter.tsx'
mountCompiled(Counter, document.querySelector('#app')!)
Then run pnpm vite for development or pnpm vite build for a static production build.
[!NOTE] The compiler and runtime share a protocol version. Always upgrade
@vidact/runtime,@vidact/vite, and@vidact/starttogether. Mixed versions fail loudly at startup rather than silently misbehaving.
Suspense, transitions, form actions, and retained UI are part of React's API and all work as expected, but they stay out of your bundle until you enable them:
vidact({ features: ['async', 'concurrent'] })
| Feature | What it enables |
|---|---|
async |
Suspense, lazy, and use(promise) |
concurrent |
useTransition, startTransition, useDeferredValue, flushSync |
actions |
Function-valued form action, useActionState, useOptimistic, useFormStatus |
retained-ui |
Activity for hiding UI while keeping its state |
Using a feature that is not enabled produces a compile error naming the flag.
Swap the plugin for vidactStart() and add a src/routes directory. Each route module exports a Route with an optional server loader; its result reaches the component as typed loaderData.
// src/routes/index.tsx
import { defineFileRoute, type RouteComponentProps } from '@vidact/start'
const loader = () => ({ greeting: 'Hello from the server' })
export function HomeRoute({ loaderData }: RouteComponentProps>) {
return
{loaderData.greeting}
}
export const Route = defineFileRoute({ loader, component: HomeRoute })
The server renders HTML with the loader's data, embeds a snapshot, and the client hydrates the existing DOM without rebuilding it. See the @vidact/start README and the Start guides for entries, navigation, data loading, and deployment.
React source
-> React Compiler analysis in Rust (AST, scope, HIR/CFG/SSA, dependencies)
-> Vidact analysis adapter
-> Vidact static updater IR
-> Vanilla DOM codegen
-> @vidact/runtime
@vidact/vite sends untouched TSX to @vidact/compiler, a prebuilt native Node-API addon. Consumers never need Rust or Cargo.@vidact/runtime/jsx-runtime.React Compiler is an analysis dependency, not Vidact's renderer or code generator. Its internal types terminate at a narrow adapter, and the rest of Vidact uses its own stable facts and IR. The architecture notes record these decisions and the analysis boundary explains the integration constraints.
Reachable source-published packages that declare React in their metadata are qualified for compilation automatically. The current evidence certifies named Base UI paths at compile and SSR time, published Button behavior and DOM ownership in three browsers, and the Base UI-backed components used by the Shop example. It is not a package-wide or shadcn-registry guarantee; the compatibility evidence lists each tested surface.
| Package | Role |
|---|---|
vidact |
Project generator with spa, start, and nitro templates |
@vidact/runtime |
Fine-grained direct-DOM runtime: scheduler, state slots, roots, keyed ranges |
@vidact/vite |
Vite plugin that compiles .tsx files and resolves react imports |
@vidact/react-types |
JSX and hook types describing what Vidact actually does, built on @types/react |
@vidact/start |
File routes, loaders, SSR, hydration, and client navigation |
@vidact/compiler |
Native compiler bindings and the vidactc CLI, for tooling authors |
@vidact/test-support |
act and DOM mutation assertions for Vitest browser tests |
The Rust side lives in crates/vidact-compiler (analysis facts and updater IR) and crates/vidact-node (the Node-API adapter).
Every example is ordinary React-shaped TSX. Run them from the repository root after the development setup.
| Example | Command | Highlights |
|---|---|---|
| TodoMVC | pnpm dev:todomvc |
Array state, keyed lists, and events with no Virtual DOM |
| Shop | pnpm dev:shop |
Streaming SSR, "use client" boundaries, Suspense, Tailwind, and local shadcn wrappers over tested Base UI paths |
| Start | pnpm dev:start |
Nested layouts, typed loaders, dynamic params, route endpoints, hydration |
| Docs | pnpm dev:docs |
The documentation site itself, built with Vidact Start and headless Fumadocs, deployed on Nitro |
The user documentation is written in examples/docs/content/docs: a quick start, a Learn section covering one concept per page, Vidact Start guides, a migration guide from React, a testing guide, and per-package references.
Requirements: Rust 1.96, Node.js 24+, pnpm 10, and Playwright's Chromium, Firefox, and WebKit installs.
scripts/prepare-oxc.sh # initialize the pinned Oxc submodule and apply the React Compiler patch
pnpm install
pnpm build:packages
pnpm typecheck
cargo test --workspace
pnpm test:browser
pnpm check runs everything CI runs: lint and format gates, type checks, the Rust suite, the cross-browser corpus, package and example verification, production size budgets, and compiler and runtime benchmarks. The benchmark methodology records workloads, sampling, environments, and regression thresholds.
[!TIP] Ordinary builds do not need Go. Only maintainers editing the checked-in Oxc patch install
git-go-patchwithgo install github.com/microsoft/go-infra/cmd/[email protected]. See patched Oxc submodule.
Every pull request needs a changeset. Run pnpm changeset for a published package change,
暂无开放 Issues,或尚未同步最近议题。