7. phaze-vscode
1. phaze-tsplugin ← editor (TS Language Service)2. phaze-compile ← build-time AST rewriting3. phaze-vite ← island HMR + chunking helpers4. phaze-astro ← Astro integration (island model)5. phaze-cloudflare ← native Cloudflare Workers adapter (whole-page)
─── Editor stack ────────────────────────────────────────────────────6. phaze-language-tools ← Volar LSP backend (.phaze → virtual .tsx)7. phaze-vscode ← VSCode extension (grammar + LSP client) ← you are here8. phaze-glow ← VSCode theme + halo runtimemadenowhere.phaze-vscode is the VSCode editor integration for .phaze files. It’s a marketplace extension — code --install-extension madenowhere.phaze-vscode — that contributes everything VSCode needs to treat .phaze files as a first-class language: an extension-to-language association, a TextMate grammar for syntax highlighting, a language configuration for brackets / comments / auto-close, and an LSP client that launches phaze-language-tools (#6) for type-aware features.
It’s deliberately the thin layer of the editor stack. The heavyweight work — parsing .phaze, synthesizing the virtual .tsx, running the TypeScript service against it, mapping responses back to .phaze positions — all lives in #6. phaze-vscode just bridges VSCode’s extension contract to that backend.
What VSCode gets
Section titled “What VSCode gets”The extension contributes three things via package.json:
1. Language registration
Section titled “1. Language registration”"languages": [ { "id": "phaze", "extensions": [".phaze"], "aliases": ["Phaze", "phaze"], "configuration": "./language-configuration.json" }]Now VSCode knows that .phaze files belong to the phaze language, that “Phaze” is the human-readable label (status bar + Command Palette → Change Language Mode), and that its bracket / comment / auto-close rules live in language-configuration.json. Themes and other extensions can target the phaze language ID.
2. TextMate grammar
Section titled “2. TextMate grammar”"grammars": [ { "language": "phaze", "scopeName": "source.phaze", "path": "./syntaxes/phaze.tmLanguage.json", "embeddedLanguages": { "source.tsx": "typescriptreact" } }]The grammar (syntaxes/phaze.tmLanguage.json) has three rules:
- Named fence (
---page,---data,---signals,---state) — distinct token scope so themes can color them as section markers. - Bare fence (
---) — separate scope for the page-mode transition fence between the head and the body. - TSX body — everything that’s not a fence is included via
source.tsx. VSCode’s embedded-language machinery lets the typescriptreact grammar run over those ranges, so JSX / TS / template literals / regex all colorize natively without re-implementing the TSX grammar.
embeddedLanguages: { "source.tsx": "typescriptreact" } is the magic — VSCode delegates bracket matching (⌘B), comment toggling (⌘/), and snippet expansion inside TSX ranges to the typescriptreact language. Without it those features would fall back to plain text.
3. Language configuration
Section titled “3. Language configuration”language-configuration.json mirrors .tsx for brackets ((), [], {}), auto-close pairs ("", '', “, (), [], {}), comment toggles (//, /* */), surrounding pairs, and folding markers. The reader gets the same editing ergonomics they expect from a .tsx file.
The LSP client
Section titled “The LSP client”On .phaze file open, src/extension.js launches phaze-language-tools as a stdio child process via vscode-languageclient:
const { LanguageClient, TransportKind } = require('vscode-languageclient/node')
async function activate(context) { // Resolve the language server's bin entry from this extension's // node_modules. require.resolve walks the actual resolver, so it // handles pnpm symlinks, hoisting, and workspace links uniformly. const serverModule = require.resolve( '@madenowhere/phaze-language-tools/bin/phaze-language-server.js', )
// Locate the workspace's TypeScript SDK — the user's // `node_modules/typescript/lib` is the source of truth so the LSP // matches whatever tsc version the project depends on. const workspaceFolder = vscode.workspace.workspaceFolders?.[0] const tsdk = path.join(workspaceFolder.uri.fsPath, 'node_modules', 'typescript', 'lib')
const serverOptions = { run: { module: serverModule, transport: TransportKind.ipc }, debug: { module: serverModule, transport: TransportKind.ipc, options: { execArgv: ['--nolazy', '--inspect=6009'] } }, } const clientOptions = { documentSelector: [{ scheme: 'file', language: 'phaze' }], initializationOptions: { typescript: { tsdk } }, }
client = new LanguageClient('phazeLanguageServer', 'Phaze Language Server', serverOptions, clientOptions) await client.start() context.subscriptions.push({ dispose: () => client?.stop() })}Three details worth understanding:
require.resolveis the bin-finder.@madenowhere/phaze-language-toolsships as a workspace-sibling dependency of the extension;require.resolve('@madenowhere/phaze-language-tools/bin/…')runs Node’s actual resolver, so pnpm’s isolatednode_modules, npm’s hoisted layout, andlink:symlinks all work without per-environment path twiddling. Same pattern Astro’s VSCode extension uses.TransportKind.ipcruns the server in a Node child process talking JSON-RPC over the parent-child IPC channel. No port allocation, no stdio buffering surprises. Thedebugconfig is identical except for--nolazy --inspect=6009so you can attach a debugger to the language server during development without touching the production runtime.initializationOptions.typescript.tsdkis the absolute path to the workspace’s TS SDK —phaze-language-toolsloadstscfrom there instead of bundling its own, so type-checking matches whatever tsc version your project depends on. If the user has no workspace folder open, the extension surfaces a warning and returns (.phazeIntelliSense needs a workspace with TypeScript installed).
What VSCode users see once it’s running:
- Hover on a
signal()call inside a fence shows the realSignal<T>type with the inferred generic. - Completion on
.after a signal binding lists(),.set,.update,.subscribe, etc. - Go-to-definition on a directive name (
use:autofocus) jumps to its export inphaze-directives. - Find references crosses the
.phaze↔ virtual.tsxboundary transparently. - Diagnostics (TS errors) appear at the correct
.phazeline.
The mapping back to .phaze positions happens inside Volar (see phaze-language-tools) — the extension is just a transport.
Why a separate extension from phaze-glow
Section titled “Why a separate extension from phaze-glow”#7 and #8 are deliberately split. #7 contributes structural language support (grammar / LSP / language configuration) — works with any color theme. #8 (phaze-glow) is a color theme + an optional workbench patcher for the halo effect — works on any source language, not just .phaze. Splitting them lets a user adopt:
- Just
#7—.phazeIntelliSense + their existing theme. - Just
#8— Phaze Glow colors / halo on any source language. - Both — the full Phaze visual identity.
If they were one extension, you’d have to choose between forcing the theme on every install (rude to users who like Solarized) or hiding the IntelliSense behind a theme switch (unhelpful). Two extensions, two clean install paths.
The two extensions also have different release cadences: language services updates ship whenever phaze-language-tools (#6) ships, which tracks phaze-compile changes. Theme tweaks (color palette adjustments, halo brightness defaults) ship on their own rhythm. Separate VSIX files = separate version histories on the marketplace.
What it does not do
Section titled “What it does not do”- No semantic highlighting beyond TS. Token coloring on
.phazefences comes from the TextMate grammar; coloring inside the TSX body comes from the embedded typescriptreact grammar + the TS service’s semantic tokens. There’s no Phaze-specific semantic colorization. - No formatter. Prettier handles
.tsxand ignores.phaze. A v2 formatter would run phaze-compile’s parser, format each fence body via Prettier’s TSX printer, and reassemble — out of scope for now. - No
.phazefile template. No “New Phaze File” command; users start from.tsxand rename. A snippet pack could ship later if there’s demand. - No build / dev-server orchestration. The extension doesn’t run
pnpm dev; it’s pure editor surface. Build/dev concerns belong to phaze-vite and the host adapters (#4/#5).
Install
Section titled “Install”code --install-extension madenowhere.phaze-vscodeOr search “Phaze” by publisher madenowhere in the Extensions view.
The extension activates on first .phaze open — no per-project config. To pair with phaze-glow:
code --install-extension madenowhere.phaze-vscodecode --install-extension madenowhere.phaze-glowThen switch theme via Command Palette → Color Theme → Phaze Glow.
| Path | Role |
|---|---|
package.json | Extension manifest — language ID, grammar, language-configuration, vscode-languageclient dep, @madenowhere/phaze-language-tools dep. |
language-configuration.json | Brackets, comments, auto-close pairs, indentation, folding — mirrors .tsx. |
syntaxes/phaze.tmLanguage.json | TextMate grammar. Three rules: named fence (---<label>), bare fence (---), TSX-body include (source.tsx). |
src/extension.js | Activation entry. Resolves the LSP bin via require.resolve, locates the workspace TS SDK, launches phaze-language-server over IPC. |