Tooling
Phaze tooling spans two toolchains — separate ecosystems that meet only at codegen:
- The TypeScript toolchain (1–9) — Node/TS npm packages that turn
.tsx/.phazesource into the browser bundle, in three layers:- Build pipeline (1–5) — between source and the JS that runs in the browser: three core (editor TS-server, build, dev-server) + two host adapters (Astro / Cloudflare), one per deployment target.
- Editor stack (6–8) — IntelliSense, syntax highlighting, theming for
.phazefiles. Independent of the build pipeline; install whichever you want, in any combination. - Check (9) — headless
tscfor.phazein CI.
- The Rust toolchain (10) — a separate crates.io ecosystem: the
phazeRust HTTP runtime (compiles Rust → Wasm on Workers) plus its one build tool,phaze-cli(codegen). It does not plug into the Babel → Oxc → Rolldown pipeline; it meets the TS side only at the codegen seam (phaze-cliemits a typed TS client from Rust handlers). The runtime crates are documented in Integrations › Rust.
They’re separate packages because they target separate stages and languages of the developer-to-runtime pipeline — editor TS plugin, build-time AST, dev-server HMR, host integration, in-editor language service, IDE extensions, CI checker, and the Rust codegen binary — and a clean separation makes it possible to use or replace any piece individually.
Separately, phaze rust (#10) is a parallel toolchain — the phaze Rust HTTP runtime on crates.io plus the phaze-cli codegen binary. It does not plug into the Babel → Oxc → Rolldown pipeline above; it’s a different language ecosystem with its own build tool, where only phaze-cli is tooling (the rest are runtime). See § phaze rust below for the summary and Integrations › Rust for the full reference.
─── Build pipeline ──────────────────────────────────────────────────1. phaze-tsplugin ← editor (TS Language Service)2. phaze-compile ← build-time AST rewriting ├── babel-plugin.ts ← the actual Babel plugin (all the visitors live here) ├── vite-plugin.ts ← thin wrapper that adapts it for Vite └── phaze-format/ ← `.phaze` parser + emit + v3 sourcemap (subpath)3. phaze-vite ← island HMR + chunking helpers4. phaze-astro ← Astro integration (island model)5. phaze-cloudflare ← native Cloudflare Workers adapter (whole-page model)
─── Editor stack ────────────────────────────────────────────────────6. phaze-language-tools ← Volar LSP backend (.phaze → virtual .tsx)7. phaze-vscode ← VSCode extension (grammar + LSP client)8. phaze-glow ← VSCode theme + halo runtime
─── Check ───────────────────────────────────────────────────────────9. phaze-check ← headless `tsc` for `.phaze` in CI
─── Rust toolchain (separate ecosystem — crates.io, NOT the Babel/Vite pipeline) ──10. phaze-cli ← Rust codegen binary (gen client / gen openapi / .fbs) └ runtime crates: phaze · phaze-core · phaze-macros · phaze-fb · phaze-native#4 and #5 are alternatives — you pick one per app depending on the deployment target. Both are build-time packages: neither ships into the phaze runtime byte budget (the sub-3 KB phaze chunk is unaffected by which adapter you use). #6/#7/#8 are editor-only, distributed via the VSCode marketplace; they never reach your build or runtime.
Each tool is documented in its own section under this Tooling page:
-
1. phaze-tsplugin — editor-only TypeScript language-service plugin. Silences
TS6133(“unused import”) false positives onuse:NAMEdirective consumers. Runs in your IDE’s TS server, not your build. -
2. phaze-compile — the heavyweight. Babel plugin (
babel-plugin.ts) that performs every compile-time phaze transform: DSL macro auto-thunking, JSX namespace lowering,/numericinline,/matchpredicate inline + import swap,/listarray-mutation inline,/timesecond-arg auto-thunk,<For>key-lift, component-children wrapping. Plus a Vite adapter (vite-plugin.ts) that exposes it to the Vite/Astro pipeline. -
3. phaze-vite — Vite-specific helpers that are NOT AST rewriting: per-component island HMR (the
replace()mechanism — one mount boundary perclient:*island) and thephazeChunks()chunk-grouping builder. The island HMR is the Astro model; phaze-cloudflare’s whole-page hydration uses a different HMR boundary (see below). Two unrelated concerns in one Vite-only package. -
4. phaze-astro — Astro integration. Registers Phaze as a JSX renderer with Astro, wires
phaze-compile/viteinto the Vite plugin chain automatically, exposes the typed-actions bridge (useAction). The component model is islands —<Component client:load/>etc. -
5. phaze-cloudflare — native Cloudflare Workers adapter, no Astro layer. A single Vite plugin owning file-system routing, virtual server/client entries, env type-gen, the dual-environment build (
client+ssr), and two single-process dev modes (watch-build + in-process Miniflare, or a Vite dev server + cf-plugin module runner). Depends on@cloudflare/vite-plugin/miniflare/wrangler; build-time only. The component model is whole-page — the entire page hydrates as one reactive tree. -
6. phaze-language-tools — Volar-based language services for
.phazefiles. ALanguagePluginthat runs phaze-compile’s format transform on each.phazefile to produce a virtual.tsxplus VolarCodeMapping[](converted from phaze-compile’s v3 sourcemap via@jridgewell/sourcemap-codec), plus a stdio language server entry that combines the plugin with the standard TypeScript + TS-twoslash services. Drives the TypeScript language service against the virtual.tsx, then translates responses back through the mappings to.phazepositions. The engine behind every downstream tool that wants type-aware.phazeediting —#7consumes it as an LSP backend, and#9(phaze check) consumes it as a CLI backend. -
7. phaze-vscode — VSCode language extension. Contributes the
.phazelanguage ID, a TextMate grammar (named-fence highlighting for---page/---data/---state+ embeddedsource.tsxbetween fences so JSX/TS colorize natively), and a language configuration (brackets, comments, auto-close — mirrors.tsx). On.phazeopen, launchesphaze-language-toolsas an LSP server viavscode-languageclient(stdio, child process) — hover types, completion, go-to-definition, find-references, diagnostics in.phazefiles. Resolves the workspace’s TypeScript SDK so the LSP matches whatever tsc version your project depends on. Marketplace package; pairs with#8but works standalone. -
8. phaze-glow — VSCode color theme + halo runtime.
Phaze Glowcolor theme is a neon dark theme tuned for.phaze— hot pink (#ff00fb) on fence markers, bright cyan on git-modified, mint on class names, yellow on keywords, neon green on git-added, on the#1a1a1abackground phaze-compile and friends use across the docs. Optional workbench patcher (Phaze: Enable Glowcommand) injectstext-shadowhalo rules around each chromatic color slot, driven by thephaze.brightnesssetting (0–1). Same pattern as Synthwave ‘84 / code-glow — VSCode will display a “your installation is corrupted” banner once after first enable (expected; dismissable). Marketplace package; pairs with#7for the full Phaze visual identity but works standalone with any source language.
The full build pipeline
Section titled “The full build pipeline”The five phaze tooling packages plug into a three-stage build pipeline that runs over every .tsx file on its way from source to bundle. Babel (phaze-compile), then Vite’s transformer, then Vite’s bundler each have a role — they’re not alternatives but a stack. On Vite 8 — the canonical phaze-cloudflare path — the transformer is Oxc and the bundler is Rolldown (they replaced esbuild and Rollup in Vite 8). phaze-astro rides whatever Vite Astro bundles, so on current Astro the last two stages are still esbuild + Rollup until Astro ships Rolldown:
Your .tsx file │ ▼┌───────────────────────────────────────────────────────────────┐│ STAGE 1 — Babel (phaze-compile's babel-plugin.ts) ││ ──────────────────────────────────────────────────── ││ AST rewriting via the visitor API. Implements every ││ phaze-specific transform: ││ • c(expr) → c(() => expr) (DSL auto-thunks) ││ • watch(expr) → effect(() => expr) ││ • s.async(expr) → s.async(() => expr) ││ • inc(count) → count.set(count() + 1) (/numeric inline) ││ • on:event={…} → onEvent={…} (JSX namespaces) ││ • use:NAME={v} → IIFE post-creation call ││ • <For for:t> → {() => t().map(...)} (inversion, 0 B) ││ • <For for:t phaze> → <For each={…} getKey={…}> ││ • interval(s,n,fn) → interval(() => {s();return n}, fn) ││ ││ OUTPUT: JSX still intact, plus the phaze-specific rewrites. │└───────────────────────────────────────────────────────────────┘ │ ▼┌───────────────────────────────────────────────────────────────┐│ STAGE 2 — Oxc (Vite 8's transformer; esbuild on ≤7) ││ ──────────────────────────────────────────────────── ││ JSX-to-jsx() lowering. Converts every `<Foo bar={1}>` to ││ `jsx(Foo, { bar: 1 })`. Also does TS-strip, minify (in ││ prod), and constant-folds `import.meta.env.DEV`. ││ ││ OUTPUT: plain ES2022 JS, no JSX left. │└───────────────────────────────────────────────────────────────┘ │ ▼┌───────────────────────────────────────────────────────────────┐│ STAGE 3 — Rolldown (Vite 8's bundler; Rollup on ≤7) ││ ──────────────────────────────────────────────────── ││ Tree-shake + chunk + emit final .js files. Reads ││ phazeChunks()'s chunk-grouping decisions, deduplicates ││ modules across the dep graph, drops unused exports, splits ││ into phaze / phaze-directives / phaze-actions / ││ component chunks. ││ ││ OUTPUT: the actual final `.js` files. │└───────────────────────────────────────────────────────────────┘Stage 3’s output path depends on the host: dist/_astro/*.js under Astro, or — under phaze-cloudflare’s dual-environment build — dist/client/assets/*.js (the browser bundle + manifest) plus a single self-contained dist/server/index.js worker.
The dev pipeline
Section titled “The dev pipeline”Stages 1–2 still run in dev (per-request, no Rolldown bundle). What differs is what serves the output. phaze-cloudflare runs everything in one process, in either of two modes — both serving real workerd with real bindings:
Your .tsx → Stage 1 (Babel) → Stage 2 (Oxc) │ ┌─────────────────────────┴─────────────────────────┐ ▼ ▼ pnpm dev pnpm dev:hmr┌────────────────────────────────┐ ┌────────────────────────────────┐│ vite build --app --watch │ │ vite (dev server) ││ → rebuilds client + worker │ │ + @cloudflare/vite-plugin ││ on every save │ │ → worker runs in Miniflare ││ → closeBundle starts / hot- │ │ via Vite 8 module runner ││ swaps in-process Miniflare │ │ → client HMR (import.meta.hot)││ (mf.setOptions, ~200–1500ms) │ │ + Tier-1 page-level accept ││ → FULL RELOAD per rebuild │ │ boundary (no reload) ││ build-grade output (CSS links, │ │ dev-server output (FOUC unless ││ shaken WGSL, subset fonts) │ │ devStylesheets; no shaping) │└────────────────────────────────┘ └────────────────────────────────┘ └─────────────────────────┬─────────────────────────┘ ▼ real workerd + real bindings (D1 / KV / R2 / cloudflare:sockets), .dev.vars secrets, shared .wrangler/state/v3 — one process, no wrangler dev(Astro’s dev path is its own Vite dev server; @astrojs/cloudflare mounts the
same @cloudflare/vite-plugin module runner that phaze-cloudflare’s dev:hmr
composes.)
Why each tool is in this slot
Section titled “Why each tool is in this slot”| Tool | Strength | Why it’s used here |
|---|---|---|
| Babel | Mature plugin/visitor API. Can traverse the AST, mutate nodes, query scope (path.scope.getBinding), preserve JSX nodes through the transform. | phaze-compile needs all of this for the namespace rewrites + macros + scope-aware /numeric tracking. Oxc (Vite 8’s transformer) and esbuild expose no equivalent public JS visitor API; the Oxc and SWC transformer APIs are Rust-only. |
| Oxc | Rust-based (the oxc toolchain), extremely fast at the JSX-to-call lowering. Vite 8’s built-in transformer — it replaced esbuild in Vite 8. | Vite uses it for the high-volume, schema-stable transforms (JSX lowering, TS-strip, minify). phaze-compile leaves JSX intact so Oxc lowers it downstream — for .phaze files this is wired via the ?lang.tsx id tag (see phaze-compile). (On Vite ≤7 / current Astro this stage is esbuild.) |
| Rolldown | Rust-based bundler: best-in-class tree-shaking, the codeSplitting / advancedChunks API for chunk layout, deep cross-module dependency analysis. Vite 8’s bundler — it replaced Rollup in Vite 8. | Production builds need all of this; phazeChunks() feeds the chunk grouping. (On Vite ≤7 / current Astro this stage is Rollup, and the same grouping lives under rollupOptions.output.manualChunks.) |
The split is what makes phaze fast at build time AND aggressive at tree-shake: Babel handles the small set of phaze-specific transforms (slow but expressive AST API), Oxc handles the large volume of generic JSX/TS transforms (fast at the boring stuff), Rolldown handles the final assembly (smartest at deciding what ships where).
See 2. phaze-compile for the deeper dive — including why phaze doesn’t switch to all-Oxc or all-SWC, and how the per-module size.mjs measurements differ from sizeReport()’s real-app numbers.
Why nine packages, not one
Section titled “Why nine packages, not one”Three reasons:
-
They target different host systems. phaze-tsplugin plugs into the TypeScript Language Server. phaze-compile plugs into Babel (with a Vite adapter). phaze-vite plugs into Vite directly. phaze-astro plugs into Astro; phaze-cloudflare plugs straight into Vite as a single plugin (no Astro). phaze-language-tools plugs into Volar’s language-service framework; phaze-vscode plugs into VSCode’s extension API as an LSP client; phaze-glow plugs into VSCode as a theme + (optionally) a workbench-CSS-patching activation extension. Different hosts, different plugin APIs, different ASTs.
-
They run at different stages. phaze-tsplugin and
#7/#8run continuously inside your editor process. phaze-compile runs once per file during the build. phaze-vite splits across build (chunking) and dev (HMR). phaze-astro runs once at Astro’sastro:config:setuphook; phaze-cloudflare runs as a Vite plugin acrossconfig/configResolved/load/closeBundle(andconfigureServerin dev). phaze-language-tools runs as a stdio child process the IDE launches per workspace. -
They have different dependency profiles. phaze-tsplugin only needs the TypeScript types. phaze-compile pulls in Babel +
@jridgewell/gen-mapping. phaze-vite pulls in Vite’s type definitions. phaze-astro pulls in Astro’s integration types; phaze-cloudflare pulls in@cloudflare/vite-plugin,miniflare, andwrangler(and bundlesphaze-compile+phaze-vite). phaze-language-tools pulls in@volar/*+volar-service-typescript; phaze-vscode pulls invscode-languageclient; phaze-glow has no runtime deps beyond Node fs (used for the workbench-HTML patch). Keeping them separate means each consumer installs only what it needs — an Astro app never pulls inminiflare/wrangler, a Cloudflare app never pulls in Astro, a project that doesn’t want the LSP never pulls in@volar/*.
None of this changes the shipped runtime budget: every tooling package is build-time-or-editor-time only. Adding phaze-cloudflare’s miniflare/wrangler/@cloudflare/vite-plugin deps, or phaze-language-tools’ @volar/* deps, grows your dev/build/editor dependency tree — never the phaze chunk that reaches the browser.
Where the user-facing wiring lives
Section titled “Where the user-facing wiring lives”For an Astro app — which is the canonical phaze deployment — you only see two phaze entries in your astro.config.mjs:
import phaze from '@madenowhere/phaze-astro' // ← the integration (#4)import phazeVite from '@madenowhere/phaze-vite' // ← HMR plugin (#3)import { phazeChunks } from '@madenowhere/phaze-vite/chunks' // ← manualChunks builder (#3)
defineConfig({ integrations: [phaze()], // ← wires phaze-compile into Vite for you vite: { plugins: [phazeVite()], // ← HMR (separate from #2) build: { rollupOptions: { output: { manualChunks: phazeChunks() } } }, },})phaze-compile is not listed explicitly because the phaze-astro integration adds it automatically to vite.plugins. So most Astro app authors interact with #4 (Astro integration) + #3 (island HMR + chunking) at the config layer, and never write any #2 (compile) call site directly — it just runs every time you save a .tsx file.
For a Cloudflare app the wiring collapses to a single plugin — cloudflare() returns the whole chain (phaze-compile, the routing/codegen plugin, and in dev @cloudflare/vite-plugin), and wires phazeChunks() into both build environments for you:
import { defineConfig } from 'vite'import cloudflare from '@madenowhere/phaze-cloudflare/vite' // ← native adapter (#5)
export default defineConfig({ plugins: [cloudflare({ pages: 'src/pages', prefetch: true, router: true })],})You don’t add phazeVite() here: phaze-vite’s per-component replace() HMR is the island model, where each client:* island is its own mount boundary. phaze-cloudflare hydrates the whole page as one tree, so there’s no per-component boundary for replace() to target. Its pnpm dev:hmr mode instead makes the client entry the HMR accept boundary (App-subtree edits re-render the root; page edits swap the active page reactively). phazeChunks() is also wired internally — passed to both the client and ssr build environments — so the baseline chunking is handled for you. (A consumer that wants to fully own chunk layout can still supply its own codeSplitting.groups on Vite 8; see phaze-vite.)
phaze-tsplugin (#1) is opt-in via tsconfig.json:
{ "compilerOptions": { "plugins": [{ "name": "@madenowhere/phaze-tsplugin" }] }}It doesn’t affect the build at all — pure editor UX.
The editor stack (#6 / #7 / #8)
Section titled “The editor stack (#6 / #7 / #8)”The editor stack doesn’t go in any config file — it installs through the VSCode marketplace:
| Package | Marketplace ID | Install command |
|---|---|---|
#7 phaze-vscode | madenowhere.phaze-vscode | code --install-extension madenowhere.phaze-vscode |
#8 phaze-glow | madenowhere.phaze-glow | code --install-extension madenowhere.phaze-glow |
#6 phaze-language-tools isn’t installed directly — it ships bundled inside #7 (or any other LSP-client extension that wants .phaze IntelliSense; future Cursor / JetBrains adapters would consume the same npm package). The CLI use case (#9 phaze check) installs it as a regular npm dev-dep.
Both extensions are independent: installing only #7 gives you grammar + IntelliSense in whatever theme you’re already using; installing only #8 gives you the neon palette on any source language. Pair them for the full Phaze visual identity.
After installing, no per-project config is required — #7 activates on .phaze file open and auto-detects the workspace’s node_modules/typescript/lib to drive its LSP. The only project-level setting is phaze.brightness (0–1, default 0.45) for #8’s halo intensity, in user or workspace settings.json.
When you’d care about which tool
Section titled “When you’d care about which tool”| Situation | Tool that’s relevant |
|---|---|
Editor flagging use:NAME imports as unused | phaze-tsplugin |
Want to know what c(expr) / <For for:item> / use:spring compile to | phaze-compile |
Want island HMR when you edit a client:* .tsx component (Astro) | phaze-vite (HMR side) |
| Want no-reload HMR for a whole-page Cloudflare app | phaze-cloudflare’s pnpm dev:hmr mode (client-entry accept boundary) |
| Tuning bundle chunk layout | phaze-vite (chunking side) |
| Setting up phaze in a fresh Astro project | phaze-astro |
| Deploying phaze directly to Cloudflare Workers (no Astro) | phaze-cloudflare — single plugin |
| Setting up phaze in a non-Astro Vite project | phaze-compile + phaze-vite |
| Setting up phaze in a non-Vite build (raw Babel, esbuild, …) | Just phaze-compile (the Babel-plugin entry, no Vite wrapper) |
Want syntax highlighting on .phaze files | phaze-vscode |
Want hover types / completion / go-to-def inside .phaze files | phaze-vscode (LSP client) + phaze-language-tools (the LSP backend it bundles) |
Want Rust hover + red squiggles inside a lang: rust fence body | phaze-vscode — its rust-analyzer + cargo check bridge (not the TS LSP) |
Want IntelliSense for .phaze in another editor (Cursor / JetBrains / Sublime) | Just phaze-language-tools — the LanguagePlugin + language server; write a thin LSP client for your editor |
| Want the neon “Phaze Glow” theme + halo effect on any source language | phaze-glow — standalone |
Want to fail CI when .phaze files have type errors | phaze-check — headless tsc wrapper, drop-in for tsc --noEmit |
| Building a Rust HTTP API on Workers, or generating a TS client from one | phaze rust → Integrations › Rust (phaze-cli) |
The numbering is execution order in the editor-to-runtime pipeline, not “install order” — Astro users start by adding phaze-astro (#4) and let it pull in #2/#3; Cloudflare users add phaze-cloudflare (#5), which pulls in #2/#3 itself. #1 is a separate, deliberately-opt-in editor improvement.
phaze rust
Section titled “phaze rust”#10 is a separate ecosystem, not a tenth package in the pipeline above. phaze is also a Rust HTTP runtime on Cloudflare Workers, published to crates.io — it shares the phaze brand and the src/api/* routing convention, but compiles Rust → Wasm rather than running through Babel → Oxc → Rolldown. None of #1–#9 are involved.
Of its crates, exactly one is tooling — the rest are runtime:
| Crate | Role |
|---|---|
phaze-cli | the tool — installable binary (cargo install phaze-cli). phaze gen client walks src/api/**/*.rs → a typed TS client; phaze gen openapi → OpenAPI 3.1; (planned) a pure-Rust .fbs parser → runtime-free TS reader. Its own page — it grows in commands. |
phaze · phaze-core · phaze-macros | runtime — the workerd runtime, the shared matcher/types, the #[get]/#[post] macros |
phaze-fb · phaze-native | runtime — FlatBuffer integration (opt-in features = ["fb"]); the hyper/native sibling backend |
The two toolchains meet at codegen: phaze-cli emits the typed TS client + OpenAPI from Rust handlers (the Rust→TS signature bridge), and (planned) a runtime-free DataView reader from .fbs so the browser reads FlatBuffers with no npm flatbuffers. See phaze-cli for the codegen binary and Integrations › Rust for the runtime.