Skip to content

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 .phaze files. 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 helpers
4. 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 on use:NAME directive 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, /numeric inline, /match predicate inline + import swap, /list array-mutation inline, /time second-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 per client:* island) and the phazeChunks() manualChunks 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/vite into 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 .phaze files. A LanguagePlugin that runs phaze-compile’s format transform on each .phaze file to produce a virtual .tsx plus Volar CodeMapping[] (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 .phaze positions. The engine behind every downstream tool that wants type-aware .phaze editing — #7 consumes it as an LSP backend, and #9 (planned phaze check) consumes it as a CLI backend.

  • 7. phaze-vscode — VSCode language extension. Contributes the .phaze language ID, a TextMate grammar (named-fence highlighting for ---page / ---data / ---state + embedded source.tsx between fences so JSX/TS colorize natively), and a language configuration (brackets, comments, auto-close — mirrors .tsx). On .phaze open, launches phaze-language-tools as an LSP server via vscode-languageclient (stdio, child process) — hover types, completion, go-to-definition, find-references, diagnostics in .phaze files. Resolves the workspace’s TypeScript SDK so the LSP matches whatever tsc version your project depends on. Marketplace package; pairs with #8 but works standalone.

  • 8. phaze-glow — VSCode color theme + halo runtime. Phaze Glow color 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 #1a1a1a background phaze-compile and friends use across the docs. Optional workbench patcher (Phaze: Enable Glow command) injects text-shadow halo rules around each chromatic color slot, driven by the phaze.brightness setting (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 #7 for the full Phaze visual identity but works standalone with any source language.

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.

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.)

ToolStrengthWhy it’s used here
BabelMature 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.
esbuildGo-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.
RollupBest-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.

Three reasons:

  1. 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.

  2. They run at different stages. phaze-tsplugin and #7/#8 run 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’s astro:config:setup hook; phaze-cloudflare runs as a Vite plugin across config/configResolved/load/closeBundle (and configureServer in dev). phaze-language-tools runs as a stdio child process the IDE launches per workspace.

  3. 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, and wrangler (and bundles phaze-compile + phaze-vite). phaze-language-tools pulls in @volar/* + volar-service-typescript; phaze-vscode pulls in vscode-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 in miniflare/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.

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:

vite.config.ts
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 doesn’t go in any config file — it installs through the VSCode marketplace:

PackageMarketplace IDInstall command
#7 phaze-vscodemadenowhere.phaze-vscodecode --install-extension madenowhere.phaze-vscode
#8 phaze-glowmadenowhere.phaze-glowcode --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.

SituationTool that’s relevant
Editor flagging use:NAME imports as unusedphaze-tsplugin
Want to know what c(expr) / <For for:item> / use:spring compile tophaze-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 appphaze-cloudflare’s pnpm dev:hmr mode (client-entry accept boundary)
Tuning bundle chunk layoutphaze-vite (chunking side)
Setting up phaze in a fresh Astro projectphaze-astro
Deploying phaze directly to Cloudflare Workers (no Astro)phaze-cloudflare — single plugin
Setting up phaze in a non-Astro Vite projectphaze-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 filesphaze-vscode
Want hover types / completion / go-to-def inside .phaze filesphaze-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 languagephaze-glow — standalone
Want to fail CI when .phaze files have type errorsphaze-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.