Skip to content

@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.

npm

Install

bash
pnpm add -D @vantris/react
# peer: vantris >= 1.2.0, and react / react-dom in your app
bash
npm install -D @vantris/react
bash
pnpm add -D @vantris/react
bash
yarn add -D @vantris/react
bash
bun add -d @vantris/react

Usage

Vantris references plugins by string and loads each package's default export:

ts
// 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:

ts
// 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: [] },
});
ts
// vantris.config.ts
export default defineConfig({ plugins: ["./plugins/react"] });

This is the standard options wrapper pattern.

Options

OptionTypeDefaultDescription
includeFilterPattern | FilterPattern[].[mj|t]sx?Files to transform.
excludeFilterPattern | FilterPattern[]node_modules, .d.tsFiles to skip (always in addition to the defaults).
jsxRuntime"automatic" | "classic""automatic"automatic needs no import React.
jsxImportSourcestring"react"Module the automatic runtime imports JSX helpers from.
fastRefreshbooleantrueEnable 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.

ts
react({
  include: [/\.[jt]sx$/],           // only JSX/TSX
  exclude: ["legacy/", /\.stories\./],
});

jsxRuntime

  • "automatic" (default) — auto-imports JSX helpers; no import React.
  • "classic" — compiles to React.createElement; requires import React.
ts
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.

ts
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.).

ts
react({
  babel: {
    plugins: ["babel-plugin-styled-components"],
  },
});

How it works

The plugin is a Vantris plugin with enforce: "pre". Its hooks:

  • config — seeds optimizeDeps.include with react, react-dom, react-dom/client, and the JSX dev/prod runtimes, so the dev server is warm before the first request.
  • configResolved — captures base and the project root (used to namespace refresh boundaries).
  • transform (dev only) — Babel strips TypeScript, compiles JSX, and runs react-refresh/babel; the module is then wrapped with the Fast Refresh boundary that self-accepts over import.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:

ts
import type {
  ReactPluginOptions,
  BabelOptions,
  FilterPattern,
} from "@vantris/react";
ts
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).
  • react and react-dom in your app.
  • Node.js ≥ 20.11.

Full example

jsonc
// 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" }
}
ts
// vantris.config.ts
import { defineConfig } from "vantris";
export default defineConfig({ plugins: ["@vantris/react"] });
tsx
// 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

Released under the MIT License.