引言
当你在 VS Code 里敲击代码,看到类型错误实时变红、悬停弹出类型信息、Tab 补全方法——这背后不是编辑器的功劳,而是一个独立的进程在替你「读代码、算类型、报诊断」:TS Server(tsserver)。它是 TypeScript 编译器能力面向编辑器场景的重新封装,也是 LSP(Language Server Protocol)生态里最成熟的语言服务器之一。理解它的架构与插件体系,是从「用 TS」走向「为 TS 生态做工具」的分水岭。
前置:/typescript-project-architecture-tsconfig/(tsconfig 与项目模型)、/typescript-advanced-types/(类型系统)。
目录
- 1. Language Service 是什么
- 2. TS Server 进程模型
- 3. LSP 协议与通信
- 4. 诊断与语义错误
- 5. 补全、悬停与签名帮助
- 6. 快速修复与重构
- 7. 自定义 TS 插件
- 8. 插件 API 深入
- 9. VS Code 扩展集成
- 10. 速查表与一句话记忆
- 延伸阅读
1. Language Service 是什么
TypeScript 编译器(tsc)与语言服务(Language Service)是同一类型系统的两个面孔:
| 场景 | tsc | Language Service |
|---|---|---|
| 任务 | 全量编译出产物 | 逐文件、增量地回答编辑器的查询 |
| 输入 | 整个项目 | 单个文件 + 编辑缓冲区 |
| 输出 | JS/d.ts/错误列表 | 诊断、补全、悬停、跳转定义 |
| 速度 | 全量(可增量) | 常驻内存、按需计算 |
| 调用方 | CI/构建 | 编辑器插件 |
Language Service 的核心价值是增量与交互:它持有整个程序(Program)的语义模型,编辑器每次按键只需告诉它「哪个文件哪一行改了什么」,它只重算受影响的文件——这就是为什么输入到报错延迟几乎感觉不到。
// 语言服务的最小用法(Node 端)
import * as ts from "typescript";
const service = ts.createLanguageService({
getScriptFileNames: () => ["a.ts"],
getScriptVersion: () => "1",
getScriptSnapshot: name => ts.ScriptSnapshot.fromString(source)
});
const diagnostics = service.getSemanticDiagnostics("a.ts");
2. TS Server 进程模型
tsserver 是语言服务的「可执行包装器」:一个常驻 Node 进程,通过 JSON 行协议(每行一个 JSON 请求/响应)与编辑器对话。
VS Code ──► tsserver 进程(常驻)
├── 每个项目一个「Project Service」
│ ├── Program(全量类型模型)
│ └── 文件缓存 / 增量更新
└── 请求队列(补全、诊断、跳转…)
关键设计:
- 项目服务(Project Service):tsserver 按「打开的 tsconfig」划分项目,同一项目内共享 Program;
- 双项目模式:编辑器文件属于「默认项目」或「明确项目」,
useWorkspaceSettings决定走哪个; - 日志协议:
--logVerbosity可输出请求日志,排查「为什么补全不出现」的第一手段。
3. LSP 协议与通信
LSP(Language Server Protocol) 是微软定义的、语言无关的「编辑器↔语言服务器」通信标准。TS Server 是 LSP 生态的祖先——它使用自有的 JSON 协议,而 typescript-language-server 等项目把 LSP 翻译成 tsserver 协议,让 TS 的智能能力也能供 Vim/Emacs/JetBrains 使用。
| LSP 能力 | 对应 tsserver 请求 |
|---|---|
textDocument/hover | hover |
textDocument/completion | completionInfo |
textDocument/publishDiagnostics | geterr(批量诊断) |
textDocument/definition | definition |
textDocument/codeAction | getCodeFixesAtPosition |
workspace/symbol | navto |
LSP 消息结构:
Content-Length: N\r\n\r\n{"jsonrpc":"2.0","method":...,"params":...}
理解 LSP 的意义:语言能力与编辑器解耦——一套语言服务可被所有编辑器复用,这也是 TS Server 之外、Rust 的 rust-analyzer 等百花齐放的原因。
4. 诊断与语义错误
编辑器里的红色波浪线来自三类诊断:
| 类型 | 来源 | 何时触发 |
|---|---|---|
| 语法诊断 | 解析器 | 键入即更新 |
| 语义诊断 | 类型检查 | 增量计算 |
| 声明文件诊断 | 检查 d.ts 的错误 | 文件变化时 |
// tsc 提供的诊断错误码分类
diagnostic.category // 0=error, 1=warning, 2=suggestion, 3=message
diagnostic.code // TS2304 等,映射到错误说明
diagnostic.relatedInformation // 关联位置(如类型不兼容的两个端点)
工程要点:
- 错误码是契约:你的工具(CI、注释
// @ts-expect-error、ESLint 规则)都依赖稳定的错误码; geterr批量:tsserver 的批量诊断请求带 file root,编辑器一次性拉取整个项目错误,避免逐个文件轮询;@ts-nocheckvs@ts-ignore:诊断层面的两个逃生门,语义不同(整文件 vs 单行)。
5. 补全、悬停与签名帮助
三个「被动查询」是编辑器体验的主力:
- 补全(completion):基于符号表的候选列表,包含:函数签名、类型注释、
/** JSDoc */文档、deprecated标记; - 悬停(hover):符号的类型 + 文档,
quickInfo请求返回; - 签名帮助(signatureHelp):当光标在函数调用括号内时,展示参数与重载列表。
// 给符号加 JSDoc,悬停与补全都会展示
/**
* 把值装箱成 Option<T>
* @example option.of(42).map(x => x * 2)
*/
export function of<T>(value: T): Option<T> { /* ... */ }
工程启示:良好 JSDoc 不只给人看——它直接提升语言服务生成的补全/悬停信息质量,等于「给类型系统写文档」。
6. 快速修复与重构
TS Server 内置一批代码修复(code fix)与重构(refactor),编辑器里表现为「灯泡图标」:
- 快速修复:为错误提供一键修法——
import缺失自动补 import、未使用变量删除、add missing 'await'等; - 重构动作:提取变量/函数/常量、重命名符号(跨文件、带预览)、转换为箭头函数、移动声明到新文件;
- 组织 imports:
organizeImports按 tsconfig 规则排序去重。
快速修复示例:
错误 TS2554:Expected 2 arguments, but got 1.
→ 修复:自动补全缺失实参 / 移除多余实参 / 改函数签名
对工具作者:这些动作都走 tsserver 的 getCodeFixesAtPosition / getApplicableRefactors API,可以在 CLI 工具里批量执行——「自动修 lint 错误」的底层就是它们。
7. 自定义 TS 插件
TypeScript 允许通过 tsPlugins 扩展语言服务能力——在 tsconfig 里声明,插件会在 tsserver 启动时被加载:
{
"compilerOptions": {
"plugins": [
{ "name": "my-ts-plugin", "languageService": true }
]
}
}
// 一个最小插件:包装语言服务、追加自定义补全
import * as ts from "typescript";
export = {
create(info: ts.server.PluginCreateInfo) {
const proxy: ts.LanguageService = Object.create(null);
for (const k of Object.keys(info.languageService) as (keyof ts.LanguageService)[]) {
proxy[k] = (...args: unknown[]) => (info.languageService as any)[k]!(...args);
}
proxy.getCompletionsAtPosition = (fileName, pos, options) => {
const prior = info.languageService.getCompletionsAtPosition(fileName, pos, options);
// ...往 prior 里注入你的候选
return prior;
};
return proxy;
}
};
典型用例:
- 为 CSS Modules 生成类型补全(
*.module.css→ 类名联合类型); - 自定义模板引擎的类型增强;
- 标记
deprecated之外的业务规则诊断(如「禁止使用某函数」)。
8. 插件 API 深入
PluginCreateInfo 是插件与宿主之间的「全部入口」:
| 成员 | 用途 |
|---|---|
languageService | 原始语言服务,所有标准能力的访问点 |
project | 当前项目(可查文件列表、compilerOptions) |
config | tsconfig plugins[]. 的配置对象 |
host | 语言服务宿主(脚本快照、文件读取) |
ts | 暴露的 TypeScript API(补全、类型工具) |
拦截模式:最常见的插件写法是「代理模式」——包一层 proxy 对象,改写感兴趣的方法(如 getSemanticDiagnostics、getCompletionsAtPosition),其余透传给原始服务。注意:插件运行在 tsserver 进程里,性能敏感——不要做阻塞式 IO,不要全量遍历符号表。
9. VS Code 扩展集成
要在 VS Code 里做「增强 TS 体验」的扩展,有两条路线:
| 路线 | 方式 | 适用 |
|---|---|---|
| tsPlugin | 声明到 tsconfig,只增强 TS 能力 | 类型补全、诊断增强 |
| VS Code 扩展 | onLanguage: typescript + 监听/命令 | UI、面板、更复杂的交互 |
// VS Code 扩展清单
{
"activationEvents": ["onLanguage:typescript"],
"main": "./out/extension.js"
}
import * as vscode from "vscode";
export function activate(ctx: vscode.ExtensionContext) {
vscode.languages.registerCompletionItemProvider(
{ language: "typescript", scheme: "file" },
{ provideCompletionItems: async (doc, pos) => { /* 自定义补全 */ } }
);
}
工程要点:扩展代码运行在 VS Code 主进程(而非 tsserver),两边的 API 完全不同——理解「哪个进程持有类型信息」是正确设计的分水岭。
10. 速查表与一句话记忆
| 概念 | 一句话解释 |
|---|---|
| Language Service | 编译器的交互式面孔:增量、按需、回答查询 |
| tsserver | 常驻进程,JSON 行协议,每项目一个 Project Service |
| LSP | 语言服务与编辑器的通用协议,TS 是它的祖先 |
| 诊断 | 语法(解析)+ 语义(类型)两级 |
| 补全/悬停 | 基于符号表 + JSDoc 的被动查询 |
| 快速修复/重构 | 错误驱动的代码修复动作(灯泡) |
| tsPlugin | 在 tsserver 内代理语言服务,注入自定义行为 |
一句话记忆:编辑器智能 = tsserver 常驻进程 + 语言服务 API(诊断/补全/修复)+ LSP 通信——要扩展它,就用 tsPlugin 在服务进程里包一层代理。
延伸阅读
- /typescript-project-architecture-tsconfig/ — 项目模型与 tsconfig 分层
- /typescript-advanced-types/ — 类型系统与语义诊断的基础
- /typescript-sdk-package-publishing/ — d.ts 工程与 JSDoc 质量
- /typescript-build-performance-optimization/ — 语言服务的性能调优
- 前端专题 — 编辑器与前端工具链
- Vite 专题 — 构建工具与语言服务的协作
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。