Tooling
Phaze ships eight tooling packages spanning two layers:
- Build pipeline (1–5) — sit between source and the JS that runs in the browser. Three core (editor TS-server, build, dev-server) + two host adapters (Astro / Cloudflare) of which you pick one per deployment target.
- Editor stack (6–8) — provide IntelliSense, syntax highlighting, theming for
.phazefiles. Independent of the build pipeline; install whichever you want, in any combination.
They’re separate packages because they target separate stages of the developer-to-runtime pipeline — editor TS plugin, build-time AST, dev-server HMR, host integration, in-editor language service, IDE extensions — and a clean separation makes it possible to use or replace any piece individually.
─── 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#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()manualChunksbuilder. 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(plannedphaze 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), esbuild, and Rollup each have a role — they’re not alternatives but a stack:
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 — esbuild (Vite's transformer) ││ ──────────────────────────────────────────────────── ││ 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 — Rollup (Vite's bundler, prod builds only) ││ ──────────────────────────────────────────────────── ││ Tree-shake + chunk + emit final .js files. Reads ││ phazeChunks()'s manualChunks 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 Rollup 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 (esbuild) │ ┌─────────────────────────┴─────────────────────────┐ ▼ ▼ 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 7 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. esbuild has no equivalent public visitor API; SWC’s is Rust-only. |
| esbuild | Go-based, ~10-100× faster than Babel at the JSX-to-call lowering. | Vite uses it for the high-volume, schema-stable transforms (JSX, TS-strip, minify). phaze-compile leaves JSX intact so esbuild can do its fast lowering pass downstream. |
| Rollup | Best-in-class tree-shaking (more aggressive than esbuild’s), manualChunks API for chunk layout, deep cross-module dependency analysis. | Production builds need all of this. esbuild’s tree-shake is decent but not as aggressive; esbuild has no chunking system that matches 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), esbuild handles the large volume of generic JSX/TS transforms (fast at the boring stuff), Rollup 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-esbuild or all-SWC, and how the per-module size.mjs measurements differ from sizeReport()’s real-app numbers.
Why eight packages, not one
Section titled “Why eight 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 handled internally — passed to both the client and ssr build environments — so consumers don’t thread manualChunks through by hand.
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, planned) will install 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 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 |
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.