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

相关文章
相关标签/搜索