Architecture
Pre-Release
Verter is pre-release software. APIs may change between releases — see the API Stability document.
Verter is a hybrid Rust + TypeScript monorepo. Rust owns carrier parsing, IDE TSX generation, runtime code generation, the shared semantic session, and the LSP server. TypeScript packages provide editor integration, TypeScript-provider adapters, protocol bindings, and bundler orchestration.
System Overview
One Compiler Authority, Two Outputs
The Rust compiler parses each carrier and produces purpose-specific outputs from the same compiler authority:
IDE output -- verter_compiler emits valid TSX and source mappings. verter_session owns host-backed resolution, invalidation, and the shared semantic graph; the LSP delegates TypeScript checking to TSGO or tsserver.
Runtime output -- The same Rust compiler emits optimized render functions for production builds through @verter/unplugin.
Repository Structure
| Directory | Purpose |
|---|---|
crates/verter_parser/ | Framework-neutral parser and syntax facts |
crates/verter_compiler/ | Runtime and IDE code generation |
crates/verter_semantic/ | Reusable semantic facts and typed IR |
crates/verter_session_query/ | Session query schema and query-domain contracts |
crates/verter_session/ | Host-backed resolution, semantic graph, caches, and compilation sessions |
crates/verter_type_runtime/ | Shared runtime support for native type evaluation |
crates/verter_workspace/ | Virtual filesystem and import-resolution authority |
crates/verter_diagnostics/ | Diagnostic engine: 22+ lint rules, visitor, DiagnosticSet |
crates/verter_actions/ | Code actions engine: quick fixes, refactoring |
crates/verter_lsp/ | Rust LSP server binary (stdio) |
crates/verter_ffi/ | FFI types for NAPI/WASM boundaries |
crates/verter_napi/ | Native Node.js bindings (NAPI-RS) |
crates/verter_wasm/ | WASM bindings (wasm-bindgen) |
packages/types/ | @verter/types -- TypeScript utility types |
packages/native/ | @verter/native -- Native binding loader |
packages/wasm/ | @verter/wasm -- WASM binding wrapper |
packages/unplugin/ | @verter/unplugin -- Universal bundler plugin |
packages/component-meta/ | @verter/component-meta -- Component metadata extraction + Type IR |
packages/vue-vscode/ | VS Code extension |
Async File Scheduler
The verter_scheduler crate provides per-file async staging with a priority queue. Files progress independently through Source → Analysis → Artifact stages. Cross-file blocking (macro type deps, external src attributes) is declarative — the scheduler manages wakeups via its BlockerRegistry.
Key concepts:
- FileNode: per-file state with ArcSwap snapshots and an atomic generation counter
- Priority tiers: Critical (hover/completion) > Interactive (did_open) > Background (workspace scan) > Maintenance
- Generation fencing: stale snapshots are invisible —
current_analysis()returnsNoneif the generation doesn't match - StageExecutor trait: the host plugs in real parse/compile logic; the scheduler provides coordination
The scheduler is integrated into VerterHost via the scheduler feature flag. During upsert(), the host populates both its legacy files map and the scheduler's FileNode snapshots in parallel.
Rust Compilation Pipeline
The Rust compiler uses an AST-based pipeline with five phases. The compile() function in verter_compiler orchestrates the entire flow:
Phase 1: Tokenizer
A zero-copy byte-level tokenizer scans the raw SFC source. It identifies <template>, <script>, and <style> block boundaries, tag names, attributes, and text content. The tokenizer operates directly on bytes without allocating intermediate string copies.
Phase 2: Parser
The parser consumes tokenizer output and builds an arena-based template AST. Elements, attributes, directives, text nodes, and interpolations are allocated in a flat arena with O(1) parent/child navigation. Structural directives (v-if, v-for, v-slot, v-once, ref) are extracted from props and cached as dedicated fields on element nodes for efficient access during codegen.
Script and style blocks are extracted as separate root nodes with their content spans and attributes (lang, scoped, module, src).
Phase 3: Style
The style phase scans <style> blocks for v-bind() expressions (CSS values bound to reactive data) and processes CSS features:
- Scoped CSS -- Inserts
[data-v-xxx]attribute selectors for style isolation - CSS Modules -- Hashes class names for local scoping
v-bind()in CSS -- Extracts expressions for runtime CSS variable injection- CSS Variable Analysis -- Extracts custom property definitions (
--name: value),var()references with fallbacks, and tracksv-bind()→ generated variable name mappings for cross-component CSS variable flow analysis
Phase 4: Script
The script phase processes <script setup> content:
- Macro expansion -- Transforms
defineProps,defineEmits,defineModel,defineSlots,defineExpose,defineOptions, andwithDefaultsinto their runtime equivalents - Binding extraction -- Identifies all declared variables, imports, and their binding types (setup, data, props, etc.) for template codegen
- Component wrapper -- Generates the component definition that wires props, emits, setup function, and render function together
- Companion script merging -- If both
<script>and<script setup>exist, merges the companion script's exports into the setup component
Phase 5: Template
The final phase walks the template AST and generates render function code. Two backends are available:
- VDOM -- Generates
_createElementVNode(),_createVNode(),_createTextVNode()calls with patch flags for Vue's virtual DOM runtime - Vapor -- Generates
_template(),_renderEffect(),_setText()calls for Vue's upcoming Vapor mode (no virtual DOM)
Both backends share a common DFS tree walker and binding resolver that determines whether each template expression references a setup binding, prop, data property, or global.
Output
The pipeline produces a CodeTransform -- a chunk-based deferred mutation engine (similar to MagicString) that tracks original source positions. From this, Verter emits:
- JavaScript output -- The compiled component module
- Source map -- VLQ-encoded source map mapping compiled output back to the original
.vuefile
TSX Codegen Path
In addition to the VDOM/Vapor render function backends, the Rust compiler has a separate TSX codegen path (crates/verter_compiler/src/tsx/). This path converts Vue templates into valid JSX that TypeScript can type-check:
v-if/v-else-if/v-elsebecome ternary expressionsv-forbecomes.map()calls:propbindings becomeprop={expression}JSX attributes@eventhandlers becomeonEvent={handler}JSX attributesv-modelbecomes the appropriate prop + event pair
The LSP server uses this TSX output for type-checking via TSGO (TypeScript's Go-based type checker), enabling hover types, diagnostics, and completions that reflect the actual template structure.
Virtual Filesystem (VFS)
All workspace file access and import resolution flows through verter_workspace. This crate is the single authority — no code outside NativeFs touches std::fs, and the host never does its own heuristic resolution.
Context-Aware Resolution
Import resolution is context-sensitive. The same specifier can resolve to different files depending on the resolution context:
| Context | Export Conditions | Legacy Fields |
|---|---|---|
(CodegenBlocker, EsmImport) | ["import", "default"] | ["module", "main"] |
(CodegenBlocker, TypeImport) | ["types", "import", "default"] | ["types", "typings", "main"] |
(ProviderGraph, *) | ["types", "import", "default"] | ["types", "typings", "main"] |
(*, RequireCall) | ["require", "default"] | ["main"] |
For example, import { Foo } from 'pkg' during codegen resolves to pkg/index.js (runtime entry), while import type { Foo } from 'pkg' resolves to pkg/index.d.ts (type entry).
Workspace API (Node.js)
JS consumers access the filesystem exclusively through the Workspace class from @verter/native. All methods are async (Promise-based, runs on libuv thread pool):
import { Workspace, VerterHost } from "@verter/native";
const ws = new Workspace(["/path/to/project"]);
// File access — all async
const content = await ws.readFile("/path/to/file.vue");
const exists = await ws.fileExists("/path/to/file.ts");
const entries = await ws.readDir("/path/to/dir");
const files = await ws.walk("/path", ["node_modules"], [".vue", ".ts"]);
// Resolution
const resolved = await ws.resolveImport("/src/App.vue", "./Child.vue");
// Project configuration
ws.configureProjects([
{
root: "/path/to/project",
workspaceRoot: "/path/to/project",
compilerOptions: {
baseUrl: ".",
// NOTE: an ordered array of { pattern, targets } — not a tsconfig-style object map.
paths: [{ pattern: "@/*", targets: ["src/*"] }],
},
},
]);
// Create host backed by workspace
const host = VerterHost.withWorkspace({}, ws);No node:fs imports are used in any JS package. The Workspace object is the sole filesystem authority from JavaScript.
File Read Priority
1. Overlay (active editor buffer — set via notifyUpsert)
2. Snapshot (cached content — populated on first read)
3. Disk (NativeFs — FilesystemWorkspace only)Resolution Priority
1. Exact resolutions (authoritative — injected by bundler/LSP via setImportDependencies)
2. Project resolver (tsconfig paths, workspace aliases, node_modules package.json)
3. None (no heuristic fallback, no extension guessing)Next Steps
- Features -- Type safety features overview
- Performance -- Compilation benchmarks
- Diagnostics & Linting -- Built-in diagnostic rules
- Cross-File Optimization -- Whole-program analysis