Dive Into Code_ VSCode 源码阅读(一)

发布于 2025-01-11 01:33:46 字数 10531 浏览 5 评论 0

VS Code 是一款新的工具,它将代码编辑器的简洁和程序开发人员在开发-构建-调试流程中所需要的工具结合在一起。Code 提供了全面的编辑和调试功能支持、一个可扩展的模型、和与现有工具的轻量化集成。

这是 VSCode Github 仓库上的介绍,如今,VSCode 的 Github Star 数已达 4.7 万,VSCode 采用了 Electron,使用的代码编辑器名为 Monaco、Monaco 也是 Visual Studio Team Service(Visual Studio Online)使用的代码编辑器,在语言上,VSCode 使用了自家的 TypeScript 语言开发。

在开始 VSCode 本身源码的解析之前,首先来看 VSCode 依赖的 Electron,理解了 Electron 可以更好的理解 VSCode 的代码组织和依赖关系;其次是在 VSCode 源码中使用到的的依赖注入模式。

Electron

Electron 是一款可以前端使用 HTML、JavaScript 和 CSS 开发桌面应用程序的框架,关于 Electron 介绍的资料很多。我们可以看看 Electron 官网提供的快速启动应用程序实例:

其中 package.json 定义如下,注意其中的 main 字段和 start 脚本:执行 npm start 即启动这个 Electron 应用:

{
    "name": "electron-quick-start",
    "version": "1.0.0",
    "description": "A minimal Electron application",
    "main": "main.js",
    "scripts": {
        "start": "electron ."
    },
    "repository": "https://github.com/electron/electron-quick-start",
    "keywords": ["Electron", "quick", "start", "tutorial", "demo"],
    "author": "GitHub",
    "license": "CC0-1.0",
    "devDependencies": {
        "electron": "~1.7.8"
    }
}

然后看 main.js 脚本:

const electron = require('electron');
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;

const path = require('path');
const url = require('url');

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

function createWindow() {
    // Create the browser window.
    mainWindow = new BrowserWindow({ width: 800, height: 600 });

    // and load the index.html of the app.
    mainWindow.loadURL(
        url.format({
            pathname: path.join(__dirname, 'index.html'),
            protocol: 'file:',
            slashes: true,
        }),
    );

    // Open the DevTools.
    // mainWindow.webContents.openDevTools()

    // Emitted when the window is closed.
    mainWindow.on('closed', function() {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
    });
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', function() {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('activate', function() {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (mainWindow === null) {
        createWindow();
    }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

可以看到, main 脚本主要定义了应用对几个事件的处理函数,其中对 ready 事件的处理函数中,创建了一个 BrowseWindow 对象,并且去加载 index.html 页面。

index.html 中,又通过 script 标签去加载了 renderer.js 脚本:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <!-- All of the Node.js APIs are available in this renderer process. -->
    We are using Node.js <script>document.write(process.versions.node)</script>,
    Chromium <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.

    <script>
      // You can also require other files to run in this process
      require('./renderer.js')
    </script>
  </body>
</html>

到此,Electron 的快速启动实例应用程序就完成了,执行 npm start 后,就可以看到界面上展示 index.html 中的内容了。

我们首先需要了解的是在上面 Electron 应用中会遇到的两种进程类型,以及它们的区别,它们称为主进程和渲染进程。

首先看主进程和渲染进程的定义:

在 Electron 应用中, package.json 中的 main 脚本运行所在的进程被称为主进程,在主进程中运行的脚本通过创建 web 页面来展示用户界面。一个 Electron 应用总是有且只有一个主进程。由于 Electron 使用了 Chromium 来展示 web 页面,所以在 Chromium 的多进程架构也被使用到。每个 Electron 中的 web 页面运行在它自己的渲染进程中。在普通的浏览器中,web 页面通常在一个沙盒环境中运行,不被允许去接触原生的资源。然而 Electron 的用户在 Node.js 的 API 支持下可以在页面中和操作系统进行一些底层交互。

主进程和渲染进程之间的区别:

主进程使用 BrowseWindow 实例创建页面,每个 BrowseWindow 实例都在自己的渲染进程里运行页面。当一个 BrowseWindow 实例被销毁后,相应的渲染进程也会被终止。主进程管理所有的 web 页面和它们对应的渲染进程。每个渲染进程都是独立的,它只关心它所运行的 web 页面。

对开发者来说,比较关心的是主进程和渲染进程中的脚本分别可以使用哪些 API。

首先 Electron API 提供了丰富的 API,其中一些 API 只能在主进程中使用,又有一些 API 只能在渲染进程中使用,而有一些主进程和渲染进程都可以使用。

然后对于 Node.js 的 API,以及第三方 npm 包,主进程和渲染进程都可以直接使用。

最后,由于渲染进程运行在 chromium 的页面中,所有还可以是有浏览器提供的 API,如 DOM 操作 API 等。

API主进程渲染进程
Electron API部分部分
Node.js API/module
浏览器 API

在了解了 Electron 之后,后面我们会看到 VSCode 中哪些代码是运行在主进程中,哪些代码是运行在渲染进程中。

依赖注入

依赖注入作为一个设计模式,前端开发者可能使用的不多,但在 VSCode 的源码中随处可见,所以这里简单介绍下。首先看依赖注入的定义:

在软件工程中,依赖注入是一种为一类对象提供依赖的对象的设计模式。被依赖的对象称为 Service ,注入则是指将被依赖的对象 Service 传递给使用服务的对象(称为 Client ),从而客户 Client 不需要主动去建立(new) 依赖的服务 Service ,也不需要通过工厂模式去获取依赖的服务 Service

在典型的依赖注入模式中,存在以下几类角色:

  • 被依赖和使用的对象,即 Service
  • 使用服务的客户对象,即 Client
  • 客户使用服务的接口定义, Interface
  • 注入器:负责建立服务对象并提供给 Client,通常也负责建立客户对象

而依赖注入的实现有几种形态,其中常见的一种的构造函数式的依赖注入:Client 在其构造函数的参数中申明所依赖的 Service,如下 TypeScript 代码所示:

class Client {
    constructor(serviceA: ServiceA, serviceB: ServiceB) {
        // 注入器在建立 Client 的时候,将依赖的 Service 通过构造函数参数传递给 Client
        // Client 此时即可将依赖的服务保存在自身状态内:
        this.serviceA = serviceA;
        this.serviceB = serviceB;
    }
}

通过这种模式,Client 在使用的时候不需要去自己构造需要的 Service 对象,这样的好处之一就就是将对象的构造和行为分离,在引入接口后,Client 和 Service 的依赖关系只需要接口来定义,Client 在构造函数参数中主需要什么依赖的服务接口,结合注入器,能给客户对象更多的灵活性和解耦。

最后,在 VSCode 的源码中,大部分基础功能是被实现为服务对象,一个服务的定义分为两部分:

  • 服务的接口
  • 服务的标识:通过 TypeScript 中的装饰器实现

Client 在申明依赖的 Service 时,同样时在构造函数参数中申明,实例如下:

class Client {
    constructor(
        @IModelService modelService: IModelService,
        @optional(IEditorService) editorService: IEditorService,
    ) {
        // ...
        this.modelService = modelService;
        this.editorService = editorService;
    }
}

这里,申明的客户对象 Client ,所依赖的 ServiceIModelServiceIEditorService ,其中装饰器 @IModelService 是 ModelService 的标识,后面的 IModelService 只是 TypeScript 中的接口定义; @optional(IEditorService) 是 EditorService 的标识,同时通过 optional 的装饰申明为可选的依赖。

最后,在代码中实际使用 Client 对象时,需要通过注入器提供的 instantiationService 来实例化的到 Client 的实例:

const myClient = instantiationService.createInstance(Client);

源码组织

在了解了 Electron 和依赖注入之后,我们就可以来看看 VSCode 自身的源代码组织了。

VSCode Core

首先 VSCode 整体由其核心 core 和内置的扩展 Extensions 组成, core 是实现了基本的代码编辑器、和 VSCode 桌面应用程序,即 VSCode workbench;同时提供扩展 API,允许内置的扩展和第三方开发的扩展程序来扩展 VSCode Core 的能力。

首先看 Core 的源码组织, Core 的源代码分为下列目录:

  • src/vs/base : 定义基础的工具方法和基础的 DOM UI 控件
  • src/vs/code : Monaco Editor 代码编辑器:其中包含单独打包发布的 Monaco Editor 和只能在 VSCode 的使用的部分
  • src/vs/platform : 依赖注入的实现和 VSCode 使用的基础服务 Services
  • src/vs/workbench : VSCode 桌面应用程序工作台的实现
  • src/vs/code : VSCode Electron 应用的入口,包括 Electron 的主进程脚本入口

其次,由于 VSCode 依赖 Electron,而在上述我们提到了 Electron 存在着主进程和渲染进程,而它们能使用的 API 有所不到,所以 VSCode Core 中每个目录的组织也按照它们能使用的 API 来组织安排。在 Core 下的每个子目录下,按照代码所运行的目标环境分为以下几类:

  • common : 只使用 JavaScript API 的源代码,可能运行在任何环境
  • browser : 需要使用浏览器提供的 API 的源代码,如 DOM 操作等
  • node : 需要使用 Node.js 提供的 API 的源代码
  • electron-browser : 需要使用 Electron 渲染进程 API 的源代码
  • electron-main : 需要使用 Electron 主进程 API 的源代码

按照上述规则,即 src/vs/workbench/browser 中的源代码只能使用基本的 JavaScript API 和浏览器提供的 API,而 src/vs/workbench/electron-browser 中的源代码则可以使用 JavaScript API,浏览器提供的 API、 Node.js 提供的 API、和 Electron 渲染进程中的 API。

VSCode Extensions

在 VSCode 代码仓库中,除了上述的 src/vsCore 之外,还有一大块即 VSCode 内置的扩展,它们源代码位于 extensions 内。

首先 VSCode 作为代码编辑器,但与各种代码编辑的功能如语法高亮、补全提示、验证等都是有扩展实现的。所以在 VSCode 的内置扩展内,一大部分都是各种编程语言的支持扩展,如: extensions\htmlextensions\javascriptextensions\cpp 等等,大部分语言扩展中都会出现如 .tmTheme.tmLanguage 等 TextMate 的语法定义。

还有一类内置的扩展是 VSCode 主体扩展,如 VSCode 默认主体 extensions/theme-defaults 等。

参考


本篇简单了介绍了在 VSCode 源码阅读之前的需要的一些准备工作,主要是 Electron 应用的结构、依赖注入设计模式、和 VSCode 源代码和大体组织情况。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

文章
评论
25 人气
更多

推荐作者

xiaolangfanhua

文章 0 评论 0

qq_r4YSZQ

文章 0 评论 0

1PKOH46yx8j0x

文章 0 评论 0

酷到爆炸

文章 0 评论 0

梦言归人

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文