感謝支持ayqy我的訂閱號,每週義務推送1篇(only unique one)原創精品博文,話題包括但不限於前端、Node、Android、數學(WebGL)、語文(課外書讀後感)、英語(文檔翻譯)
若是以爲弱水三千,一瓢太少,能夠去 http://blog.ayqy.net 看個痛快 javascript
寫在前面
從源碼來看,VSCode主體只是個Editor(核心部分可在Web環境獨立運行,叫Monaco),並不提供任何語言特性相關的功能,好比:前端
語法支持:語法校驗、高亮、格式化、Lint檢查等等java
編輯體驗:跳轉到定義、智能提示、自動補全、查找引用、變量重命名等等node
這些通通沒有,都是由插件提供的,對JS的支持也是這樣react
一.內置插件
VS Code內置插件中,與JavaScript有關的只有一個vscode/extensions/javascript/,並且是個純粹的語言支持型插件:typescript
"contributes": { // 語言id "languages": [], // 語法 "grammars": [], // 代碼片斷 "snippets": [], // 語言相關配置文件校驗規則及提示 "jsonValidation": [] }
P.S.關於jsonValidation
的做用,見Json Schema with VS Codejson
一堆配置文件顯然提供不了跳轉定義之類的強力功能,所以,還有兩個TypeScript相關的插件:api
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擴展點訪問Electron API,篡改UI
P.S.普通插件所處的Node進程也有此限制,具體見四.進程模型
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
三.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提供支持
參考資料
Microsoft/vscode 1.28.2
Architectural Overview
本文分享自微信公衆號 - 前端向後(backward-fe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。