Getting started
Reze is a compiler-driven UI framework with fine-grained reactivity. Write components in JSX, keep state in signals, and let compiled bindings update the DOM when their dependencies change.
This guide creates a browser application with TypeScript and Vite. Use a Node.js version supported by your installed Vite release and pnpm.
Create the project
mkdir my-reze-app
cd my-reze-app
pnpm init
pnpm add reze-js
pnpm add -D @rezejs/vite-plugin vite typescript
mkdir src
Set "type": "module" in package.json and add these scripts:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "tsc --noEmit"
}
}
Keep the dependencies created by the installation commands.
Configure Vite
Create vite.config.ts:
import reze from "@rezejs/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [reze()],
});
The plugin transforms Reze JSX and compiler syntax such as $signal. A TypeScript JSX setting alone does not perform this transformation.
Create tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "preserve",
"jsxImportSource": "reze-js",
"strict": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["vite/client"]
},
"include": ["src", "vite.config.ts"]
}
Use .tsx for files containing JSX. jsx: "preserve" leaves JSX available for the Reze compiler; jsxImportSource selects Reze's JSX types.
Create the HTML entry
Create index.html in the project root:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Reze app</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Write your first component
Create src/Counter.tsx:
import { $computed, $signal } from "reze-js";
export function Counter(props: { step: number }) {
let count = $signal(0);
const doubled = $computed(count * 2);
return (
<section>
<p>Count: <output>{count}</output></p>
<p>Doubled: {doubled}</p>
<button onClick={() => (count -= props.step)}>Decrease</button>
<button onClick={() => (count = 0)} disabled={count === 0}>
Reset
</button>
<button onClick={() => (count += props.step)}>Increase</button>
</section>
);
}
$signal declares writable reactive state. $computed derives a value from an expression: do not wrap its argument in an arrow function. JSX bindings update when the values they read change.
Create src/main.tsx:
import { render } from "reze-js";
import { Counter } from "./Counter";
render(() => <Counter step={1} />, document.getElementById("app")!);
Run and build
pnpm dev
Open the URL printed by Vite. The initial count and doubled value are 0, and Reset is disabled. Increase changes them to 1 and 2; Reset returns both to 0.
pnpm check
pnpm build
pnpm preview
Type checking and bundling are separate commands. Vite writes the production application to dist; preview serves that output locally for inspection, not as a production hosting service.
Troubleshooting
- JSX parse error: check the
.tsxextension and ensurereze()is installed in the Vite plugin list. - Compiler syntax throws at runtime: the file was not transformed. Check the plugin's file extensions and do not execute
$signalsource directly with Node.js. - Nothing mounts: ensure the HTML contains
id="app"and loads/src/main.tsx. - Reactive state cannot be exported: export a getter function or use the ordinary
signalAPI across module boundaries.
Next: reactivity, components and JSX, and the compiler. For multiple screens, follow routing.