Installation
Install the core packages:
npm install @pyreon/core @pyreon/reactivity @pyreon/runtime-dom @pyreon/vite-pluginbun add @pyreon/core @pyreon/reactivity @pyreon/runtime-dom @pyreon/vite-pluginpnpm add @pyreon/core @pyreon/reactivity @pyreon/runtime-dom @pyreon/vite-pluginyarn add @pyreon/core @pyreon/reactivity @pyreon/runtime-dom @pyreon/vite-pluginVite Setup
Add the Pyreon plugin to your Vite config. The plugin is a default export — convention is to name the import pyreon:
import { defineConfig } from 'vite'
import pyreon from '@pyreon/vite-plugin'
export default defineConfig({
plugins: [pyreon()],
})TypeScript Setup
Point TypeScript's JSX at Pyreon. The @pyreon/typescript preset sets this for you (npm install -D @pyreon/typescript):
{
"extends": "@pyreon/typescript/app"
}Or set the two options by hand:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@pyreon/core"
}
}Your First Component
import { signal, computed } from '@pyreon/reactivity'
import { Show } from '@pyreon/core'
import { mount } from '@pyreon/runtime-dom'
const count = signal(0)
function App() {
const doubled = computed(() => count() * 2)
return (
<div>
<h1>Hello Pyreon!</h1>
<button onClick={() => count.update((n) => n + 1)}>
Clicks: {count()}
</button>
<p>Doubled: {doubled()}</p>
<Show when={() => count() > 0}>
<p>You've started clicking!</p>
</Show>
</div>
)
}
mount(<App />, document.getElementById('app')!)HTML Entry Point
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pyreon App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>Adding SSR
For server-side rendering, install the server runtime:
npm install @pyreon/runtime-serverbun add @pyreon/runtime-serverpnpm add @pyreon/runtime-serveryarn add @pyreon/runtime-serverimport { renderToString } from '@pyreon/runtime-server'
import App from './App'
export async function render() {
return await renderToString(<App />)
}For streaming SSR with Suspense support — renderToStream returns a ReadableStream<string> you hand to a Response (or pipe to your platform's writable):
import { renderToStream } from '@pyreon/runtime-server'
import App from './App'
export function render(): ReadableStream<string> {
return renderToStream(<App />)
}
// e.g. in a Request-handling server entry:
export function handleRequest() {
return new Response(render(), { headers: { 'Content-Type': 'text/html' } })
}Adding Routing
npm install @pyreon/routerbun add @pyreon/routerpnpm add @pyreon/routeryarn add @pyreon/routerimport { createRouter, lazy } from '@pyreon/router'
export const router = createRouter({
routes: [
{ path: '/', component: lazy(() => import('./pages/Home')) },
{ path: '/about', component: lazy(() => import('./pages/About')) },
{ path: '/users/:id', component: lazy(() => import('./pages/User')) },
],
})import { RouterProvider, RouterView, RouterLink } from '@pyreon/router'
import { router } from './router'
export default function App() {
return (
<RouterProvider router={router}>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<RouterView />
</RouterProvider>
)
}Using a Compatibility Layer
If you're coming from React, you can use familiar hooks:
npm install @pyreon/react-compatbun add @pyreon/react-compatpnpm add @pyreon/react-compatyarn add @pyreon/react-compatTell the Vite plugin to compile with React semantics, so components re-render the way React code expects:
export default defineConfig({
plugins: [pyreon({ compat: 'react' })],
})import { useState, useEffect, memo } from '@pyreon/react-compat'
const Counter = memo(() => {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `Count: ${count}`
}, [count])
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>
})What's Next?
Learn about Reactivity — the signal engine at the core
Explore the Component Model — component functions, lifecycle, control flow
Set up Routing — type-safe nested routes
Add State Management — Pinia-inspired stores
Style with Styler — CSS-in-JS for signals