TSRX
TSRX is a TypeScript language extension for declarative
UIs. @vincle/core's jsx-runtime
exports the jsx, jsxs, and Fragment that TSRX's compiler emits, so .tsrx
components render through vincle: no dedicated target package, no React. Set
jsxImportSource: "@vincle/core" in the bundler plugin and in your
tsconfig.json.
Setup on Bun#
{ "dependencies": { "@tsrx/bun-plugin-react": "^0.1.99", "@tsrx/react": "^0.3.4", "@vincle/core": "^0.9.0" }, "devDependencies": { "@tsrx/typescript-plugin": "^0.4.7", "typescript": "^5.9.3" }}{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@vincle/core", "plugins": [{ "name": "@tsrx/typescript-plugin" }] }}import tsrxReact from "@tsrx/bun-plugin-react";
await Bun.build({ entrypoints: ["./src/index.tsx"], outdir: "./dist", plugins: [tsrxReact({ jsxImportSource: "@vincle/core" })],});export function Hello({ names }: { names: string[] }) @{ const hour = new Date().getHours(); const greeting = hour < 18 ? "Bonjour" : "Bonsoir";
<> <div class="card"> <h1>{greeting}, TSRX + vincle !</h1>
@for (const name of names) { <div class="row"> <span class="badge">{name[0].toUpperCase()}</span> <strong>{name}</strong> </div> }
@if (names.length > 3) { <p class="more">…and {names.length - 3} more.</p> } </div>
<style> .card { padding: 1rem; border: 1px solid #ccc; border-radius: 8px; } .row { display: flex; gap: 0.5rem; align-items: center; } .badge { font-weight: 600; } .more { color: #666; } </style> </>}import { renderToString } from "@vincle/core";import { Hello } from "./App.tsrx";
const html = await renderToString(<Hello names={["vincle", "TSRX"]} />);console.log(html);bun build.ts && bun dist/index.jsSetup on Vite#
Same option on the Vite plugin:
import { defineConfig } from "vite";import tsrxReact from "@tsrx/vite-plugin-react";
export default defineConfig({ plugins: [tsrxReact({ jsxImportSource: "@vincle/core" })],});For the rest of your .tsx modules, set the import source as in
TypeScript setup → Vite / esbuild.
Notes#
- Use the build, not a preload. The plugin's extracted styles only work
inside
Bun.build; thecssloader it relies on is not available at runtime. The build is also what emits the scoped styles asdist/<entry>.css: inline it into your document's head when serving. - Keep
@tsrx/reacta direct dependency when imports are left external (SSR, library builds): generated modules import@tsrx/react/runtime/iterable, which ships inside it.