commit 95f23e2eb3b5d36367a8d7b1700afd2b6490f483 Author: Luke Hagar Date: Sun Nov 9 22:22:52 2025 -0600 Saving inital Docs POC diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d4bff1 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# VolarJS Documentation Hub + +VolarJS powers Vue tooling across editors, yet the official docs are scattered. This repository collects practical, engineer-focused notes about the Volar language server so teams can understand, extend, and debug it without spelunking through source code. + +## Scope +- Explain Volar architecture: core packages, key entrypoints, and how they plug into editor hosts. +- Document the LSP surface that Volar implements (capabilities, custom requests, known quirks). +- Share recipes for common tasks such as adding diagnostics, language features, or IDE integrations. +- Track ecosystem tooling (volar-service modules, language plugins, CLI utilities) and their compatibility. +- Publish getting-started and integration guides (e.g., combining Volar with JSON/YAML language services). + +## Roadmap +1. **Landscape survey** – catalogue existing blog posts, RFCs, and source references. +2. **LSP deep dive** – map each request / notification to the Volar handlers and note deviations from the spec. +3. **Extensibility guide** – document how to write and register custom language features. +4. **Debugging playbook** – logging, tracing, and profiling techniques for the Volar server process. + +## Repo Structure +Planned directories: +- `docs/` – longer-form guides (architecture, extensibility, debugging). See the [docs index](docs/README.md) for a full table of contents. +- `docs/live-examples.md` – curated list of real projects using VolarJS (VS Code extension, Neovim/LSP integrations, online IDEs, CLI tooling, community plugins). +- Key guides (see [docs index](docs/README.md) for the full list): + - [`docs/getting-started.md`](docs/getting-started.md) + - [`docs/building-lsp-json-yaml.md`](docs/building-lsp-json-yaml.md) + - [`docs/plugin-authoring.md`](docs/plugin-authoring.md) + - [`docs/source-map-and-code-gen.md`](docs/source-map-and-code-gen.md) + - [`docs/configuration-and-projects.md`](docs/configuration-and-projects.md) + - [`docs/performance-and-debugging.md`](docs/performance-and-debugging.md) + - [`docs/testing-and-ci.md`](docs/testing-and-ci.md) + - [`docs/telemetry-and-observability.md`](docs/telemetry-and-observability.md) + - [`docs/workspace-diagnostics.md`](docs/workspace-diagnostics.md) + - [`docs/error-handling-and-resilience.md`](docs/error-handling-and-resilience.md) + - [`docs/ux-best-practices.md`](docs/ux-best-practices.md) + - [`docs/lsp-benchmarking.md`](docs/lsp-benchmarking.md) + - [`docs/troubleshooting-faq.md`](docs/troubleshooting-faq.md) + - [`docs/release-and-upgrade.md`](docs/release-and-upgrade.md) + - [`docs/vetur-migration.md`](docs/vetur-migration.md) + - [`docs/custom-diagnostics.md`](docs/custom-diagnostics.md) + - [`docs/monaco-playground.md`](docs/monaco-playground.md) + - [`docs/volar-kit-and-editor.md`](docs/volar-kit-and-editor.md) + - [`docs/typescript-plugin-deep-dive.md`](docs/typescript-plugin-deep-dive.md) + - [`docs/volar-service-catalog.md`](docs/volar-service-catalog.md) + - [`docs/cli-integration.md`](docs/cli-integration.md) + - [`docs/editor-integration-alt.md`](docs/editor-integration-alt.md) + - [`docs/cross-language-recipes.md`](docs/cross-language-recipes.md) + - [`docs/package-api-reference.md`](docs/package-api-reference.md) + - [`docs/response-formatting.md`](docs/response-formatting.md) + - [`docs/cross-language-recipes.md`](docs/cross-language-recipes.md) + - [`docs/package-api-reference.md`](docs/package-api-reference.md) + - [`docs/telemetry-and-observability.md`](docs/telemetry-and-observability.md) +- `reference/` – API and protocol notes, ideally generated from code comments over time. +- `examples/` – minimal projects showing custom Volar features in action. + - `examples/json-yaml-lsp` – runnable server that mixes Volar with JSON/YAML services. + +## Contributing +Issues and PRs are welcome. Please prefer: +- Real-world examples that demonstrate a concept. +- Links back to Volar source or specs for verification. +- Clear reproduction steps for bugs or editor quirks. + +--- +This repo is intentionally lightweight—start by dropping notes in Markdown, then iterate toward polished docs once content stabilizes. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0c0ecb0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,51 @@ +# VolarJS Docs Index & Table of Contents + +Use this index to jump to any guide. Each entry lists key sections so you can dive straight into the content you need. + +## Core Guides + +1. **[Getting Started](getting-started.md)** – mental model, package overview, installation, bootstrapping, integration paths, troubleshooting. +2. **[Building a JSON/YAML LSP](building-lsp-json-yaml.md)** – architecture, setup steps, performance/cancellation, diagnostics/quick fixes, packaging. +3. **[Plugin Authoring](plugin-authoring.md)** – language plugins, language-service plugins, TypeScript bridge, registration, best practices. +4. **[Source Map & Code Gen](source-map-and-code-gen.md)** – `@volar/source-map` and `@volar/code-gen` APIs, examples, debugging tips. +5. **[Configuration & Projects](configuration-and-projects.md)** – Take Over Mode, monorepos, multi-root workspaces, settings synchronization. +6. **[Performance & Debugging](performance-and-debugging.md)** – profiling, throttling, hotspots, telemetry hooks, troubleshooting scenarios. +7. **[Testing & CI](testing-and-ci.md)** – unit/integration testing, VS Code/Neovim harnesses, snapshot strategies, CI workflows. +8. **[Telemetry & Observability](telemetry-and-observability.md)** – logging, telemetry events, work-done progress, health endpoints. +9. **[Workspace Diagnostics](workspace-diagnostics.md)** – protocol primer, result IDs, incremental updates, partial results. +10. **[Error Handling & Resilience](error-handling-and-resilience.md)** – schema fallbacks, configuration validation, restart strategies. +11. **[UX Best Practices](ux-best-practices.md)** – progress indicators, notifications, settings UX, editor-specific guidance. +12. **[LSP Benchmarking](lsp-benchmarking.md)** – metrics, tooling (`lsp-bench`, custom harnesses), reporting, CI automation. +13. **[Troubleshooting & FAQ](troubleshooting-faq.md)** – quick fixes for duplicate diagnostics, schema fetch failures, logging. +14. **[Release & Upgrade Guide](release-and-upgrade.md)** – version matrix, upgrade checklist, CI matrices. +15. **[Vetur Migration](vetur-migration.md)** – step-by-step instructions for moving from Vetur to Volar. +16. **[Custom Diagnostics](custom-diagnostics.md)** – recipe for building project-specific diagnostic plugins. +17. **[Monaco Playground](monaco-playground.md)** – integrating Volar with browser playgrounds via `@volar/monaco`. +18. **[Volar Kit, Editor & Monaco](volar-kit-and-editor.md)** – bootstrap servers with `@volar/kit`, embed language services in custom editors, and integrate with Monaco. +19. **[TypeScript Plugin Deep Dive](typescript-plugin-deep-dive.md)** – `@volar/vue-typescript`, compiler options, macros, diagnostics control. +20. **[Volar Service Catalog](volar-service-catalog.md)** – official/community `volar-service-*` plugins and configuration patterns. +21. **[CLI Integration](cli-integration.md)** – building `vue-tsc`-style headless tooling, workspace diagnostics in CI. +22. **[Alternative Editor Integration](editor-integration-alt.md)** – guidance for JetBrains, Sublime, Neovim, Emacs, custom editors, VS Code Web. +23. **[Cross-Language Recipes](cross-language-recipes.md)** – adapting Volar’s architecture to Astro, Svelte, Marko, Tailwind-heavy setups, etc. +24. **[Package API Reference](package-api-reference.md)** – quick lookup for `@volar/editor`, `@volar/language-service`, `@volar/typescript`, etc. +25. **[Response Formatting & Content](response-formatting.md)** – best practices for diagnostics, hovers, completions, and code actions. + +## Example Catalogue + +- **[Live Examples](live-examples.md)** – curated list of editor extensions, online IDEs, CLI tooling, and community plugins powered by VolarJS. + +## Examples + +- **[`examples/json-yaml-lsp`](../examples/json-yaml-lsp/README.md)** – runnable project combining Volar with JSON/YAML services, showcasing diagnostics, quick fixes, `$ref` definitions, workspace diagnostics, and configuration reloads. + +## Suggested Reading Order + +1. Start with **Getting Started** to grasp the core packages. +2. Follow with **Plugin Authoring** and **Source Map & Code Gen** if you plan to extend Volar. +3. Consult **Configuration & Projects** before rolling out to monorepos or multi-root workspaces. +4. Use **Performance**, **Testing**, **Telemetry**, and **Error Handling** guides to harden your server. +5. Refer to **UX Best Practices** and **Benchmarking** when polishing the user experience and ensuring regressions don’t slip in. + +## Cross-Links + +Every guide links back to this index via its navigation header. If you create new docs, add them here so the ecosystem stays discoverable. diff --git a/docs/building-lsp-json-yaml.md b/docs/building-lsp-json-yaml.md new file mode 100644 index 0000000..62b8429 --- /dev/null +++ b/docs/building-lsp-json-yaml.md @@ -0,0 +1,457 @@ +# Building a JSON/YAML-Aware LSP with VolarJS + +[TOC](#table-of-contents) • [Getting Started](getting-started.md) • [Plugin Authoring](plugin-authoring.md) • [Performance](performance-and-debugging.md) + +## Table of Contents + +1. [Architecture Snapshot](#architecture-snapshot) +2. [Required Packages](#required-packages) +3. [Step 1 – Bootstrap a Volar Server Shell](#step-1--bootstrap-a-volar-server-shell) +4. [Step 2 – Wire JSON and YAML Services](#step-2--wire-json-and-yaml-services) +5. [Step 3 – Dispatch Requests Inside the LSP Server](#step-3--dispatch-requests-inside-the-lsp-server) +6. [Step 4 – Capabilities and Initialization](#step-4--capabilities-and-initialization) +7. [Step 5 – LSP-Friendly Performance & Cancellation](#step-5--lsp-friendly-performance--cancellation) +8. [Diagnostics, Quick Fixes, and `$ref` Definitions](#diagnostics-quick-fixes-and-ref-definitions) +9. [Testing the Server](#testing-the-server) +10. [Operational Considerations](#operational-considerations) +11. [Packaging & Distribution](#packaging--distribution) +12. [Next Steps](#next-steps) + +This guide walks through composing a custom language server that combines Volar’s connection/runtime utilities with the existing VS Code JSON and Red Hat YAML services. The goal: a single LSP binary that understands structured config files while leaving room for your own Volar plugins. + +## Architecture Snapshot + +``` +Client (VS Code / Neovim / Monaco LSP) ─▶ @volar/language-server + ├─▶ Volar language service (custom plugins) + ├─▶ vscode-json-languageservice + └─▶ yaml-language-server (service API) +``` + +- `@volar/language-server` supplies the transport, file watching, and initialization plumbing. +- The Volar service handles any custom documents you plug in later (left empty in this starter). +- JSON and YAML requests are delegated to their respective language service implementations. You route incoming LSP requests to the right backend based on document URI / languageId. + +## Required Packages + +```bash +npm install --save-dev \ + @volar/language-server \ + vscode-json-languageservice \ + yaml-language-server +``` + +Optional helpers: + +- `@volar/kit` if you prefer a batteries-included bootstrapper for config + watch. +- `volar-service-` plugins for any custom diagnostics or editor behaviors you plan to add later. + +## Step 1 – Bootstrap a Volar Server Shell + +```ts +// server/host.ts +import { createConnection, createServer } from '@volar/language-server/node'; +import { createSimpleProject } from '@volar/language-server/lib/project/simpleProject'; + +export const connection = createConnection(); +export const server = createServer(connection); + +const project = createSimpleProject([]); +const languageServicePlugins = []; + +connection.onInitialize((params) => server.initialize(params, project, languageServicePlugins)); +connection.onInitialized(() => server.initialized()); +connection.onShutdown(() => server.shutdown()); +``` + +- `createSimpleProject([])` wires Volar’s document tracking and transport without custom language plugins. Swap in your own `LanguagePlugin` array whenever you add domain-specific logic. +- `languageServicePlugins` is where you register Volar service plugins (`volar-service-*`) should you need additional completions, hovers, or refactors for your target language. + +## Step 2 – Wire JSON and YAML Services + +```ts +// server/jsonYaml.ts +import { getLanguageService as createJsonService } from 'vscode-json-languageservice'; +import { getLanguageService as createYamlService } from 'yaml-language-server'; + +export function createJsonYamlServices() { + const json = createJsonService({ + schemaRequestService: async (uri) => fetchSchema(uri), + workspaceContext: { + resolveRelativePath(relativePath, resource) { + try { + const base = new URL(resource); + return new URL(relativePath, base).toString(); + } catch { + return relativePath; + } + }, + }, + contributions: [], + }); + + const yaml = createYamlService({ + schemaRequestService: async (uri) => fetchSchema(uri), + workspaceContext: { + resolveRelativePath(relativePath, resource) { + try { + const base = new URL(resource); + return new URL(relativePath, base).toString(); + } catch { + return relativePath; + } + }, + }, + telemetry: { send: () => {}, sendError: () => {}, sendTrack: () => {} }, + clientCapabilities: {}, + }); + + return { json, yaml }; +} +``` + +Tips: + +- Implement `fetchSchema` to load schemas from HTTP, file system, or an internal registry. +- Cache schemas aggressively; both services expect memoized responses for performance. +- Use `server.workspaceFolders` (see the example) to resolve relative schema paths so `foo/schema.json` can be fetched without hard-coding a root. + +## Step 3 – Dispatch Requests Inside the LSP Server + +```ts +// server/main.ts +import { URI, Utils } from 'vscode-uri'; +import { connection, server } from './host'; +import { createJsonYamlServices } from './jsonYaml'; +import type { TextDocument } from 'vscode-languageserver-textdocument'; + +const structured = createJsonYamlServices({ + resolveWorkspaceUri(relativePath) { + const [root] = server.workspaceFolders.all; + return root ? Utils.joinPath(root, relativePath).toString() : undefined; + }, +}); +const pendingValidation = new Map>(); +const VALIDATION_DELAY = 200; + +connection.onInitialized(async () => { + server.initialized(); + await applyConfiguration(); + server.configurations.onDidChange(applyConfiguration); + server.documents.all().forEach((document) => queueValidation(document.uri)); +}); + +server.documents.onDidOpen(({ document }) => queueValidation(document.uri)); +server.documents.onDidChangeContent(({ document }) => queueValidation(document.uri)); +server.documents.onDidClose(({ document }) => { + cancelQueuedValidation(document.uri); + connection.sendDiagnostics({ uri: document.uri, diagnostics: [] }); +}); + +// Workspace diagnostics: editors can fetch full workspace snapshots when supported +connection.languages.diagnostics.onWorkspace(async (_params, token) => { + const items = []; + for (const doc of server.documents.all()) { + if (token.isCancellationRequested) break; + items.push({ + uri: doc.uri, + kind: DocumentDiagnosticReportKind.Full, + version: doc.version ?? null, + items: await collectDiagnostics(doc), + }); + } + return { items }; +}); + +connection.onCompletion(async (params, token) => { + if (token.isCancellationRequested) return null; + const doc = getDocument(params.textDocument.uri); + if (!doc) return null; + + const backend = pickBackend(doc); + if (backend === 'json') { + const result = await structured.completeJson(doc, params.position); + return token.isCancellationRequested ? null : result; + } + if (backend === 'yaml') { + const result = await structured.completeYaml(doc, params.position); + return token.isCancellationRequested ? null : result; + } + return null; +}); + +connection.onHover(async (params, token) => { + if (token.isCancellationRequested) return null; + const doc = getDocument(params.textDocument.uri); + if (!doc) return null; + + const backend = pickBackend(doc); + if (backend === 'json') { + const result = await structured.hoverJson(doc, params.position); + return token.isCancellationRequested ? null : result; + } + if (backend === 'yaml') { + const result = await structured.hoverYaml(doc, params.position); + return token.isCancellationRequested ? null : result; + } + return null; +}); + +async function validate(document: TextDocument) { + const diagnostics = await collectDiagnostics(document); + + const latest = getDocument(document.uri); + if (!latest || latest.version !== document.version) { + return; + } + + connection.sendDiagnostics({ uri: document.uri, diagnostics }); +} + +function queueValidation(uri: string) { + cancelQueuedValidation(uri); + const timer = setTimeout(() => { + pendingValidation.delete(uri); + const doc = getDocument(uri); + if (doc) { + validate(doc); + } + }, VALIDATION_DELAY); + pendingValidation.set(uri, timer); +} + +function cancelQueuedValidation(uri: string) { + const handle = pendingValidation.get(uri); + if (handle) { + clearTimeout(handle); + pendingValidation.delete(uri); + } +} + +async function collectDiagnostics(document: TextDocument) { + const backend = pickBackend(document); + if (backend === 'json') { + return structured.validateJson(document); + } + if (backend === 'yaml') { + return structured.validateYaml(document); + } + return []; +} + +function getDocument(uri: string): TextDocument | undefined { + return server.documents.get(URI.parse(uri)) as TextDocument | undefined; +} +``` + +> Tip: This snippet imports `DocumentDiagnosticReportKind` from `vscode-languageserver/node` to mark each workspace diagnostic report as `Full`. + +Routing Strategy: + +1. Check `textDocument.languageId` (if the client sets it) and fall back to file extension. +2. If neither JSON nor YAML match, forward the request to Volar. +3. For JSON/YAML services, you must manage document snapshots—use `connection.workspace.getTextDocument(uri)` or maintain a `TextDocuments` instance to keep their APIs synchronized. + +## Step 4 – Capabilities and Initialization + +Make sure the combined server advertises the correct capabilities: + +```ts +connection.onInitialize(() => ({ + capabilities: { + textDocumentSync: TextDocumentSyncKind.Incremental, + completionProvider: { triggerCharacters: ['.', '"', '/', '<', ':'] }, + hoverProvider: true, + documentFormattingProvider: true, + schemaRequestService: true, + }, +})); +``` + +- JSON/YAML features may have narrower trigger characters; merge the superset so clients activate correctly. +- Expose custom `initializationOptions` to toggle schemas, plugin sets, or Take Over Mode. + +> **Example project:** `examples/json-yaml-lsp/` contains a runnable implementation that wires the same services using the bare `@volar/language-server` utilities so you can attach any editor client immediately. + +## Step 5 – LSP-Friendly Performance & Cancellation + +To stay responsive on large workspaces: + +```ts +const pendingValidation = new Map>(); +const VALIDATION_DELAY = 200; + +function queueValidation(uri: string) { + cancelQueuedValidation(uri); + const timer = setTimeout(async () => { + pendingValidation.delete(uri); + const document = getDocument(uri); + if (document) { + await validateDocument(document); + } + }, VALIDATION_DELAY); + pendingValidation.set(uri, timer); +} + +connection.onCompletion(async (params, token) => { + if (token.isCancellationRequested) return null; + const document = getDocument(params.textDocument.uri); + if (!document) return null; + + const result = await structured.completeJson(document, params.position); + return token.isCancellationRequested ? null : result; +}); + +server.documents.onDidClose(({ document }) => { + cancelQueuedValidation(document.uri); + connection.sendDiagnostics({ uri: document.uri, diagnostics: [] }); +}); + +async function applyConfiguration() { + const config = + (await server.configurations.get('volarJsonYaml', undefined)) ?? {}; + structured.configure(config); + await server.languageFeatures.requestRefresh(false); + server.documents.all().forEach((document) => queueValidation(document.uri)); +} +``` + +- **Debounce validations** so rapid edits don’t trigger repeated schema checks. +- **Gate diagnostics by version** (after awaiting async work, re-fetch the document and compare `version` before sending). +- **Honor cancellation tokens** on hover/completion so abandoned requests don’t waste CPU. +- **Clear diagnostics on close** to avoid stale warnings when a document leaves the workspace. +- **Reflect configuration changes** via `server.configurations.get/onDidChange` and call `server.languageFeatures.requestRefresh` so clients re-query capabilities when settings toggle. +- The sample exports a `JsonYamlConfiguration` helper type so the configuration payload is strongly typed on both server and JSON/YAML helper layers. +- **Workspace diagnostics**: register `connection.languages.diagnostics.onWorkspace` so editors that support the capability (VS Code Insiders, Neovim, etc.) can request batched diagnostics across every open file. +- Clients can provide that payload via normal LSP configuration plumbing (e.g., VS Code settings): + +```json +{ + "volarJsonYaml": { + "json": { + "schemas": [{ "fileMatch": ["foo.json"], "url": "./schemas/foo.schema.json" }] + }, + "yaml": { + "customTags": ["!secret scalar"] + } + } +} +``` + +The sample project implements the same helpers so you can copy the exact patterns. + +## Diagnostics, Quick Fixes, and `$ref` Definitions + +With the wiring above you can progressively light up richer language features: + +```ts +connection.onCodeAction(async (params, token) => { + if (token.isCancellationRequested) return null; + const doc = getDocument(params.textDocument.uri); + if (!doc) return null; + + if (pickBackend(doc) === 'json') { + // Offers “Add $schema reference” quick fix whenever the root object lacks $schema. + return await structured.codeActionsJson(doc, params); + } + if (pickBackend(doc) === 'yaml') { + return await structured.codeActionsYaml(doc, params); + } + return null; +}); + +connection.onDefinition(async (params, token) => { + if (token.isCancellationRequested) return null; + const doc = getDocument(params.textDocument.uri); + if (!doc) return null; + return pickBackend(doc) === 'json' + ? structured.definitionJson(doc, params.position) // navigates $ref targets, component refs, etc. + : structured.definitionYaml(doc, params.position); +}); +``` + +- **Diagnostics events** still flow through `validateDocument`, but now the handlers above can surface follow-up quick fixes (e.g., add a `$schema`, insert missing YAML keys) tied to the same document. +- **Auto-fix suggestions** leverage the YAML service’s built-in `getCodeAction` plus the JSON helper’s custom fix builder so editors can surface actionable “Quick Fix…” entries. +- **Hover context** already goes through the JSON/YAML services, so schema descriptions and enum docs populate tooltips automatically. +- **Definition requests** reuse `findDefinition` / `doDefinition`, which means `$ref` values in JSON or anchors/aliases in YAML jump straight to their targets. + +## Testing the Server + +1. Use the `vscode-languageclient` sample extension to spin up the server and ensure `.json` and `.yaml` each get the expected diagnostics. +2. Add fixtures under `examples/fixtures/` (one per file type) and run automated LSP probes via `vscode-languageserver/node`’s `createConnection` in tests. +3. Validate schema fetching offline by stubbing the request service. + +## Best Practices Checklist (Apply All of Them) + +1. **Single source of truth for documents** – always read file content through Volar’s `server.documents` API so completions, hovers, diagnostics, and code actions see the same in-memory snapshot. +2. **Cache aggressively** – schema downloads, JSON document parses, and YAML ASTs should be re-used whenever possible. The sample’s `cacheSchemas` + `getJsonDocument` are patterns to copy. +3. **Respect cancellation tokens everywhere** – every `connection.on*` handler should short-circuit when `token.isCancellationRequested` is true, even after `await`. +4. **Debounce diagnostics** – never bombard the client with validation spam. Use the `queueValidation` helper in the sample as a template for other expensive tasks. +5. **Version-gate publishDiagnostics** – after running async work, re-fetch `server.documents.get()` and compare `version` before calling `sendDiagnostics`. +6. **Clear diagnostics on close** – send `[]` when a document is closed so stale warnings vanish from the Problems panel. +7. **Implement workspace diagnostics** – if the client supports `workspace/diagnostic`, return a `WorkspaceFullDocumentDiagnosticReport` for each document; otherwise rely on standard document-level `textDocument/publishDiagnostics`. +8. **Single queue for configuration** – keep a shared `applyConfiguration` function that reconfigures your JSON/YAML helpers, requests a capability refresh, and re-validates open documents. Guard it with throttling if your editor emits frequent configuration updates. +9. **Expose strongly typed settings** – the `JsonYamlConfiguration` interface keeps JSON/YAML configuration honest; use it end-to-end so TypeScript catches mismatched schema settings. +10. **Honor schema-relative resolution** – combine `server.workspaceFolders` with `Utils.joinPath` so workspace-relative schema references resolve consistently between CLI, VS Code, and other editors. + +### Diagnostics Lifecycle + +- **Collect once, reuse everywhere** – the `collectDiagnostics` helper powers both `publishDiagnostics` and the workspace handler, so the behavior is identical regardless of how the client asks for diagnostics. +- **Workspace diagnostics** – always return `WorkspaceDocumentDiagnosticReport` objects with `kind: DocumentDiagnosticReportKind.Full` (or `Unchanged` if you implement result IDs). Include `version` so incremental clients know which snapshot produced the report. +- **Auto-fixes** – pair every diagnostic family with at least one actionable code action. The sample shows how to generate a “Add $schema” quick fix for JSON while delegating YAML fixes to the upstream service. + +### Hover/Completion Detail + +- Let schema-driven services provide the content, but normalize the results if needed (e.g., strip fenced code blocks, deduplicate markdown). Volar encourages leaving formatting to the service so you get free upgrades as those services improve. +- Remember to include trigger characters that match your schema’s syntax (`"`, `<`, `/`, etc.) so completions fire exactly when the client expects them. + +### Definitions & `$ref` Intellisense + +- Forward `textDocument/definition` to both JSON and YAML services—those implementations already understand `$ref`, component references, and YAML anchors. +- Always return `DefinitionLink[]` rather than raw `Location[]`, so editors that support peek views can highlight the exact range of the definition. + +### Code Actions + Quick Fixes + +- Respect the client’s `context.only` filter when building actions. The sample JSON quick fix bails out unless `CodeActionKind.QuickFix` is requested. +- Provide descriptive titles that match the editor UX. “Add $schema reference” reads better than “Insert snippet”. + +### Schema Strategy + +- Treat schema URIs the same way the CLI would: handle `http(s)://`, `file://`, and workspace-relative paths. The `resolveWorkspaceUri` callback plus `normalizeUri` implementation show how to cover every case. +- Expose per-folder overrides with globbing (e.g., `config/*.json`) and keep defaults in `volarJsonYaml.json.defaultSchemaUri`. + +### Testing & Observability + +- Add an integration test harness that spins up the LSP server with `vscode-languageserver/node` and issues representative LSP requests (hover, completion, diagnostics, workspace/diagnostic). Compare the responses to snapshots. +- Log schema fetches and large diagnostics only when `process.env.VOLAR_DIAGNOSTICS_DEBUG` (or similar) is enabled to avoid spamming the client in normal operation. +- Implement `connection.telemetry.logEvent` for significant events (schema download failures, YAML validation errors) so consuming editors can surface them in their telemetry pipelines if desired. + +### Performance Knobs to Keep Handy + +- `VALIDATION_DELAY` – tune this constant per project; increase it for massive monorepos, decrease it for tiny repos. +- `pendingValidation` map – always cancel old timers when new edits arrive to avoid stacking multiple validations for the same document. +- `applyConfiguration` – wrap the internals in a try/catch that logs configuration errors and falls back to the previous known-good settings so a malformed config doesn’t brick the server mid-session. + +## Operational Considerations + +- **Performance**: JSON and YAML services are CPU-light compared to Vue. Keep them stateless and reuse schema caches to avoid blocking Volar’s TypeScript workers. +- **Telemetry**: The YAML service expects a telemetry sink; provide a no-op logger or hook into your analytics pipeline. +- **Take Over Mode**: If your editor uses Take Over Mode (Volar replacing the default TS server), ensure the JSON/YAML portions do not register duplicate document selectors. +- **Schema Distribution**: For enterprise deployments, serve schemas from a local file share or CDN and pass that base URL through `initializationOptions`. + +## Packaging & Distribution + +1. **Editor extension** – bundle the server script with a VS Code/Neovim/Sublime extension and launch it over stdio (`node dist/server.js`). The sample project’s `npm run build` output is ready for this flow. +2. **Single binary** – tools like `pkg`, `nexe`, or `esbuild --platform=node` can squash the server (and schemas) into an executable for locked-down environments. Keep schema fetching pluggable so air-gapped installs can point to `file://` URIs. +3. **Hosted worker** – for web editors (Monaco, CodeSandbox) run the server logic inside a worker using `@volar/monaco` plus the same JSON/YAML services, forwarding messages over `postMessage`. +4. **CLI utility** – expose a CLI command (e.g., `volar-json-yaml check path/to/workspace`) that spins up the same JSON/YAML helpers without the LSP transport for CI validation; reuse the shared connection setup from the sample. + +Whichever path you choose, keep the example project as a reference implementation so contributors can run `npm install && npm run dev` to replicate issues or experiment with new plugins. + +## Next Steps + +1. Package the server with `pkg`/`nexe` or ship it via an editor extension. +2. Layer additional `volar-service-*` plugins (e.g., Tailwind, Prettier) as needed. +3. Add integration tests that open mixed-workspace folders to confirm routing stays correct. + +With this pattern you can keep leveraging Volar’s Vue expertise while borrowing the battle-tested JSON/YAML implementations that already exist—no need to reinvent highlight/completion logic for those file types. diff --git a/docs/cli-integration.md b/docs/cli-integration.md new file mode 100644 index 0000000..6821bb6 --- /dev/null +++ b/docs/cli-integration.md @@ -0,0 +1,153 @@ +# CLI Integration & Headless Workflows + +> [Docs Index](README.md) • [Repo README](../README.md) • [Workspace Diagnostics](workspace-diagnostics.md) • [Testing & CI](testing-and-ci.md) • [LSP Benchmarking](lsp-benchmarking.md) + +Language intelligence isn’t limited to editors—teams often run Volar-powered checks in CI, pre-commit hooks, or bespoke tooling. This guide shows how to build CLI utilities (à la `vue-tsc`) that reuse your Volar language service for headless diagnostics, formatting, or codegen tasks. + +## Approaches + +| Approach | Pros | Cons | +| --- | --- | --- | +| Reuse `vue-tsc` | Fast to adopt, maintained by Vue team | Limited to diagnostics; less customizable | +| Custom CLI (Node) | Full control, integrate custom plugins/services | Must manage FS, config, output formatting | +| LSP client CLI | Reuse existing LSP server, speak JSON-RPC | More setup (start server, send requests) | + +## Building a Custom CLI + +### 1. Shared Language Service Factory + +Factor your language service creation into a reusable module (used by LSP + CLI): + +```ts +export function createVolarService(tsconfigPath: string, config: MyConfig) { + const tsProject = createTypeScriptProject(tsconfigPath); + const core = createLanguageCore({ plugins: [...], ts: tsProject.typescript }); + return createLanguageService(core, [volarServiceCss(), customService(config)], env, project); +} +``` + +### 2. CLI Entrypoint + +```ts +#!/usr/bin/env node +import { URI } from 'vscode-uri'; +import glob from 'fast-glob'; +import chalk from 'chalk'; +import { createVolarService } from './service'; + +async function main() { + const service = createVolarService(process.argv[2] ?? 'tsconfig.json', loadConfig()); + const files = await glob(['src/**/*.vue', 'src/**/*.ts']); + + let hasErrors = false; + for (const file of files) { + const uri = URI.file(file).toString(); + const diagnostics = await service.getDiagnostics(uri); + diagnostics.forEach((diag) => { + hasErrors = hasErrors || diag.severity === 1; + console.log(formatDiagnostic(file, diag)); + }); + } + + process.exit(hasErrors ? 1 : 0); +} +``` + +### 3. Formatting Output + +Use a standard format (VS Code-style) so editors/CI parse easily: + +```ts +function formatDiagnostic(file: string, diag: Diagnostic) { + const range = `${diag.range.start.line + 1}:${diag.range.start.character + 1}`; + const severity = ['Error', 'Warning', 'Information', 'Hint'][diag.severity ?? 0]; + return `${chalk.gray(file)}:${range} - ${severity} ${diag.message} (${diag.source})`; +} +``` + +### 4. JSON Output (Optional) + +Provide `--json` flag to emit machine-readable diagnostics: + +```ts +if (argv.json) { + console.log(JSON.stringify({ uri, diagnostics }, null, 2)); +} +``` + +## Leveraging `workspace/diagnostic` + +Instead of looping per file, reuse the workspace diagnostics pipeline: + +```ts +const report = await service.context.project.context.languageService.getWorkspaceDiagnostics(); +report.forEach((docReport) => { + docReport.items.forEach((diag) => { + console.log(formatDiagnostic(URI.parse(docReport.uri).fsPath, diag)); + }); +}); +``` + +This ensures CLI output matches editor diagnostics exactly. + +## Integrating with CI + +1. Add a script (`volar-check`) that runs your CLI. +2. Wire it into CI workflows: + +```yaml +- run: npm run volar-check -- --json > artifacts/volar.json +- uses: actions/upload-artifact@v4 + with: + name: volar-diagnostics + path: artifacts/volar.json +``` + +3. Fail the pipeline when exit code ≠ 0. + +## Pre-commit Hooks + +Use `lint-staged` or `husky` to run targeted diagnostics: + +```json +{ + "lint-staged": { + "*.{vue,ts,js}": "volar-check --files" + } +} +``` + +Implement `--files` flag to accept a comma-separated list and restrict diagnostics. + +## Formatting & Code Actions + +You can expose formatting or quick fixes via CLI: + +```ts +const edits = await service.getDocumentFormattingEdits(uri, { tabSize: 2, insertSpaces: true }, undefined); +applyEditsToFile(fsPath, edits); +``` + +For code actions, use `service.getCodeActions` and apply `WorkspaceEdit`. + +## Logging & Telemetry + +- Print progress (“Analyzing 123 files…”) with timestamps. +- Collect metrics (time per file, total diagnostics) to feed into benchmarking dashboards. + +## Packaging & Distribution + +- Bundle with `pkg`/`nexe` if users lack Node. +- Provide `npx` entry for quick runs (`npx volar-check`). +- Document environment variables (`VOLAR_TRACE`, `VOLAR_CONFIG_PATH`) to customize behavior. + +## Best Practices Checklist + +- [ ] Shared service factory reused by both LSP and CLI. +- [ ] Configurable input (`tsconfig`, glob patterns, file list). +- [ ] Structured output (text + optional JSON). +- [ ] Proper exit codes (0 = success, 1 = errors, 2 = config failure). +- [ ] Support for ignoring warnings (e.g., `--max-warnings`). +- [ ] Telemetry/logging toggles for verbose debugging. + +By bringing Volar’s language service into CLI form, you align editor diagnostics with automated checks, prevent regressions before they hit production, and empower teams to build custom workflows (codemods, linting, documentation pipelines) on top of the same intelligence. diff --git a/docs/configuration-and-projects.md b/docs/configuration-and-projects.md new file mode 100644 index 0000000..e5cc0ce --- /dev/null +++ b/docs/configuration-and-projects.md @@ -0,0 +1,190 @@ +# Advanced Configuration & Project Shapes + +> [Docs Index](README.md) • [Repo README](../README.md) • [Getting Started](getting-started.md) • [Performance Guide](performance-and-debugging.md) + +Volar can power everything from tiny single-file experiments to massive monorepos with multiple editors attached. This guide documents every configuration option, project topology, and editor integration knob so you can tune Volar for any workspace. + +## Configuration Surfaces + +| Surface | Who controls it | Purpose | +| --- | --- | --- | +| `vueCompilerOptions` in `tsconfig.json` / `jsconfig.json` | Project authors | Vue-specific compiler tweaks (reactivity transform, macros, experimental features). | +| Volar client settings (VS Code “Volar” section, Neovim config, etc.) | End users | Editor integration options (Take Over Mode, diagnostic toggles, traces). | +| `server.configurations` (`volarJsonYaml` in our example) | Language server authors | Custom feature flags for your own plugins/services. | +| CLI flags/environment variables | Tooling authors | Overrides for headless usage (e.g., `VOLAR_TRACE=protocol` for logging). | + +### Volar Client Settings (VS Code) + +Common keys in `settings.json`: + +```json +{ + "volar.takeOverMode.enabled": true, + "volar.tsPlugin": true, + "volar.vueserver.log": "verbose", + "volar.diagnostics.onChange": true, + "volar.autoCompleteRefs": true +} +``` + +- `takeOverMode.enabled`: Replace VS Code’s TypeScript LS with Volar’s integrated server for `.ts`/`.js` files (required for advanced template-inferred types). When true, ensure only one TS server runs to avoid duplicate diagnostics. +- `tsPlugin`: Enables Volar’s TypeScript plugin so `tsserver` understands `.vue`. +- `diagnostics.onChange`: Live diagnostics while typing vs on save. +- `vueserver.log`: `off | error | warn | info | verbose` – use `verbose` for deep debugging. + +### `vueCompilerOptions` + +`tsconfig.json` / `jsconfig.json` supports: + +```json +{ + "compilerOptions": { /* TS options */ }, + "vueCompilerOptions": { + "target": 3, // Vue compiler target + "plugins": ["@vue-macros/volar"], + "experimentalCompatMode": 2, + "data": { + "useDefineModel": true + } + } +} +``` + +Consult the official `@vue/language-core` docs for every `vueCompilerOptions` flag; key ones include: + +- `target`: Vue version (2, 3, 3.3, etc.) – affects template compilation. +- `plugins`: list of compiler plugins (macros, transform experiments). +- `experimentalCompatMode`: compatibility with legacy APIs. +- `data.useDefineModel`: toggles `