【CSDN 編者按】Visual Studio Code(以下簡稱 VS Code)是一個強大的工具,可惜很多人卻沒有找到它的正確打開方式。今天就教大家3個好用易學的小技巧,讓你的VS Code使用起來如行云流水!
原文鏈接:https://medium.com/fractions/3-visual-studio-code-tips-to-boost-your-workflow-b107ec573d75
聲明:本文為 CSDN 翻譯,轉載請注明來源。
以下為譯文:
只要你知道如何使用VS Code,它就是一個萬能的工具。
隨著時間的推移,VS Code變得越來越好,并添加了更多的特性。然而,這些特性通常都隱藏在VS Code的JSON設置中,大多數新手根本就無法找到。今天,我將與大家分享3個不同尋常的技巧,它們可以幫助大家提升開發效率。
配置文件又名dotfiles,是開發中不可或缺的一部分,因為現在已經不是2000年,沒有人再使用普通的HTML、CSS和JavaScript了。我們現在幾乎有了做任何事情的工具,有轉譯器、編譯器、綁定器、編譯器、美化器……幸運的是,我們可以根據項目的需要,用配置文件對它們進行配置。
然而,在根目錄中有幾十個配置文件會導致一團亂。盡管這些可定制的工具非常棒,但在配置它們一次之后,我從來沒有打開它們,除非在項目中有我無法預見的東西。那么,為什么我每次都要在這個混亂的文件夾中找到我的主文件夾呢?
幸運的是,VS Code有一個還在實驗階段的設置功能,叫做fileNesting。它允許開發者可視化地將文件嵌套到另一個文件中,并清除工作區。好在它不會打亂文件結構,而且所有的預配置工具都可以在沒有任何額外努力的情況下繼續工作。
對于這個項目,我將把我所有的配置文件放在package.json文件和README.md下的變更日志和許可證。
有了這個設置,我終于可以找到任何我第一眼想要的,如果我需要編輯任何配置文件,我可以展開像package.json的一個文件夾,并編輯它下面的文件。
對于這個技巧,你必須向setting.json中添加三個條目。按Ctrl或Cmd + Shift + P打開它,并寫入“settings.json”。然后將這些條目添加到末尾。
"explorer.experimental.fileNesting.enabled": true,
"explorer.experimental.fileNesting.expand": false,
"explorer.experimental.fileNesting.patterns": {
// Append as many as you want
// The keys are the parents and the values are the nested files.
"package.json": ".gitignore, .parcelrc, .prettierc ...",
"README.md": "CHANGELOG.md, LICENCE"
}
就是這樣!沒有更多的混亂的根,所有都容易查找。不要忘記查看并啟動我截屏的這個項目,以確認文件結構沒有改變。
不需要擴展
擴展太棒了!它們是VS Code強大的主要原因。由于其背后龐大的社區,這些擴展的數量變得越來越多。然而,這種龐大并不總是一件好事,因為你添加的擴展越多,VS Code加載的時間就越長。在某個結點之后,它將需要花費超過6-7秒,如果你愿意等待那么長時間,為什么不使用IDE呢?
此外,在擴展中,可能會出現一些安全性和性能問題,這些問題可能導致您甚至無法想象的結果。
以下是我的建議:如果擴展對你的工作站不是那么重要,就不要安裝它。相反,看看VS Code的文檔,試著找到一種本地的方法。如我之前所說,使用設置。你可以用VS Code做很多事情。下面是一個擴展及其設置的小列表settings.json的替代品。
雙引號彩色化
這是一個非常有用的方法,我用了很長時間。但現在它是在VS Code中本地實現的,而不是擴展,我使用的是快速的本地擴展。
要啟用它,請打開settings.json,并添加以下內容:
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs":"active
自動導入
自動導入是另一個應用廣泛的擴展,高達2M+的下載文件。但當你不需要的時候,為什么要讓你的工作空間被占用呢?
下面是VS Code開發者實現的相同功能。將這段代碼添加到settings.json中。
"javascript.suggest.autoImports": true,
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always"
自動關閉和重命名HTML標簽
這些擴展是我在系統上安裝的第一個,但現在它們都沒有了,因為現在VS Code可以通過這些設置自動做到:
"editor.linkedEditing": true,
"html.autoClosingTags": true,
"javascript.autoClosingTags": true,
"typescript.autoClosingTags": true,
Doxygen文檔生成器
這是另一個在記錄你的代碼時非常有用的擴展,正因為如此,VS code決定自己實現它。盡管如此,仍有600多萬用戶在自己的工作站上安裝了這個擴展。
這是默認啟用的,但如果不是,你可以添加以下settings.json:
"javascript.suggest.completeJSDocs": true,
"javascript.suggest.jsdoc.generateReturns": true,
"typescript.suggest.completeJSDocs": true,
"typescript.suggest.jsdoc.generateReturns": true,
更多的情況是,雖然本地就能實現,但人們仍然使用外部擴展。如果你有任何建議,不要忘記在回復部分分享它們。
立即重命名
當你不得不在整個代碼庫中更改函數或變量的名稱時,因為你不能使用好的ol ' find & replace來代替它,這是很糟糕的。變量名可以在字符串中,甚至可以在另一個函數名中,改變它會破壞一切。
幸運的是,VS Code比你想象的更聰明。它可以很容易地區分哪些字符是預期的變量名,并只更改變量名。
為此,你必須選擇需要重命名的變量并按F2。然后,輸入新的變量名并按Enter鍵。瞧!什么都沒有損壞,變量的名稱也立即改變了。
恭喜你!現在你知道了我用來加速開發環境的3個VS Code技巧。總之,VS Code是一個強大的工具,并且實現了它的目的——甚至更多。然而,如果你不知道如何使用,即使你擁有世界上最好的工具也無濟于事。
END
成就一億技術人
試(Debugging)作為軟件開發環境中無法缺少的部分,長期以來都作為評價一款 IDE 產品優劣的重要指標,VS Code 在 1.47 版本 中廢棄了舊版本的 Node Debug、Debugger For Chrome 等插件集,正式采用了全新的 JavaScript Debugger 插件,用于滿足所有 JavaScript 場景下的調試需求,不僅提供了豐富的調試能力,還為我們帶了了嶄新的 JavaScript Debug Terminal , Profiling 以及更好的斷點和源文件映射等能力。
本文將從 VSCode JavaScript Debugger 的功能入手,從源碼角度分析其實現對應功能所使用的技術手段及優秀的代碼設計,讓大家對其中的功能及實現原理有大致理解。
同時,在 2.18 版本的 OpenSumi 框架中,我們也適配了最新的 JavaScript Debugger 1.67.2 版本插件,大部分功能已經可以正常使用,歡迎大家升級體驗。
由于公眾號鏈接限制,文章中提到的詳細代碼均可在 https://github.com/microsoft/vscode-js-debug 倉庫中查看
VS Code JavaScript Debugger 依舊是基于 DAP 實現的一款 JavaScript 調試器。其支持了 Node.js, Chrome, Edge, WebView2, VS Code Extension 等研發場景調試。
DAP 是什么?
了解調試相關功能的實現,不得不提的就是 VS Code 早期建設的 DAP (Debug Adapter Protocol)方案,其摒棄了 IDE 直接與調試器對接的方案,通過實現 DAP 的方式,將于調試器適配的邏輯,承接在 Adapter(調試適配器) 之中,從而達到多個實現了同一套 DAP 協議的工具可以復用彼此調試適配器的效果,如下圖所示:
而上面圖示的適配器部分,一般組成了 VS Code 中調試插件中調試能力實現的核心。
目前支持 DAP 協議的開發工具列表見:Implementations Tools supporting the DAP:https://microsoft.github.io/debug-adapter-protocol/implementors/tools/ (OpenSumi 也在列表之中 ~)
多種調試能力
如上面介紹的,VS Code 中,調試相關的能力都是基于 DAP 去實現的,忽略建立鏈接的部分,在 JavaScript Debugger 中,所有的調試請求入口都在 adapter/debugAdapter.ts#L78 中處理,部分代碼如下所示:
// 初始化 Debugger
this.dap.on('initialize', params=> this._onInitialize(params));
// 設置斷點
this.dap.on('setBreakpoints', params=> this._onSetBreakpoints(params));
// 設置異常斷點
this.dap.on('setExceptionBreakpoints', params=> this.setExceptionBreakpoints(params));
// 配置初始化完成事件
this.dap.on('configurationDone', ()=> this.configurationDone());
// 請求資源
this.dap.on('loadedSources', ()=> this._onLoadedSources());
通過對 DAP 的實現,使得 JavaScript Debugger 可以先暫時忽略 Debug Adaptor 與不同調試器的適配邏輯,將調試抽象為一個個具體的請求及函數方法。
以設置斷點的 setBreakpoints 為例,JavaScript Debugger 將具體設置斷點的能力抽象與 adapter/breakpoints.ts 文件中,如下:
public async setBreakpoints(
params: Dap.SetBreakpointsParams,
ids: number[],
): Promise<Dap.SetBreakpointsResult> {
// 安裝代碼 SourceMap 文件
if (!this._sourceMapHandlerInstalled && this._thread && params.breakpoints?.length) {
await this._installSourceMapHandler(this._thread);
}
// ... 省略部分參數訂正及等待相關進程初始化的過程
// ... 省略合并已有的斷點邏輯,同時移除未與調試進程綁定的斷點
if (thread && result.new.length) {
// 為調試器添加斷點
this.ensureModuleEntryBreakpoint(thread, params.source);
// 這里的 Promise.all 結構是為了確保設置斷點過程中不會因為用戶的某次 disabled 操作而丟失準確性
// 相當于取了當前時刻有效的一份斷點列表
const currentList=getCurrent();
const promise=Promise.all(
result.new
.filter(this._enabledFilter)
.filter(bp=> currentList?.includes(bp))
// 實際斷點設置邏輯
.map(b=> b.enable(thread)),
);
// 添加斷點設置 promise 至 this._launchBlocker, 后續調試器依賴對 `launchBlocker` 方法來確保斷點已經處理完畢
this.addLaunchBlocker(Promise.race([delay(breakpointSetTimeout), promise]));
await promise;
}
// 返回斷點設置的 DAP 消息
const dapBreakpoints=await Promise.all(result.list.map(b=> b.toDap()));
this._breakpointsStatisticsCalculator.registerBreakpoints(dapBreakpoints);
// 更新當前斷點狀態
delay(0).then(()=> result.new.forEach(bp=> bp.markSetCompleted()));
return { breakpoints: dapBreakpoints };
}
接下來可以看到 adapter/breakpoints/breakpointBase.ts#L162 中實現的 enable 方法,如下:
public async enable(thread: Thread): Promise<void> {
if (this.isEnabled) {
return;
}
this.isEnabled=true;
const promises: Promise<void>[]=[this._setPredicted(thread)];
const source=this._manager._sourceContainer.source(this.source);
if (!source || !(source instanceof SourceFromMap)) {
promises.push(
// 當不存在資源或非 SourceMap 資源時
// 根據斷點位置、代碼偏移量計算最終斷點位置后在調試器文件路徑下斷點
this._setByPath(thread, uiToRawOffset(this.originalPosition, source?.runtimeScriptOffset)),
);
}
await Promise.all(promises);
...
}
根據資源類型進一步處理斷點資源路徑,核心代碼如下(詳細代碼可見:adapter/breakpoints/breakpointBase.ts#L429):
protected async _setByPath(thread: Thread, lineColumn: LineColumn): Promise<void> {
const sourceByPath=this._manager._sourceContainer.source({ path: this.source.path });
// ... 忽略對已經映射到本地的資源的處理
if (this.source.path) {
const urlRegexp=await this._manager._sourceContainer.sourcePathResolver.absolutePathToUrlRegexp(
this.source.path,
);
if (!urlRegexp) {
return;
}
// 通過正則表達式設置斷點
await this._setByUrlRegexp(thread, urlRegexp, lineColumn);
} else {
const source=this._manager._sourceContainer.source(this.source);
const url=source?.url;
if (!url) {
return;
}
// 直接通過路徑設置斷點
await this._setByUrl(thread, url, lineColumn);
if (this.source.path !==url && this.source.path !==undefined) {
await this._setByUrl(thread, absolutePathToFileUrl(this.source.path), lineColumn);
}
}
最終在進程中設置斷點信息,部分核心代碼如下(詳細代碼可見:adapter/breakpoints/breakpointBase.ts#L513 ):
protected async _setByUrlRegexp(
thread: Thread,
urlRegex: string,
lineColumn: LineColumn,
): Promise<void> {
lineColumn=base1To0(lineColumn);
const previous=this.hasSetOnLocationByRegexp(urlRegex, lineColumn);
if (previous) {
if (previous.state===CdpReferenceState.Pending) {
await previous.done;
}
return;
}
// 設置斷點
return this._setAny(thread, {
urlRegex,
condition: this.getBreakCondition(),
...lineColumn,
});
}
在 node-debug/node-debug2 等插件的以往實現中,到這一步一般是通過向調試器發送具體 “設置斷點指令” 的消息,執行相應命令,如 node/nodeV8Protocol.ts#L463 中下面的代碼:
private send(typ: NodeV8MessageType, message: NodeV8Message) : void {
message.type=typ;
message.seq=this._sequence++;
const json=JSON.stringify(message);
const data='Content-Length: ' + Buffer.byteLength(json, 'utf8') + '\r\n\r\n' + json;
if (this._writableStream) {
this._writableStream.write(data);
}
}
而在 JavaScript Debugger 中,會將所有這類消息都抽象為統一的 CDP (Chrome Devtools Protocol) , 通過這種方式,抹平所有 JS 調試場景下的差異性,讓其擁有對接所有 JavaScript 場景調試場景的能力,繼續以 “設置斷點” 這一流程為例,此時 JavaScript Debugger 不再是發送具體命令,而是通過 CDP 鏈接,發送一條設置斷點的消息,部分核心代碼如下(詳細代碼可見:adapter/breakpoints/breakpointBase.ts#L581 ):
const result=isSetByLocation(args)
? await thread.cdp().Debugger.setBreakpoint(args)
: await thread.cdp().Debugger.setBreakpointByUrl(args);
通過這層巧妙的 CDP 鏈接,可以將所有用戶操作指令統一為一層抽象的結構處理,后面只需要根據不同的調試器類型,選擇性處理 CDP 消息即可,如圖所示:
通過這層結構設計,能讓 JavaScript Debugger 輕松兼容三種模式調試 Node Launch, Node Attach, Chrome Devtools Attach, 從而實現對全 JavaScript 場景的調試能力。
了解詳細的 CDP 協議,可以查看文檔 CDP (Chrome Devtools Protocol) ,在調試領域,Chrome Devtools 擁有更加全面的場景及能力支持,部分能力,如 DOMSnapshot 并不能在 Node 場景下使用,因此在實現過程中也需要選擇性處理。
同時,通過這樣的改造,也讓運行于 VS Code 中的調試進程可以通過 Chrome Devtools 或其他支持 CDP 協議的調試工具進行鏈接調試,如運行 extension.js-debug.requestCDPProxy 命令獲取調試信息,如下圖所示:
在 Chrome Devtools 中可以拼接為 chrome-devtools://devtools/custom/inspector.html?ws=ws://127.0.0.1:53591/273c30144bc597afcbefa2058bfacc4b0160647e 的路徑直接進行調試。
JavaScript Debug Terminal
如果要評選 JavaScript Debugger 中最好用的功能,那么我一定投票給 JavaScript Debug Terminal 這一功能。
JavaScript Debug Terminal 為用戶提供了一種無需關注調試配置,只需在終端運行腳本即可快速進行調試的能力,如下所示(OpenSumi 中的運行效果):
眾所周知,Node.js 在調試模式下提供了兩種 flag 選項,一個是 --inspect , 另一個則是 --inspect-brk ,兩者都可以讓 Node.js 程序以調試模式啟動,唯一區別即是 --inspect-brk 會在調試器未被 attach 前阻塞 Node.js 腳本的執行,這個特性在老版本的 Node Debug 插件中被廣泛使用,用于保障在調試執行前設置斷點等。
而在 JavaScript Debugger 中,采用了一個全新的腳本運行模式,讓 Node.js 的調試可以不再依賴 --inspect-brk , 其原理即是向在 JavaScript Debug Terminal 中運行的腳本注入 NODE_OPTIONS 選項,如下所示:
在傳入 NODE_OPTIONS:'--require .../vscode-js-debug/out/src/targets/node/bootloader.bundle.js' 的環境變量后,Node.js 在腳本執行前便會提前先去加載 bootloader.bundle.js 內的文件內容,而后再執行腳本,這中間就提供了大量可操作性。
進一步看這個 targets/node/bootloader.ts#L31 文件,里面寫了一段自執行代碼,在全局創建一個 $jsDebugIsRegistered 對象, 通過程序內部構造的 VSCODE_INSPECTOR_OPTIONS 對象直接與調試進程進行 IPC 通信,配置格式如下所示:
{
// 調試進程 IPC 通信地址
"inspectorIpc":"/var/folders/qh/r2tjb8vd1z3_qtlnxy47b4vh0000gn/T/node-cdp.33805-2.sock",
// 一些配置
"deferredMode":false,
"waitForDebugger":"",
"execPath":".../node",
"onlyEntrypoint":false,
"autoAttachMode":"always",
// 文件回調地址,如果存在,在調試進程中的打印的日志將會寫入到該文件中
"fileCallback":"/var/folders/qh/r2tjb8vd1z3_qtlnxy47b4vh0000gn/T/node-debug-callback-d2db3d91a6f5ae91"
}
在獲取到 inspectorIpc 等配置后,即會嘗試通過讀文件的方式確認 inspector 進程 的連通性,偽代碼如下(詳細代碼可見:targets/node/bootloader.ts#L246):
fs.readdirSync(path.dirname(inspectorIpc)).includes(path.basename(inspectorIpc));
在確定 inspector 進程 的連通性后,接下來就可以使用 inspector 庫, 獲取 inspector.url() 后進行鏈接操作,部分代碼如下(詳細代碼見:targets/node/bootloader.ts#L111):
(()=> {
...
// 當進程執行時傳入了 `--inspect` 時,inspector.url() 可以獲取到當前的調試地址,命令行情況需要額外處理
const info: IAutoAttachInfo={
ipcAddress: env.inspectorIpc || '',
pid: String(process.pid),
telemetry,
scriptName: process.argv[1],
inspectorURL: inspector.url() as string,
waitForDebugger: true,
ownId,
openerId: env.openerId,
};
// 當需要立即啟動調試時,執行 watchdog 程序監聽進程創建
if (mode===Mode.Immediate) {
// 代碼見:https://github.com/microsoft/vscode-js-debug/blob/b056fbb86ef2e2e5aa99663ff18411c80bdac3c5/src/targets/node/bootloader.ts#L276
spawnWatchdog(env.execPath || process.execPath, info);
}
...
})();
function spawnWatchdog(execPath: string, watchdogInfo: IWatchdogInfo) {
const p=spawn(execPath, [watchdogPath], {
env: {
NODE_INSPECTOR_INFO: JSON.stringify(watchdogInfo),
NODE_SKIP_PLATFORM_CHECK: process.env.NODE_SKIP_PLATFORM_CHECK,
},
stdio: 'ignore',
detached: true,
});
p.unref();
return p;
}
接下來就是執行下面的代碼 targets/node/watchdog.ts, 部分代碼如下:
const info: IWatchdogInfo=JSON.parse(process.env.NODE_INSPECTOR_INFO!);
(async ()=> {
process.on('exit', ()=> {
logger.info(LogTag.Runtime, 'Process exiting');
logger.dispose();
if (info.pid && !info.dynamicAttach && (!wd || wd.isTargetAlive)) {
process.kill(Number(info.pid));
}
});
const wd=await WatchDog.attach(info);
wd.onEnd(()=> process.exit());
})();
實際上這里又用了一個子進程去處理 CDP 通信的鏈接,最終執行到如下位置代碼 targets/node/watchdogSpawn.ts#L122,部分代碼如下:
class WatchDog {
...
// 鏈接本地 IPC 通信地址,即前面從環境變量中獲取的 inspectorIpc
public static async attach(info: IWatchdogInfo) {
const pipe: net.Socket=await new Promise((resolve, reject)=> {
const cnx: net.Socket=net.createConnection(info.ipcAddress, ()=> resolve(cnx));
cnx.on('error', reject);
});
const server=new RawPipeTransport(Logger.null, pipe);
return new WatchDog(info, server);
}
constructor(private readonly info: IWatchdogInfo, private readonly server: ITransport) {
this.listenToServer();
}
// 鏈接 Server 后,發送第一條 `Target.targetCreated` 通知調試進程已經可以開始調試
private listenToServer() {
const { server, targetInfo }=this;
server.send(JSON.stringify({ method: 'Target.targetCreated', params: { targetInfo } }));
server.onMessage(async ([data])=> {
// Fast-path to check if we might need to parse it:
if (
this.target &&
!data.includes(Method.AttachToTarget) &&
!data.includes(Method.DetachFromTarget)
) {
// 向 inspectorUrl 建立的鏈接發送消息
this.target.send(data);
return;
}
// 解析消息體
const result=await this.execute(data);
if (result) {
// 向調試進程發送消息
server.send(JSON.stringify(result));
}
});
server.onEnd(()=> {
this.disposeTarget();
this.onEndEmitter.fire({ killed: this.gracefulExit, code: this.gracefulExit ? 0 : 1 });
});
}
...
}
可以看到,在 Node.js 腳本被真正執行前,JavaScript Debug Terminal 為了讓 CDP 鏈接能夠正常初始化以及通信做了一系列工作,也正是這里的初始化操作,讓即使是在終端被執行的腳本依舊可以與我們的調試進程進行 CDP 通信。
這里忽略掉了部分終端創建的邏輯,實際上在創建終端的過程中,JavaScript Debugger 也采用了一些特殊的處理,如不直接通過插件進程創建終端的邏輯,而是通過 vscode.window.onDidOpenTerminal 去接收新終端的創建,見 ui/debugTerminalUI.ts#L197 。這些操作對于 Terminal 實例在插件進程的唯一性有一定要求,這也是前期插件適配工作的成本之一。
Automatic Browser Debugging
看完 JavaScript Debug Terminal 的實現原理,我們再來看一下另外一個重要特性的實現:Automatic browser debugging ,想要使用該功能,你需要在 JavaScript Debug Terminal 中使用,或手動配置debug.javascript.debugByLinkOptions 為 on 或 always ,開啟了該功能后,所有你在終端以調試模式打開的網址將都可以自動 Attach 上響應的調試進程。
link-debugging.gif
其核心原理即是通過 ui/terminalLinkHandler.ts 往 Terminal 中注冊鏈接點擊處理邏輯,實現 vscode.TerminalLinkProvider (https://code.visualstudio.com/api/references/vscode-api#TerminalLinkProvider) 的結構。
export class TerminalLinkHandler implements vscode.TerminalLinkProvider<ITerminalLink>, IDisposable {
// 根據給定的 Terminal 獲取其內容中可被點擊的 Link 數組,配置其基礎信息
public provideTerminalLinks(context: vscode.TerminalLinkContext): ITerminalLink[] {
switch (this.baseConfiguration.enabled) {
case 'off':
return [];
case 'always':
break;
case 'on':
default:
if (!this.enabledTerminals.has(context.terminal)) {
return [];
}
}
const links: ITerminalLink[]=[];
for (const link of findLink(context.line, 'url')) {
let start=-1;
while ((start=context.line.indexOf(link.value, start + 1)) !==-1) {
let uri: URL;
try {
uri=new URL(link.href);
} catch {
continue;
}
// hack for https://github.com/Soapbox/linkifyjs/issues/317
if (
uri.protocol===Protocol.Http &&
!link.value.startsWith(Protocol.Http) &&
!isLoopbackIp(uri.hostname)
) {
uri.protocol=Protocol.Https;
}
if (uri.protocol !==Protocol.Http && uri.protocol !==Protocol.Https) {
continue;
}
links.push({
startIndex: start,
length: link.value.length,
tooltip: localize('terminalLinkHover.debug', 'Debug URL'),
target: uri,
workspaceFolder: getCwd()?.index,
});
}
}
return links;
}
/**
* 處理具體點擊鏈接后的操作
*/
public async handleTerminalLink(terminal: ITerminalLink): Promise<void> {
if (!(await this.handleTerminalLinkInner(terminal))) {
vscode.env.openExternal(vscode.Uri.parse(terminal.target.toString()));
}
}
}
在鏈接被打開前,會進入 handleTerminalLinkInner 的邏輯進行調試進程的鏈接處理,如下:
向上檢索默認的瀏覽器信息,是否為 Edge,否則使用 pwa-chrome 調試類型啟動調試。
在找不到對應調試信息(即 DAP 消息)的情況下,輸出 Using the "preview" debug extension , 結束調試。
Profile
Profile 主要為開發者提供對進程性能及堆棧信息的分析能力,在 JavaScript Debugger 中,由于所有的通信均通過 CDP 協議處理,生成的報告文件也自然的能通過 Chrome Devtools 中查看,VS Code 中默認僅支持基礎的報告查看,你也可以通過安裝 ms-vscode.vscode-js-profile-flame 插件查看。
實現該功能依舊是通過 DAP 消息進行通信處理,與上面提到的 設置斷點 案例實際類似,DAP 通信中收到 startSefProfile 時開始向 CDP 鏈接發送 Profiler.enable ,Profiler.start 指令,進而在不同的調試器中處理該指令,在 DAP 通信中收到 stopSelfProfile 指令時,向 CDP 鏈接發送 Profiler.stop 指令,收集 profile 信息后寫入對應文件,詳細代碼可見:adapter/selfProfile.ts
Debug Console
JavaScript Debugger 在發布日志中著重標注了對于 Top-Level await 的支持,原有的 DebugConsole 對于變量執行的邏輯依舊是依賴 DAP 中接收 evaluate 指令(代碼見:adapter/debugAdapter.ts#L99) ,繼而轉化為 CDP 的 Runtime.evaluate 指令執行。由于不同調試器運行環境的差異性,變量或表達式最終的執行指令需要根據環境進行區分處理(詳細代碼可見:adapter/threads.ts#L394)。以在調試控制臺執行如下代碼為例:
const res=await fetch('http://api.github.com/orgs/microsoft');
console.log(await res.json());
當表達式為 Top-Level await 時,需要將表達式進行重寫
從上面的表達式轉化為可執行的閉包結構(解析邏輯可見:common/sourceUtils.ts#L67)同時在參數中標記 awaitPromise=true , 在部分調試器執行 Runtime.evalute 時,當參數中存在 awaitPromise=true 時,會將閉包執行的返回結果作為輸出值進行返回,轉化后的結果如下所示:
(async ()=> {
(res=await fetch('http://api.github.com/orgs/microsoft'));
return console.log(await res.json());
})();
最終執行結果就能夠正常輸出:
這樣便實現了對 Top-Level await 的支持。
以上整體上是針對部分功能實現的解析,部分功能的優化也依賴 DAP 及代碼邏輯的優化實現,如更好的代碼映射及 Return value interception 等,希望看完本文能讓你對 VS Code 的 JavaScript Debugger 有大致的理解。
者:biaochenxuying
轉發鏈接:https://github.com/biaochenxuying/blog/issues/31
*請認真填寫需求信息,我們會在24小時內與您取得聯系。