microsoft / microsoft/TypeScript

API usage patterns for complex editor extensions

未关闭
#63,800 56 条评论 110 个 reaction 已指派 1 人 在 GitHub 查看

@andrewbranch 已经在做这个了。

开始于 2026年2月18日。

主要语言
Go
星标
111k
派生
14.3k
平均合并
2 天 4 小时
30 天内合并 PR
132

描述

TypeScript 6.0 and earlier allowed loading third-party plugin code directly into TS Server, exposing language service and project system internals for proxying and patching. This was often used in coordination with editor extensions, sometimes in tandem with an additional custom language server, to provide language support for languages that embed or transform into TypeScript.

In the Go implementation, we can't dynamically link third-party code into the server process. Instead, we provide an IPC-based API and a TypeScript client library, currently under development, for communicating with the server. The API will expose most of the read-only features of today's `ts.Program` and `ts.TypeChecker` APIs; I want to use this issue to discuss and explore what other features the API needs in order to replace TS Server plugins.

## Case study: Vue

I'm going to present my very basic understanding of how Vue's language support integrates with TypeScript today. There are three components:

1. `@vue/typescript-plugin`, the TS Server plugin
2. `@vue/language-server`, an additional LSP server responsible mostly for serving requests in the template and CSS parts of `.vue` files
3. The VS Code extension that registers the TS plugin, spawns the Vue language server, and brokers communication between the two.

When the TS Server plugin initializes, it tells TypeScript to include the `.vue` extension in its projects and overrides TS Server's view of `.vue` file contents with valid TypeScript syntax. It then intercepts requests and responses for those files in order to map positions between locations in the original `.vue` file content and locations in the virtual TypeScript-only content. It also overrides module resolution to allow `import {} from "./MyComponent.vue"` to resolve, and `readDirectory` to include `.vue` files in path completions. Finally, it installs some custom request handlers that provide information from the TypeScript language service to `@vue/typescript-plugin`, proxied through the VS Code extension.

## Proposal

I'd like to get feedback on a system where, instead of making the TypeScript LSP server serve requests for non-TypeScript files directly, we let an auxiliary language server, through API usage, compute or proxy whatever information it needs from the TypeScript language service. Roughly how I imagine that looking:

- The Vue language server would tell the TypeScript API to open / keep open any projects for which it wants to include `.vue` files, and would "open" the virtual files, then continue to manage their state in a similar way in `textDocument/didChange` and `workspace/didChangeWatchedFiles` handlers.

```ts
const snapshot = await api.updateSnapshot({
openProject: projectTsconfigUri,
openFiles: projectVueFiles.map(file => ({
uri: `virtual://@vue/language-server/${file.basePath}`,
content: getServiceScriptContent(file),
scriptKind: ScriptKind.TS,
defaultProject: projectTsconfigUri,
version: file.version,
}))
})
```

- Language service handlers in the Vue language server could access these virtual files through `Program`/`TypeChecker` APIs for semantic analysis as well as initiate standard LSP requests for them.

```ts
connection.onCompletion(async (params) => {
const { uri, position } = params.textDocument;
using snapshot = await api.updateSnapshot();
const { project, checker } = await snapshot.getDefaultProjectForFile(uri);
const sourceFile = program.getSourceFile(uri);

const virtualFilePosition = mapToVirtualFilePosition(uri, position);
const node = getTokenAtPosition(sourceFile, virtualFilePosition);
const tsCompletions = await snapshot.lspRequest("textDocument/completion", {
textDocument: { uri },
position: virtualFilePosition,
});

if (shouldProvideSpecialVueCompletions(node)) {
const vueCompletions = await getVueCompletionsUsingSemanticAPIs(node, checker, params);
return [...tsCompletions, ...vueCompletions];
}
});
```

- For module resolution, we'd likely want to provide some kind of static mapping API that can be updated as needed—having callbacks into client code for module resolution is something we want to avoid. Whatever the format, it should cover module resolution, auto imports, and path completions—it seems silly that today's TS Server plugins can specify extra file extensions and external files but still have to patch `readDirectory` to get them to show up in path completions.

This approach shifts the responsibility and control for servicing LSP requests for non-TypeScript files to the language servers that actually own those files. With the exception of module resolution, most of what other language servers need from TypeScript falls pretty cleanly out of existing LSP functionality (opening and updating a virtual file is the same thing as opening an untitled file in an editor; we just need to add a project association) or existing/planned API functionality.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。