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

7.7 KiB
Raw Permalink Blame History

@volar/kit, @volar/editor, and @volar/monaco Deep Dive

Docs IndexRepo READMEPlugin AuthoringConfiguration & ProjectsAlternative Editors

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

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

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
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.
  1. Virtual File System

Implement a file host used by the language service:

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
  },
};
  1. Synchronizing Monaco Models

Listen to Monaco model events and forward them to Volar:

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.