vscode 是前端工程師經常使用的 ide,並且它的實現也是基於前端技術。既然是前端技術實現的,那麼咱們用所掌握的前端技術,徹底能夠實現一個相似 vscode 的 ide。但在那以前,咱們首先仍是要把 vscode 是怎麼實現的理清楚。本文咱們就來理一下 vscode 是怎麼跑起來的。javascript
首先, vscode 是一個 electron 應用,窗口等功能的實現基於 electron,因此想梳理清楚 vscode 的啓動流程,須要先了解下 electron。css
electron 基於 node 和 chromium 作 js 邏輯的執行和頁面渲染,而且提供了 BrowserWindow 來建立窗口,提供了 electron 包的 api,來執行一些操做系統的功能,好比打開文件選擇窗口、進程通訊等。html
每一個 BrowserWindow 窗口內的 js 都跑在一個渲染進程,而 electron 有一個主進程,負責和各個窗口內的渲染進程通訊。前端
如圖所示,主進程可使用 nodejs 的 api 和 electron 提供給主進程的 api,渲染進程可使用瀏覽器的 api、nodejs 的 api 以及 electron 提供給渲染進程的 api,除此之外,渲染進程還可使用 html 和 css 來作頁面的佈局。java
vscode 的每一個窗口就是一個 BrowserWindow,咱們啓動 vscode 的時候是啓動的主進程,而後主進程會啓動一個 BrowserWindow 來加載窗口的 html,這樣就完成的 vscode 窗口的建立。(後續新建窗口也是同樣的建立 BrowserWindow,只不過要由渲染進程經過 ipc 通知主進程,而後主進程再建立 BrowserWindow,不像第一次啓動窗口直接主進程建立 BrowserWindow 便可。)node
咱們知道 vscode 基於 electron 來跑,electron 會加載主進程的 js 文件,也就是 vscode 的 package.json 的 main 字段所聲明的 ./out/main.js,咱們就從這個文件開始看。typescript
(下面的代碼都是我從源碼簡化來的,方便你們理清思路)json
// src/main.js
const { app } = require('electron');
app.once('ready', onReady);
async function onReady() {
try {
startup(xxxConfig);
} catch (error) {
console.error(error);
}
}
function startUp() {
require('./bootstrap-amd').load('vs/code/electron-main/main');
}
複製代碼
能夠看到,./src/main.js 裏面只是一段引導代碼,在 app 的 ready 事件時開始執行入口 js。也就是 vs/code/electron-main/main,這是主進程的入口邏輯。bootstrap
// src/vs/code/electron-main/main.ts
class CodeMain {
main(): void {
try {
this.startup();
} catch (error) {
console.error(error.message);
app.exit(1);
}
}
private async startup(): Promise<void> {
// 建立服務
const [
instantiationService,
instanceEnvironment,
environmentMainService,
configurationService,
stateMainService
] = this.createServices();
// 初始化服務
await this.initServices();
// 啓動
await instantiationService.invokeFunction(async accessor => {
//建立 CodeApplication 的對象,而後調用 startup
return instantiationService.createInstance(CodeApplication).startup();
});
}
}
const code = new CodeMain();
code.main();
複製代碼
能夠看到,vscode 建立了 CodeMain 的對象,這個是入口邏輯的開始,也是最根上的一個類。它建立和初始化了一些服務,而後建立了 CodeApplication 的對象。windows
也許你會說,建立對象爲啥不直接 new,還要調用 xxx.invokeFunction() 和 xxx.createInstance() 呢?
這是由於 vscode 實現了 ioc 的容器,也就是在這個容器內部的任意對象均可以聲明依賴,而後由容器自動注入。
原本是直接依賴,可是經過反轉成注入依賴的方式,就避免了耦合,這就是 ioc (invert of control)的思想,或者叫 di(dependency inject)。
這個 CodeApplication 就是 ioc 容器。
//src/vs/code/electron-main/app.ts
export class CodeApplication {
// 依賴注入
constructor( @IInstantiationService private readonly mainInstantiationService: IInstantiationService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService ){
super();
}
async startup(): Promise<void> {
const mainProcessElectronServer = new ElectronIPCServer();
this.openFirstWindow(mainProcessElectronServer)
}
private openFirstWindow(mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] {
this.windowsMainService.open({...});
}
}
複製代碼
CodeApplication 裏面經過裝飾器的方式聲明依賴,當經過容器建立實例的時候會自動注入聲明的依賴。
startup 裏面啓動了第一個窗口,也就是渲染進程,由於主進程和渲染進程之間要經過 ipc 通訊,因此還會建立一個 ElectronIPCServer 的實例(其實它只是對 ipc 通訊作了封裝)。
最終經過 windowMainService 的服務建立了窗口。
雖然比較繞,可是經過 service 和 ioc 的方式,可以更好的治理複雜度,保證應用的架構不會越迭代越亂。
而後咱們來看具體的窗口建立邏輯。
//src/vs/platform/windows/electron-main/windowsMainService.ts
export class WindowsMainService {
open(openConfig): ICodeWindow[] {
this.doOpen(openConfig);
}
doOpen(openConfig) {
this.openInBrowserWindow();
}
openInBrowserWindow(options) {
// 建立窗口
this.instantiationService.createInstance(CodeWindow);
}
}
複製代碼
在 windowMainService 裏面最終建立了 CodeWindow 的實例,這就是對 BrowserWindow 的封裝,也就是 vscode 的窗口。(用 xxx.createIntance 建立是由於要受 ioc 容器管理)
//src/vs/platform/windows/electron-main/window.ts
import { BrowserWindow } from 'electron';
export class CodeWindow {
constructor() {
const options = {...};
this._win = new BrowserWindow(options);
this.registerListeners();
this._win.loadURL('vs/code/electron-browser/workbench/workbench.html');
}
}
複製代碼
CodeWindow 是對 electron 的 BrowserWindow 的封裝,就是 vscode 的 window 類。
它建立 BrowserWindow 窗口,而且監聽一系列窗口事件,最後加載 workbench 的 html。這就是 vscode 窗口的內容,也就是咱們平時看到的 vscode 的部分。
至此,咱們完成了 electron 啓動到展現第一個 vscode 窗口的邏輯,已經可以看到 vscode 的界面了。
vscode 是基於 electron 作窗口的建立和進程通訊的,應用啓動的時候會跑主進程,從 src/main 開始執行,而後建立 CodeMain 對象。
CodeMain 裏會初始化不少服務,而後建立 CodeApplication,這是 ioc 的實現,全局惟一。對象的建立由容器來管理,裏面全部的對象均可以相互依賴注入。
最開始會先經過 windowMainSerice 服務來建立一個 CodeWindow 的實例,這就是窗口對象,是對 electron 的BrowserWindow 的封裝。
窗口內加載 workbench.html,這就是咱們看到的 vscode 的界面。
vscode 就是經過這樣的方式來基於 electron 實現了窗口的建立和界面的顯示,感興趣的能夠參考本文去看下 vscode 1.59.0 的源碼,是能學到不少架構方面的東西的,好比 ioc 容器來作對象的統一管理,經過各類服務來管理各類資源,這樣集中管理的方式使得架構不會越迭代越亂,複雜度獲得了很好的治理,此外,學習 vscode 源碼也可以提高對 electron 的掌握。