title: VSCode 是怎么运行起来的? description: 小胡子哥的个人网站 warning: true author: 小胡子哥 tags:
之前有基于 VSCode 做二次开发的经验,约摸全投入持续了 5 个多月,开发了一个 Editor,算是超级魔改吧,虽然保留了 VSCode 的样子,但是整个板块都有比较大的调整,新增了 Webview 预览面板、Devtool 调试工具、顶部控制区、插件市场等等。
当时由于需求的实现不需要了解全部的 VSCode 源码,但是也把大部分的源码啃得差不多了,包括:
但是也有很多内容没有掌握,应该说没有太多兴趣和时间了解,包括:
这两天突然来了兴致,把之前没了解的部分源码通读了一遍,当然,仍然有一些疑惑,也仍然有一些不感兴趣的部分,后续空了会有更多的梳理,下面先贴上这两天的阅读笔记。其实我应该画图来帮助读者理解,不过以下主要是个人笔记,就懒得整理了,感兴趣的读者将就着看。
阅读的版本是
v1.37.0
,是目前 VSCode 源码仓库 master 分支最新的代码。
基本就是 IoC 的实现原理,以及 Service 的全局管理机制。
提供了一个泛型装饰器 createDecorator
,入参是 ServiceName 和 IService,后者是泛型入参:
function createDecorator<IService>('service'): ServiceIdentifier<IService> {}
内部对 service
的实际处理是:
parameter
,意思是在类中 method
不允许被它装饰;并将装饰器的 toString 函数置为 service
这个 String返回的 ServiceIdentifier
,格式为:
export interface ServiceIdentifier<T> {
(...args: any[]): void;
type: T;
}
ServiceCollection
,它是一个 Map 类型,储存格式为:<ServiceIdentifier, instanceOrDescriptor>[]
instantiationService
服务上,实际上是挂在 instantiationService
的私有成员变量 _services
上this._services.set(IInstantiationService, this)
把自己装进了服务容器Trace
方法,记录了每次调用的耗时IdleValue
类返回了一个 Proxy 对象,只有真正用到的时候才执行返回服务实例看看 VSCode 在启动前和启动时都做了哪些事情。
VSCode 的入口启动文件是 ./out/main.js
,对应源文件目录是 ./src/vs/main.ts
。
vs/base/common/performance
,通过数组记录,每两项为一个数据单元,[name, timestamp, ...]
,兼容 amd/cmdvs/base/node/languagePacks
,将大量 fs 操作 Promise 化后,提供一个查、写 NLSConfiguration
的方法,兼容 amd/cmd./bootstrap
injectNodeModuleLookupPath
,注入一个 node_modules 的查询路径enableASARSupport
,同上,注入 .asar
路径uriFromPath
,一个兼容 win/mac 的将 path 路径转换成磁盘 uri 的方法./bootstrap-amd
github:Microsoft/vscode-loader
,vs/code/electron-main/main
main
setUnexpectedErrorHandler
,避免 Electron 底层报错,上层进行劫持validatePaths
入参验证startup -> createServices,doStartup
bufferLogService
instantiationService, instanceEnvironment
ServiceCollection
记录所有注册的服务,它是一个单纯的 Map,记录的是 <ServiceIdentifier, instanceOrDescriptor>[]
environmentService/logService/configurationService/lifecycleService/stateService/requestService/themeMainService/signService
ServiceCollection
挂载到 instantiationService
,作为 _services
私有成员instantiationService
挂在到 _services
私有成员上,相当于依然使用 ServiceCollection
管理environmentService/configurationService/stateService
的初始化mainIpcServer
,并连接 sharedIpcServer
mainIpcServer
,入参是 environmentService
中记录的 mainIPCHandle
地址EADDRINUSE
错误,检查是否已经存在了一个 IPCHandler,如果存在则直接连接,连接失败会重试 1 次startup
方法启动入口文件是 ./src/vs/code/electron-main/app.ts
,对应的类为 CodeApplication
,实例化的时候做了两件事情
startup
启动ElectronIPCServer
ipcMain
监听 ipc:hello
事件,通过 webContents.id
标记 Client 身份 ipc:message
和 ipc:disconnect
事件SharedProcess
BrowserWindow
,启用了 nodeIntegration
vs/code/electron-browser/sharedProcess/sharedProcess.html?config=${config}
,通过 url 传参sharedIPCHandle
的值传递给 SharedProcess
SharedProcess
创建一个 IPCServerIPCServer
MainProcess
连接到 SharedProcess
的 IPCServerCollectionService
容器driverHandle
,则新建一个 Dirver 的 IPCServerProxyAuth
模块,不知道哪里会用到,内容很简单,就是一个输入账号密码的 BrowserWindow
弹窗openFirstWindow
打开 VSCode 主界面launchService
updateChannel/issueChannel/workspaceChannel/windowsChannel/menubarChannel/urlChannel/storageChannel
等windowsMainService(windowManager)
打开界面IPCServer 的大致原理,细节非常多,下面只是列了提纲,主要是实现了一套序列化和反序列化的协议,以及 call
调用和 listen
监听的两大逻辑。
net server
,通过 mainIPCHandle
文件句柄进行监听netSocket
封装了 NodeSocket
,并绑定了通讯协议 Protocol
/-------------------------------|------\
| HEADER | |
|-------------------------------| DATA |
| TYPE | ID | ACK | DATA_LENGTH | |
\-------------------------------|------/
net socket
后,将 netSocket
同上包装两层,NodeSocket + Protocol
ctx
为 main
,创建 IPCClient
,IPCClient
既是一个 client
也是一个 server
,共享一个 socket,通过 Protocol 协议进行通讯ChannelClient
,维护一个通讯的 handlers 管理器requestId
和 responseHandler
写入管理器,通过 protocal.send
将请求发出protocal.onMessage
,通过 requestId
匹配 responseHandler
处理结果ChannelServer
,指定 ctx 为 main
(写入 ctx 的值)onRawMessage
监听消息,解析 header 和 body 后,选择对应的 channel
,执行 channel.call
,执行结果通过 protocal.send
返回以上的分析过程,对读者的作用可能并不大,主要是逻辑过于冗长,防止自己读着读着开始迷失,把觉得比较核心的逻辑记录了下来。
如果读者想了解 VSCode 的全盘代码,有几处是必须全部理解的:
后续空闲,我还会结合单测和自动化测试,熟悉 VSCode 整体架构,等有了内容再来分享。