信上發起的投票活動的商家前撲后續的進行著,好些人一次性參加了好幾場評選活動,如果單單靠投票拉票的方式,肯定大家都會反感的。所以可以通過自己制作微信投票刷票神器的方式來刷投票拉票。那么我們自己該如何制作一款微信投票刷票神器呢,其實也不難的,如果你是編程小白的話你可以接著往下看小編現在給大家分享的微信投票刷票神器代碼給大家學習和了解下。
微信投票刷票神器代碼制作教程
ArrayList<VoteBean> mData=new ArrayList<VoteBean>(開始執行程序);
for(int i=0;i<5;i++){獲取活動所有參賽者名單,以豎狀展示。
VoteBean bean=http://www.aivote.com/ new VoteBean(填寫本次活動主辦方ID);
bean.setTitle(“選項” + (char)(65+i));勾選單個參賽者名單
bean.setIsCheck(false);設定投票分鐘增加票數分值
mData.add(bean);設置分鐘投票IP切換次數}
mVoteTopAdapter=http://www.aivote.com/13932.html new VoteTopAdapter(this,mData);設定需要增加的投票數數量
mList.setAdapter http://www.aivtp.com/ (mVoteTopAdapter);控制全局
ArrayList<String> mVoteData=new ArrayList<String>(開始執行投票);
注釋:只要通過通用版的封包工具封包,然后解壓就可以使用了,非常簡單易操作。
018中國軟件大會暨首屆CIO創新峰會將于12月20日-21日在北京召開。作為我國軟件和信息技術服務產業一年一度最高規格的精英盛會,2018中國軟件大會將聚焦“駕馭數字化轉型,走進智能互聯時代”,全面系統總結我國軟件和信息技術服務產業取得的年度成就。
本屆盛會將推出第十七屆中國軟件和信息服務十大領軍人物/領軍企業的重磅評選,同期還將推出2018中國軟件和信息服務業青年領袖、2018中國數字化轉型杰出30人、2018中國軟件和信息服務業風云人物/風云企業/領軍產業園區、2018中國云計算/大數據/人工智能領軍企業以及2018年度專有領域獎項等多項重大獎評。
作為我國軟件和信息技術服務業的重磅評選,“雙十評選”(中國軟件和信息服務十大領軍人物/領軍企業)已經成功舉辦十六載。隨著物聯網、云計算、大數據、人工智能等技術的發展,互聯網正逐漸進入到智能互聯時代。無論是在企業的數字化轉型過程中,還是在智能互聯時代的發展中,軟件和信息服務領域的領軍人物和領軍企業都將對產業發展起到至關重要的作用。
今年,大會還將創新推出2018中國軟件和信息服務業青年領袖,重在表彰對中國軟件和信息技術服務行業發展做出突出貢獻的45歲以下青年企業領袖。
在數據化轉型的過程中,中國龐大的CIO群體在推進企業數字化轉型創新應用方面承擔了重要職責和使命。對此,本次大會將隆重推出在企業數字化轉型中作出杰出貢獻的CIO,推舉“2018中國數字化轉型杰出CIO30人”。
過去一年,在中國軟件和信息技術服務業,大數據、云計算、人工智能等創新技術被廣泛應用到各行各業志之中,為智能互聯時代的企業數字化轉型奠定了良好的基礎。2018中國云計算/大數據/人工智能領軍企業將推舉各個領域的龍頭企業,過去一年,正是在他們的引領下,數字化轉型從理論走向實踐,為傳統產業的變革增加了助力。
2018年,在軟件和信息技術服務業的諸多細分領域中,涌現了一批在管理創新、技術創新、商業模式創新、應用創新等方面表現突出的典型人物。 在細分行業、細分產品線、細分應用領域等方面,也涌現了一批優秀的企業、產品、解決方案等。 因此,大會同期還將對2018中國軟件和信息服務業風云人物/風云企業/領軍產業園區以及專有領域獎項進行公開評選。
本屆評選秉承公開公正的原則,將采取線上投票和線下專家評審的方式,歡迎大家踴躍參與投票。
預知更多大會詳情,請登錄大會官網http://csc.softic.com.cn/index.html,或掃描以下二維碼進入投票頁面。
2018中國軟件產業年度盛宴已盛大啟航!
內容精彩,不容錯過!
獎項參評、參與宣傳片拍攝、大會深度合作、報名參會,請咨詢
【2018中國軟件大會組委會】
010-88559465 / 8840 / 8844 / 8999
長按/掃碼 了解更多大會信息
關于轉載
·無原創標識文章請按照原文內容編輯,可直接轉載,轉載后請將轉載鏈接發送給我們;
· 有原創標識文章,請發送【文章名稱—待授權公眾號名稱及ID】給我們申請白名單授權。
未經許可的轉載以及改編者,我們將依照微信公眾號相關規則追究其責任。
聯系郵箱:chenmm@softic.com.cn。
試(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 有大致的理解。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。