Visual Studio Code / Egret Wing 技术架构:基础

系列目录

VS Code 简单介绍

Visual Studio Code (下面简称VSC) 是由微软公司开发的开源、免费、跨平台的代码编辑器,微软希望它在保持核心轻量化文本编辑器的基础上,为编辑器添加项目支持、智能感知和编译调试。

VSC Team 由著名工程师 Erich Gamma 领导,Erich 是《设计模式》作者之一,Eclipse 之父,拥有多年的 IDE 开发经验。

Erich Gamma

VSC 的前身是微软基于云端的编辑器项目:Monaco 编辑器,它作为微软云服务的一部分,提供在线编辑源代码的能力。

Monaco 编辑器

由于云端编辑器的种种限制,和微软近年来对Windows外平台的态度转变,微软决定由它扩展开发为一个全平台通用的代码编辑器。

VSC 技术路线

Electron

为了保护本地文件的安全性,浏览器都没有提供直接的本地文件访问权限。为了实现本地文件系统的访问,VSC 采用了 Github 的开源项目 Electron。

Electron 原名 Atom-Shell,是 Github 为 Atom 编辑器编写的一个开源框架。它将 Chromium 和 Node.js 完美融合,让开发者能用使用 HTML 来实现 App UI,用 Node.js API 来访问文件系统。

TypeScript

VSC 的主要代码都是用 TypeScript 编写,目前 VSC 的核心有 1100 多个 TS 文件,TypeScript 的语言优势为多次重构提供了保障。

纯 DOM 操作

为了保证 UI 响应速度,VSC 没有采用现有的 UI 库,大部分 UI 采用绝对尺寸,简单粗暴的避免大面积 UI 的联动刷新。

VSC 为优化性能做的努力

VSC team 做了很多工作来提高 VSC 的性能,我们来看一下:

启动速度优化

基于 HTML 技术的编辑器,受限于 Chrome 一般都会有启动速度慢的问题。除了基本的 JS / CSS 合并压缩外,VSC 还将特别常用的 ActivityBar icon 直接内嵌在了 css 中。但是即便如此,启动速度跟 Sublime Text 等编辑器还是有比较大的差距。

这里说一个技巧,当我们用 VSC 打开一个文件的时候,VSC 会默认启动一个新的 VSC 窗口,这样启动的时间比较长,我们可以通过设置全局设置项里的 "window.openFilesInNewWindow": false 来使用已经打开的 VSC 实例打开新文件,这样就几乎没有了等待的时间。

代码编辑器滚动虚拟化

让编辑器只渲染可见的部分,减小大文件编辑对浏览器的压力。同时配合 css translate3d 避免重复渲染没有改变的代码行。

着色速度优化

为了不重复发明轮子,VSC 采用了跟 TextMate 一样的代码着色分析语法。它是基于正则表达式的一套分析方案,虽然 JS 原生支持正则表达式,但为了更高的效率,VSC 使用了 C++ 编写的一套正则表达式引擎来提高效率。

多进程架构红利

前面讲的这些都只是一些小的优化,真正保证响应速度的还是多进程架构带来的优势。

VSC 的进程结构

VSC 采用多进程架构,VSC 启动后主要有下面的几个进程:

多进程

后台进程

后台进程是 VSC 的入口,主要负责管理编辑器生命周期,进程间通信,自动更新,菜单管理等。

我们启动 VSC 的时候,后台进程会首先启动,读取各种配置信息和历史记录,然后将这些信息和主窗口 UI 的 HTML 主文件路径整合成一个 URL,启动一个浏览器窗口来显示编辑器的 UI。后台进程会一直关注 UI 进程的状态,当所有 UI 进程被关闭的时候,整个编辑器退出。

此外后台进程还会开启一个本地的 Socket,当有新的 VSC 进程启动的时候,会尝试连接这个 Socket,并将启动的参数信息传递给它,由已经存在的 VSC 来执行相关的动作,这样能够保证 VSC 的唯一性,避免出现多开文件夹带来的问题。

编辑器窗口

编辑器窗口进程负责整个 UI 的展示。也就是我们所见的部分。UI 全部用 HTML 编写没有太多需要介绍的部分。

IO

项目文件的读取和保存由主进程的 NodeJS API 完成,因为全部是异步操作,即便有比较大的文件,也不会对 UI 造成阻塞。IO 跟 UI 在一个进程,并采用异步操作,在保证 IO 性能的基础上也保证了 UI 的响应速度。

插件进程

每一个 UI 窗口会启动一个 NodeJS 子进程作为插件的宿主进程。所有的插件会共同运行在这个进程中。

这样设计最主要的目的就是避免复杂的插件系统阻塞 UI 的响应。这要从JS和浏览器说起。

在大部分的操作系统中,显示器的刷新频率是 60 帧每秒,也就是说应用需要在 16.7 毫秒内完成所有的计算和 UI 刷新。HTML DOM 的速度向来为人诟病,留给JS的时间就更少了。所以要在这么短的时间内完成所有指令才能保证 UI 的响应速度。

但是事实上我们很难在这么短的时间内完成诸如对一万行代码进行着色这种任务。这就需要我们将这种耗时比较长的任务转移到其他的线程或进程来做,等耗时任务结束,再将结果返回给 UI 进程即可。

VSC 最近的版本中将所有的语言支持都改成了插件。包括之前在 UI 进程用 Web Worker 实现的 Markdown 语言支持。后面我会介绍一个典型的语言服务的工作方式。

但是将插件放在一个单独进程也有很明显的缺点,因为是一个单独的进程,而不是 UI 进程,所以没有办法直接访问 DOM 树,想要实时高效的改变 UI 变得很难,在 VSC 的扩展体系中几乎没有对 UI 进行扩展的 API。

Debug 进程

Debugger 插件跟普通的插件有一点区别,它不运行在插件进程中,而是在每次 debug 的时候由UI单独新开一个进程。

搜索进程

搜索是一个十分耗时的任务,VSC 也使用的单独的进程来实现这个功能,保证主窗口的效率。

将耗时的任务分到多个进程中,有效的保证了主进程的响应速度。