Setup
Phaze uses the JSX automatic transform — same configuration shape as Preact and React, just a different jsxImportSource. The tabs below show the equivalent setup for each framework. The phaze (God Mode) tab adds the compile-time tooling that unlocks the namespace DSL (on:click, class:hidden, bind:value, use:NAME, <For for:item>) and the auto-thunked macros (c(expr), watch(expr), s.async(expr), inc(count), 3-arg timeout/interval).
Install
Section titled “Install”pnpm add @madenowhere/phaze# runtime + compile + HMR + (optional) shipped use:-directivespnpm add @madenowhere/phaze \ @madenowhere/phaze-vite \ @madenowhere/phaze-directives
# Astro app — adds the integration (pulls in phaze-compile + phaze-render-to-string)pnpm add @madenowhere/phaze-astro# Plain Vite app — install phaze-compile directly instead of phaze-astropnpm add @madenowhere/phaze-compile
# editor UX (silences TS6133 false positives on use:NAME imports)pnpm add -D @madenowhere/phaze-tspluginpnpm add preactpnpm add react react-domtsconfig.json
Section titled “tsconfig.json”{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@madenowhere/phaze" }}{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@madenowhere/phaze", "plugins": [{ "name": "@madenowhere/phaze-tsplugin" }] }}{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" }}{ "compilerOptions": { "jsx": "react-jsx" }}import { render, signal } from '@madenowhere/phaze'
function App() { const count = signal(0) return ( <button onClick={() => count.set(count.current() + 1)}> {count} </button> )}
render(App, document.getElementById('app')!)import { render, signal } from '@madenowhere/phaze'import { inc } from '@madenowhere/phaze/numeric'
function App() { const count = signal(0) return ( <button on:click={inc(count)}> {count} </button> )}
render(App, document.getElementById('app')!)Same runtime — on:click lowers to onClick, inc(count) inlines to count.set(count() + 1), both at compile time (zero runtime cost on top of the plain phaze tab).
import { render } from 'preact'import { useState } from 'preact/hooks'
function App() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount(count + 1)}> {count} </button> )}
render(<App />, document.getElementById('app')!)import { createRoot } from 'react-dom/client'import { useState } from 'react'
function App() { const [count, setCount] = useState(0) return ( <button onClick={() => setCount(count + 1)}> {count} </button> )}
createRoot(document.getElementById('app')!).render(<App />)Notable differences
Section titled “Notable differences”- No hooks. Phaze uses
signal()at module or component scope. There is no rules-of-hooks constraint and nouseEffect; reactive computations areeffect()and run when their dependencies change. render(component, container)takes the component function directly, not a JSX element. Returns a dispose function.{count}in JSX binds the signal directly to a text node. Phaze’s JSX runtime detects function children and wires reactive bindings.- No
className. Useclass— same as Preact.
If you use Vite directly (without an integration), set the JSX import source there too:
import { defineConfig } from 'vite'
export default defineConfig({ esbuild: { jsx: 'automatic', jsxImportSource: '@madenowhere/phaze', },})import { defineConfig } from 'vite'import phazeCompile from '@madenowhere/phaze-compile/vite'import phazeVite from '@madenowhere/phaze-vite'import { phazeChunks } from '@madenowhere/phaze-vite/chunks'
export default defineConfig({ esbuild: { jsx: 'automatic', jsxImportSource: '@madenowhere/phaze', }, plugins: [ phazeCompile(), // Babel pass — DSL macros, namespace lowering, /numeric inline, … phazeVite(), // per-component HMR ], build: { rollupOptions: { output: { manualChunks: phazeChunks() }, // keeps phaze its own ~3 KB-brotli chunk }, },})See phaze-compile, phaze-vite, and phaze-tsplugin for what each tool does and where it sits in the build pipeline.
If you use Astro with the @madenowhere/phaze-astro integration, both the plain and God Mode setups are configured for you — the integration adds phaze-compile to the Vite plugin chain automatically, so you only wire phaze-vite + phazeChunks() yourself.