大纲
- 整体设计 vscode, monaco editor,extension,Electron,language service
- IDE代码的组织
- 复杂动态界面的代码抽象设计
- 为什么IDE不卡?相比Atom
- 插件的设计
系列文章
- vscode整体介绍
- 从vscode启动流程学electron应用开发
- 从vscode看大型程序设计 service 设计模式
- 从vscode看复杂前端交互组织
- 从vscode看产品设计
TODO
- 代码是如何组织的
梳理和理解了Register模式- 启动流程
- 右键菜单流程解析
- 右键菜单复杂交互背后
- 如果在界面上加一块界面,需要怎么做?
- 复杂动态界面代码抽象,常见联动如何做?纯DOM,以及事件绑定
整体设计
- vscode
- monaco editor
- extension
- Electron
- language service,eslint等等
进程结构
多进程架构带来的速度优化
有一个主进程和多个渲染进程。如果VScode你开了多个窗口,每个窗口对应一个渲染进程,都是互相独立不影响的。每个渲染进程下,又开启有独立的插件宿主进程。
主进程
**
负责启动,打开浏览器的窗口,自动更新等与系统相关的功能。
渲染进程
**
即我们看到的整个浏览器窗口。因为Electron的缘故,可以在浏览器中运行Nodejs代码。我们开的每一个窗口都对应一个独立的渲染进程,互相之间不影响。
插件进程
每个渲染进程下又有独立的插件进程,因此保证了主进程的运行速度,但同时插件对UI的操作能力非常弱
进程间通讯,IPC/RPC
渲染进程中可以开Worker进行耗时计算,
通信 调用
- IPC通信
- 主进程和render进程的通信
- 利用electron提供的ipcRender和ipcMain进行通信。
- IPC通信,类似EventEmitter,通过消息机制调用
- RPC通信
- Render进程和插件进程的通信,有一个
RPCProtocol.ts
实现。 - RPC是更高级的实现,调用其它进程的方法,就像调用本地API方法一样。
- Electron中的remote就是RPC的一种实现
- Render进程和插件进程的通信,有一个
IDE代码的组织
- 依赖注入
- 案例,启动流程的代码解析
- 案例,右键菜单的代码解析
分类
- workbench 整体视图框架
- platform 可被依赖注入的各种纯服务
- editor 文本编辑器
- code 应用主入口
- base 通用的公共方法和公共视图组件
- extensions 内置插件
MainThreadWebviews // 很重要的,什么?
流程解析
启动流程代码解析
- main.js
- vs/code/electron-main/main
/src/vs/workbench/browser/parts/editor/
右键菜单代码解析
复杂动态界面的代码抽象设计
特征
- 动态化
- 界面复杂,区块繁多
- 相似性较多
有哪些设计?
- 面向对象 非常细力度华
- 区块Controller维护
- Command
- 依赖注入,Service
- 案例,菜单栏变化,以及和右侧文件区联动
- 案例,右侧代码和eslint的联动
Service服务化设计
整个程序都Service化,通过依赖注入,或者通过暴露ServicesAccessor来访问到任何一个Service。
- 通过依赖注入方式,方便的引入依赖的Service
- 依赖注入,可以帮助Vscode在不同端(Web,PC端)根据特征注入不同的Service
- 有助于将Controller中较复杂逻辑拆分出Service,降低耦合度
有哪些Service
- editorService
- workspaceContextService
- listService 这个代码设计值得看一下 register的注册模式
- IConfigurationService
editorService.ts
/src/vs/workbench/services/editor/common/editorService.ts
- editorService 即右侧的编辑区域
- 属性 数据 标明当前的状态 激活的,可见的编辑窗口
- 事件监听 监听变化 可监听激活的,可见的编辑窗口变化
- 方法 可进行操作,判断 打开编辑器,判断是否打开
activeEditor
visibleEditors
onDidActiveEditorChange
onDidVisibleEditorsChange
openEditor
isOpen
Accessor获取方式
CommandsRegistry.registerCommand('_workbench.open', function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn, string?]) {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const openerService = accessor.get(IOpenerService);
const urlService = accessor.get(IURLService);
List Service
Command设计解耦
https://tuhrig.de/messages-vs-events-vs-commands/
各组件间存在互相调用,有几种解决方式
- 引用类,即new实例,然后调用方法
- Command,即命令模式,
- Event 主要是有多个监听者,即多个消费者,
- Message,跟command没啥差别
但是在VSCode中,存在插件调用等消息传递,不遍引用类,因此用command调用非常合适
同时Command也比记忆各种类
面向对象的深度使用
相似组件抽象interface & 约束
将不用类型都抽象出其interface,对实现的类约束其实现。因为在vscode中有很多外观有差异,但是行为表现差不多的组件。
export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider {
}
export abstract class Widget extends Disposable {
}
类的标记
IMainContext 和 rpcProtocol
统一管理 通过类别来判断
不同的列表都存储在了listService中统一存放。
但是通过 element instanceOf OutlineElement
来判断点击的是否是某种需要的类型
// 面包屑上的打开
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.revealFocusedFromTreeAside',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.Enter,
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey),
handler(accessor) {
const editors = accessor.get(IEditorService);
const lists = accessor.get(IListService);
const element = <OutlineElement | IFileStat>lists.lastFocusedList.getFocus();
if (element instanceof OutlineElement) {
// open symbol in editor
return editors.openEditor({
resource: OutlineModel.get(element).textModel.uri,
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
}, SIDE_GROUP);
面向对象 多个implements
其实是各种定义interface,来约束类的实现,定义interface也帮助仔细想清楚设计哪些能力
面向对象继承
存在的问题
- 修改麻烦。增加新的属性,每个类都要修改
- 对人的要求太高了。设计的不好,会混乱不堪
export interface ICodeEditor extends editorCommon.IEditor {}
export interface IActiveCodeEditor extends ICodeEditor {}
较高层次抽象
将整个工作区抽象为几个较大的区块,并通过service模式来互相交互
- sidebarPart
- editorService
- StatusbarPart IStatusbarService
- TitlebarPart
问题
- 面向对象,修改不容易
- 没有类似jsx或vue的模板,整个界面看起来不清晰
设计模式
- 依赖注入
- 注册器模式
依赖注入
依赖注入在整个程序中占了很大的部分
export class OpenWorkspaceConfigFileAction extends Action {
static readonly ID = 'workbench.action.openWorkspaceConfigFile';
static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File");
constructor(
id: string,
label: string,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
@IEditorService private editorService: IEditorService
) {
super(id, label);
this.enabled = !!this.workspaceContextService.getWorkspace().configuration;
}
run(): Thenable<any> {
return this.editorService.openEditor({ resource: this.workspaceContextService.getWorkspace().configuration });
}
}
注册器模式
快捷键的注册模式
KeybindingsRegistry.registerCommandAndKeybindingRule
ListService的源码
export class ListService implements IListService {
private lists: IRegisteredList[] = [];
private _lastFocusedWidget: ListWidget | undefined = undefined;
get lastFocusedList(): ListWidget | undefined {
return this._lastFocusedWidget;
}
register(widget: ListWidget, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {
// 加入列表中
// 监听widget的状态,如销毁,focus等状态变更,修改lists和_lastFocusedWidget状态
}
}
使用ListService
// 面包屑上的打开
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'breadcrumbs.revealFocusedFromTreeAside',
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyCode.Enter,
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey),
handler(accessor) {
const editors = accessor.get(IEditorService);
const lists = accessor.get(IListService);
const element = <OutlineElement | IFileStat>lists.lastFocusedList.getFocus();
if (element instanceof OutlineElement) {
// open symbol in editor
return editors.openEditor({
resource: OutlineModel.get(element).textModel.uri,
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
}, SIDE_GROUP);
为什么不卡
插件的设计
- 如何在插件中获取到vscode对象
- 和插件的通信方式
Typescript的一些用法
- implements 实现接口的约束,定义了非常多的协议
extHost.protocol.ts
- private 的声明
- @符号 什么意思?依赖注入时使用的标记
- 面向对象来判断属于什么类型
if (this.element instanceof FileElement) {
} else if (this.element instanceof OutlineModel) {
}
借鉴
Visual Studio Code有哪些工程方面的亮点 - 知乎
微软 VSCode IDE 源码分析揭秘 - 知乎
从 VSCode 看大型 IDE 技术架构 - 知乎
vscode编辑器打开大项目能够快速预览,这是如何做到的?软件算法比atom做的好? - 知乎
vscode 定制开发 —— Workbench 源码解读及实战 | 匠心博客
vscode 定制开发 —— 基础准备 | 匠心博客
vscode源码剖析 | xzper
Visual Studio Code / Egret Wing 技术架构:基础 · Chen’s Blog
Visual Studio Code / Egret Wing 技术架构:插件系统 · Chen’s Blog
What I Learned by Building My Own VS Code Extension | CSS-Tricks