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