前置き
[ソースコード](/articles/vs-code ソースコード简析/#articleHeader4) から見ると、VSCode 本体は単なる Editor です(コア部分は Web 環境で独立して実行可能で、Monaco と呼ばれます)。言語特性関連の機能は一切提供していません。例えば:
-
構文サポート:構文検証、ハイライト、フォーマット、Lint 検査など
-
編集体験:定義へジャンプ、スマートヒント、自動補完、参照検索、変数リネームなど
これらすべてなく、すべてプラグインによって提供されており、JS に対するサポートも同様です
一.内蔵プラグイン
VS Code 内蔵プラグインの中で、JavaScript に関連するものは vscode/extensions/javascript/ のみで、しかも純粋な言語サポート型プラグインです:
"contributes": {
// 言語 id
"languages": [],
// 構文
"grammars": [],
// コードスニペット
"snippets": [],
// 言語関連設定ファイル検証ルール及びヒント
"jsonValidation": []
}
P.S.jsonValidation の作用については、Json Schema with VS Code を参照
一堆の設定ファイルでは明らかに定義へジャンプなどの強力な機能を提供できないため、さらに 2 つの TypeScript 関連プラグインがあります:
-
typescript-basics:
javascriptプラグインに類似し、TS 言語構文サポートを提供 -
typescript-language-features:言語特性関連の高度なサポートを提供。ジャンプ、宣言/参照検索、補完ヒント、outline/breadcrumb などコード意味に関わる高度な機能
その中で typescript-language-features は VS Code が JS/TS(および JSX/TSX)コードの意味を理解し、定義へジャンプなどの機能をサポートできる鍵です:
"activationEvents": [
"onLanguage:javascript",
"onLanguage:javascriptreact",
"onLanguage:typescript",
"onLanguage:typescriptreact",
"onLanguage:jsx-tags",
"onLanguage:jsonc"
]
二.typescript-language-features
構造
./src
├── commands.ts # TS 関連カスタム command
├── extension.ts # プラグインエントリー
├── features # 各種言語特性、ハイライト、折りたたみ、定義へジャンプなど
├── languageProvider.ts # VSCode 機能エントリーに対接
├── protocol.const.ts # TS 言語要素定数
├── protocol.d.ts # tsserver インターフェースプロトコル
├── server.ts # tsserver プロセスを管理
├── test
├── typeScriptServiceClientHost.ts # Client 管理を担当
├── typescriptService.ts # Client インターフェース形態を定義
├── typescriptServiceClient.ts # Client 具体実装
├── typings
└── utils
P.S.参照ソースコードバージョン v1.28.2、最新のソースコード ディレクトリ構造はすでに変わりましたが、思路は同じです
その中で最も重要な 3 部分は features、server と typescriptServiceClient です:
-
Feature:VSCode に対接し、ハイライト、折りたたみ、ジャンプなど Editor 機能エントリーに具体実装を提供
-
Server:TSServer に接入し、JS コード意味を理解する能力を獲得し、意味関連の機能にデータ源を提供
-
Client:Server と対話(既定のインターフェースプロトコルに従い)、リクエストを発起し、応答データを受信
起動フロー
具体的には、該プラグインがアクティブになる時に主にこの 3 つのことが発生しました:
-
すべてのプラグインが追加した
TypeScriptServerPluginを見つけ出し、Client ready 後に登録 -
TypeScriptServiceClientHost を作成
-
TypeScriptServiceClientを作成し、直ちに TSServer プロセスを作成 -
LanguageProviderを作成し、VSCode 機能エントリーに対接することを担当 -
TSServer ready 後、VSCode と TSServer の接続を開始
-
LanguageProvider が VSCode 各項機能を登録。例えば
vscode.languages.registerCompletionItemProviderで補完ヒントに対接 -
直ちに診断(構文検証、タイプ検査など)をトリガー
その中で比較的面白いのは TypeScriptServerPlugin の登録、TSServer の作成、および Client と Server 間の通信です
TypeScriptServerPlugin の登録
TS v2.3.0+ のみ外部 Plugin を登録し、コマンドラインパラメータを通じて传入:
if (apiVersion.gte(API.v230)) {
const pluginPaths = this._pluginPathsProvider.getPluginPaths();
if (plugins.length) {
args.push('--globalPlugins', plugins.map(x => x.name).join(','));
if (currentVersion.path === this._versionProvider.defaultVersion.path) {
pluginPaths.push(...plugins.map(x => x.path));
}
}
if (pluginPaths.length !== 0) {
args.push('--pluginProbeLocations', pluginPaths.join(','));
}
}
TSServer plugin API は TS v2.3.0 で推出されたためです:
TypeScript 2.3 officially makes a language server plugin API available. This API allows plugins to augment the regular editing experience that TypeScript already delivers. What all of this means is that you can get an enhanced editing experience for many different workloads.
つまり、VSCode の宇宙級 JS 編集体験は、すべて下層の TypeScript による恩恵です:
One of TypeScript's goals is to deliver a state-of-the-art editing experience to the JavaScript world.
(Announcing TypeScript 2.3 から引用)
P.S.低バージョン TS の状況が存在する理由は、VSCode が外部 TS の使用を許可 しているためです(内蔵のはもちろん高バージョン)
TSServer の作成
TSServer は単独の Node プロセスで実行:
public spawn(
version: TypeScriptVersion,
configuration: TypeScriptServiceConfiguration,
pluginManager: PluginManager
): TypeScriptServer {
const apiVersion = version.version || API.defaultVersion;
const { args, cancellationPipeName, tsServerLogFile } = this.getTsServerArgs(configuration, version, pluginManager);
// fork 一个 tsserver プロセス
// 内蔵の TSServer は extensions/node_modules/typescript/lib/tsserver.js に位置
const childProcess = electron.fork(version.tsServerPath, args, this.getForkOptions());
return new TypeScriptServer(childProcess, tsServerLogFile, cancellationPipeName, this._logger, this._telemetryReporter, this._tracer);
}
その中で、electron.fork はネイティブ fork() のカプセル化で、Electron API アクセスを制限:
import cp = require('child_process');
export function fork(modulePath, args, options): cp.ChildProcess {
const newEnv = generatePatchedEnv(process.env, modulePath);
return cp.fork(modulePath, args, {
silent: true,
cwd: options.cwd,
env: newEnv,
execArgv: options.execArgv
});
}
ネイティブ cp.fork() との違いは環境変数の Patch にあります:
function generatePatchedEnv(env: any, modulePath: string): any {
const newEnv = Object.assign({}, env);
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
// Ensure we always have a PATH set
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
return newEnv;
}
その中で ELECTRON_RUN_AS_NODE はElectron API アクセスを制限するために使用:
ELECTRON_RUN_AS_NODE: Starts the process as a normal Node.js process.
主にUI カスタマイズ制限と安全性を考慮してのもので、否则第三者 VSCode プラグインは [typescriptServerPlugins 拡張点](/articles/typescriptserverplugin-vscode プラグイン開発ノート 3/#articleHeader8) を通じて Electron API にアクセスし、UI を改竄できます
P.S.通常プラグインが所在する Node プロセスにもこの制限があります。詳細は [四。プロセスモデル](/articles/api 注入機制及プラグイン起動フロー-vscode プラグイン開発ノート 2/#articleHeader8) を参照
Client と Server 通信
TSServer が子プロセスで実行しているため、API 呼び出しにはクロスプロセスの問題が存在します。したがって TSServer は一組の JSON プロトコル protocol.d.ts を定義し、主に API 名およびメッセージフォーマットを含みます:
// コマンド
const enum CommandTypes {
Definition = "definition",
Format = "format",
References = "references",
// ...
}
// 基本メッセージフォーマット
interface Message {
seq: number;
type: "request" | "response" | "event";
}
// リクエストメッセージフォーマット
interface Request extends Message {
type: "request";
command: string;
arguments?: any;
}
// 応答メッセージフォーマット
interface Response extends Message {
type: "response";
request_seq: number;
success: boolean;
command: string;
message?: string;
body?: any;
metadata?: unknown;
}
標準入力/出力を通じてメッセージを受信/発信します。詳細は Message format を参照:
tsserver listens on stdin and writes messages back to stdout.
P.S.プロセス間通信に関する詳細情報は、[1.stdin/stdout を通じて json を传递](/articles/nodejs プロセス間通信/#articleHeader8) を参照
三.TSServer
TSServer は TS と密接不可分で、図の通り:

その中で、最も重要な 3 ブロックは:
-
コンパイラコア(Core TypeScript Compiler)
完全なコンパイラを実装。詞法分析、タイプ検証、構文分析、コード生成などを含む
-
エディタ面向の言語サービス(Language Service)
文補完、API ヒント、コードフォーマット、ファイル内ジャンプ、配色、ブレーク位置検証などを提供。还有一些よりシーン化された API。例えば増分コンパイル。詳細は Standalone Server (tsserver) を参照
-
独立コンパイラ(Standalone Compiler (
tsc))CLI ツール。入力ファイルに対してコンパイル変換を行い、ファイルに出力
TSServer は独立のプロセスサービス(Standalone Server (tsserver))として、Compiler と Service の上に一層のカプセル化を建立し、JSON プロトコルの形式でインターフェースを暴露します。詳細は Using the Language Service API を参照
したがって、TSServer は tsc の完全な能力を持ち、エディタ面向の言語サービスサポートもあり、エディタバックグラウンドプロセスなどのアプリケーションシーンに非常に適しています
四.まとめ
至此、すべてが明了になりました。最も重要な意味分析能力及びデータサポートは下層 TSServer からです。したがって、定義へジャンプの大まかなフローはこの通り:
-
ユーザーが VSCode インターフェースで Go to Definition をクリック
-
内蔵プラグイン
typescript-language-featuresが登録した対応 Feature 実装をトリガー -
Feature が Client を通じて TSServer へのリクエストを発起
-
TSServer が関連 AST を検索して Definitions を見つけ出し、既定プロトコルフォーマットに従って出力
-
Client が応答を受け取り、データを取り出し、Feature に传递
-
Feature が原始データを VSCode 表示に必要なフォーマットに変換
-
VSCode がデータを取得し、カーソルを Editor 指定位置に移動。ポン、ジャンプしました
P.S.VSCode 中の其它 JS 意味関連の機能もこれと類似し、すべて TSServer がサポートを提供
コメントはまだありません