@vantris/react
The official React plugin for Vantris. It adds React Fast Refresh (stateful hot reload) in dev, compiles JSX through Babel with the automatic runtime, and pre-bundles the React packages so the dev server is warm on first load.
Vantris already understands JSX out of the box — this plugin is what turns a save into an in-place component update that keeps your state.
Install
pnpm add -D @vantris/react
# peer: vantris >= 1.2.0, and react / react-dom in your appnpm install -D @vantris/reactpnpm add -D @vantris/reactyarn add -D @vantris/reactbun add -d @vantris/reactUsage
Vantris references plugins by string and loads each package's default export:
// vantris.config.ts
import { defineConfig } from "vantris";
export default defineConfig({
plugins: ["@vantris/react"],
});That's it. vantris dev now has Fast Refresh; vantris build compiles JSX as usual.
With options
Because Vantris loads plugins by string, pass options through a tiny local wrapper module and reference that file:
// plugins/react.ts
import { react } from "@vantris/react";
export default react({
jsxRuntime: "automatic", // or "classic"
jsxImportSource: "react", // e.g. "@emotion/react"
fastRefresh: true,
// Extra Babel presets/plugins merged into the transform:
babel: { plugins: [] },
});// vantris.config.ts
export default defineConfig({ plugins: ["./plugins/react"] });This is the standard options wrapper pattern.
Options
| Option | Type | Default | Description |
|---|---|---|---|
include | FilterPattern | FilterPattern[] | .[mj|t]sx? | Files to transform. |
exclude | FilterPattern | FilterPattern[] | node_modules, .d.ts | Files to skip (always in addition to the defaults). |
jsxRuntime | "automatic" | "classic" | "automatic" | automatic needs no import React. |
jsxImportSource | string | "react" | Module the automatic runtime imports JSX helpers from. |
fastRefresh | boolean | true | Enable Fast Refresh in dev (always off in a build). |
babel | { presets, plugins, parserOpts } | {} | Merged into the Babel transform. |
A FilterPattern is a substring, a RegExp, or an (id: string) => boolean predicate.
include / exclude
Which files the plugin transforms. include defaults to every .js/.jsx/.ts/.tsx/.mjs module; exclude always skips node_modules and .d.ts files, plus anything you add.
react({
include: [/\.[jt]sx$/], // only JSX/TSX
exclude: ["legacy/", /\.stories\./],
});jsxRuntime
"automatic"(default) — auto-imports JSX helpers; noimport React."classic"— compiles toReact.createElement; requiresimport React.
react({ jsxRuntime: "classic" });jsxImportSource
The module the automatic runtime imports JSX helpers from. Change it for JSX libraries like Emotion or a Preact-compat setup.
react({ jsxImportSource: "@emotion/react" });fastRefresh
Enable React Fast Refresh in dev — component state survives edits. Automatically off for production builds. Set false to disable stateful refresh (edits then fall back to Vantris's default HMR).
babel
An escape hatch, à la @vitejs/plugin-react: extra Babel presets, plugins, and parserOpts merged into the transform. Use it for Babel-based tools (styled-components' babel plugin, macros, etc.).
react({
babel: {
plugins: ["babel-plugin-styled-components"],
},
});How it works
The plugin is a Vantris plugin with enforce: "pre". Its hooks:
config— seedsoptimizeDeps.includewithreact,react-dom,react-dom/client, and the JSX dev/prod runtimes, so the dev server is warm before the first request.configResolved— capturesbaseand the project root (used to namespace refresh boundaries).transform(dev only) — Babel strips TypeScript, compiles JSX, and runsreact-refresh/babel; the module is then wrapped with the Fast Refresh boundary that self-accepts overimport.meta.hot.transformIndexHtml(dev only) — injects the Fast Refresh preamble before any application module runs.resolveId/load— serve the shared refresh runtime as a virtual ES module at/@vantris-react/runtime.js.
In a production build the plugin steps aside — Vantris/Rolldown compiles JSX — and it does nothing else. Fast Refresh is dev-only by construction.
Types
The package exports its option types for the wrapper module:
import type {
ReactPluginOptions,
BabelOptions,
FilterPattern,
} from "@vantris/react";interface ReactPluginOptions {
include?: FilterPattern | FilterPattern[];
exclude?: FilterPattern | FilterPattern[];
jsxRuntime?: "automatic" | "classic";
jsxImportSource?: string;
fastRefresh?: boolean;
babel?: BabelOptions;
}
interface BabelOptions {
presets?: PluginItem[];
plugins?: PluginItem[];
parserOpts?: Partial<ParserOptions>;
}
type FilterPattern = string | RegExp | ((id: string) => boolean);Requirements
vantris≥ 1.2.0 (peer dependency — the plugin API landed in 1.2.0).reactandreact-domin your app.- Node.js ≥ 20.11.
Full example
// package.json
{
"type": "module",
"scripts": { "dev": "vantris dev", "build": "vantris build" },
"dependencies": { "react": "^19", "react-dom": "^19" },
"devDependencies": { "vantris": "^1.2.0", "@vantris/react": "^0.1.0" }
}// vantris.config.ts
import { defineConfig } from "vantris";
export default defineConfig({ plugins: ["@vantris/react"] });// src/App.tsx — edit while the count is non-zero; it survives the save
import { useState } from "react";
export function App() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>Count: {n}</button>;
}See React & JSX for the broader React story, including zero-plugin JSX and choosing a runtime.
License
MIT