vscode 是怎麼跑起來的

vscode 是前端工程師經常使用的 ide,並且它的實現也是基於前端技術。既然是前端技術實現的,那麼咱們用所掌握的前端技術,徹底能夠實現一個相似 vscode 的 ide。但在那以前,咱們首先仍是要把 vscode 是怎麼實現的理清楚。本文咱們就來理一下 vscode 是怎麼跑起來的。javascript

首先, vscode 是一個 electron 應用,窗口等功能的實現基於 electron,因此想梳理清楚 vscode 的啓動流程,須要先了解下 electron。css

electron

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

vsocde 窗口啓動流程

咱們知道 vscode 基於 electron 來跑,electron 會加載主進程的 js 文件,也就是 vscode 的 package.json 的 main 字段所聲明的 ./out/main.js,咱們就從這個文件開始看。typescript

src/main

(下面的代碼都是我從源碼簡化來的,方便你們理清思路)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

CodeMain

// 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 容器。

CodeApplication

//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 的方式,可以更好的治理複雜度,保證應用的架構不會越迭代越亂。

而後咱們來看具體的窗口建立邏輯。

windowMainService

//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 容器管理)

CodeWindow

//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 的掌握。

相關文章
相關標籤/搜索