Files
volar-docs/docs/volar-kit-and-editor.md
2025-11-09 22:22:52 -06:00

199 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# `@volar/kit`, `@volar/editor`, and `@volar/monaco` Deep Dive
> [Docs Index](README.md) • [Repo README](../README.md) • [Plugin Authoring](plugin-authoring.md) • [Configuration & Projects](configuration-and-projects.md) • [Alternative Editors](editor-integration-alt.md)
These companion packages encapsulate Volars runtime wiring so you can spin up language servers, custom editors, or browser-based playgrounds without rebuilding the infrastructure each time. This guide documents every API surface, lifecycle hook, and best practice for:
- `@volar/kit` batteries-included Node bootstrapper for LSP servers (watchers, configuration, project factories).
- `@volar/editor` minimal abstractions for embedding Volar in non-LSP editors (custom document/connection implementations).
- `@volar/monaco` drop-in helpers for wiring Volars language service into Monaco-based experiences (VS Code Web, StackBlitz, custom playgrounds).
## 1. `@volar/kit`
### Purpose
`@volar/kit` handles the repetitive work of:
- Scaffolding an LSP server with sensible defaults.
- Loading `tsconfig` projects (optionally with project references).
- Watching the file system for changes and triggering reloads/re-diagnostics.
- Exposing configuration hooks and restart commands.
### Quickstart
```ts
import { createLanguageServer } from '@volar/kit';
import * as ts from 'typescript';
import { createVueProject } from '@vue/language-core';
const server = createLanguageServer({
ts,
loadProjects(rootFolders) {
return rootFolders.map((folder) =>
createVueProject(ts, {
rootUri: folder,
tsconfig: `${folder.fsPath}/tsconfig.json`,
}),
);
},
});
server.start();
```
### Lifecycle Hooks
| Hook | Signature | Use |
| --- | --- | --- |
| `onCreate` | `(server) => void` | Add custom commands, middleware, telemetry. |
| `loadProjects` | `(folders: URI[]) => Project[]` | Return an array of project instances; each project exposes `getLanguageService` and TS integration hooks. |
| `watchFilePatterns` | `string[]` | Additional globs for the internal FS watcher. |
| `resolveConfig` | `(connection) => Promise<Config>` | Async configuration loader; automatically re-applied on `workspace/didChangeConfiguration`. |
### Configuration Options
| Option | Description |
| --- | --- |
| `ts` | TypeScript module (required). |
| `tsLocalized` | Map of localized diagnostic messages (optional). |
| `projectFeatures.workspaceDiagnostics` | Enable workspace diagnostics handling (`true` by default when the client supports it). |
| `projectFeatures.interFileDependencies` | When `true`, diagnostics consider cross-file dependencies; `false` means per-file only. |
| `projectFeatures.strict` | Equivalent to `vue-tsc --strict`; surfaces additional diagnostics. |
### Best Practices
1. **Single source for TypeScript** pass the same `ts` object everywhere (`@volar/kit`, plugin code, CLI) to avoid version mismatches.
2. **Debounce reloads** if you implement custom watchers, wrap `project.reload()` calls with a debounce to prevent thrashing.
3. **Expose restart commands** `server.commands.register('volar.restart', () => server.restart())` so users can force reload from the editor.
## 2. `@volar/editor`
### Purpose
Designed for editors that dont use LSP (custom IDEs, in-house editors). It provides building blocks for:
- Managing documents (`TextDocument`, `SnapshotDocument`).
- Routing requests to the language service without JSON-RPC.
- Handling workspace edits, diagnostics, and commands manually.
### Key APIs
- `createEditorConnection(languageService)` returns an object with methods mirroring LSP requests (`doValidation`, `doComplete`, etc.) but callable directly.
- `SnapshotDocument` text document implementation that tracks incremental updates and script snapshots.
- `TextDocuments` event emitter (`onDidOpen`, `onDidChangeContent`, etc.) you can wire to your editors change notifications.
### Usage Pattern
```ts
import { createEditorConnection } from '@volar/editor';
import { createLanguageService } from '@volar/language-service';
const language = createLanguage(...);
const service = createLanguageService(language, plugins, env, project);
const editor = createEditorConnection(service);
editor.documents.onDidOpen(({ document }) => {
const diagnostics = editor.languageFeatures.getDiagnostics(document.uri);
applyDiagnostics(document.uri, diagnostics);
});
editor.documents.onDidChangeContent(({ document }) => {
const completions = editor.languageFeatures.getCompletionItems(document.uri, position);
showCompletions(completions);
});
```
### When to Use
- Custom Electron apps that embed Monaco but want offline/deterministic routing.
- In-house IDEs (e.g., game studios) that prefer direct API calls over LSP.
## 3. `@volar/monaco`
### Purpose
Wraps Volars language service to integrate seamlessly with Monaco editor (used in VS Code Web, StackBlitz, CodeSandbox, Vue SFC Playground).
### Setup Steps
1. **Worker Registration**
```ts
import * as monaco from 'monaco-editor';
import { setupLanguageServiceForMonaco } from '@volar/monaco';
setupLanguageServiceForMonaco(monaco, {
ts: () => import('typescript'),
service: () => import('your-volar-service-entry'),
});
```
- Provide async loaders for TypeScript and your language service bundle.
- Bundlers like Vite or Webpack should treat the worker entry as `worker` output.
2. **Virtual File System**
Implement a file host used by the language service:
```ts
const files = new Map<string, string>();
const fsHost = {
readFile(uri: string) {
return files.get(uri);
},
writeFile(uri: string, content: string) {
files.set(uri, content);
},
getDirectoryEntries(uri: string) {
// return child URIs
},
};
```
3. **Synchronizing Monaco Models**
Listen to Monaco model events and forward them to Volar:
```ts
monaco.editor.onDidCreateModel((model) => {
const uri = model.uri.toString();
fsHost.writeFile(uri, model.getValue());
model.onDidChangeContent(() => {
fsHost.writeFile(uri, model.getValue());
languageService.syncFile(uri, model.getValue());
});
});
```
### Bundling Tips
- Use dynamic imports to lazy-load the worker bundle.
- Ensure TypeScript is available in the worker scope (via `ts = await loadTS()`).
- Provide polyfills for `fetch`/`FileReader` in browsers that lack them.
### Features Enabled
- IntelliSense for `.vue`/custom files.
- Diagnostics inline in Monaco (markers API).
- Formatting via Monacos `languages.registerDocumentFormattingEditProvider`.
## Combining the Pieces
| Scenario | Recommended Package |
| --- | --- |
| Node-based LSP server | `@volar/kit` |
| Custom desktop editor | `@volar/editor` |
| Browser playground | `@volar/monaco` |
| Hybrid (Node server + Monaco client) | `@volar/kit` on the backend + `@volar/monaco` on the frontend |
## Cross-Cutting Best Practices
1. **Shared Language Service Factory** keep your `createLanguageService` logic in one module and import it in both LSP (+@volar/kit) and Monaco contexts to avoid divergence.
2. **File System Abstraction** design a file host interface that works for Node (fs/promises) and browser (in-memory map or IndexedDB).
3. **Configuration Sync** if you expose custom settings, ensure both LSP + Monaco flows consume the same configuration object (`applyConfiguration(config)`).
4. **Version Alignment** lock `@volar/*` package versions across server and client to prevent subtle mismatches.
5. **Telemetry Parity** forward the same telemetry/logging events from both LSP and Monaco contexts so you can compare editor behaviors.
By understanding the responsibilities of `@volar/kit`, `@volar/editor`, and `@volar/monaco`, you can implement Volar-powered experiences everywhere—from desktop editors to in-browser playgrounds—without reimplementing the plumbing each time.