做者简介 zqlu 蚂蚁金服·数据体验技术团队javascript
VS Code 是一款新的工具,它将代码编辑器的简洁和程序开发人员在开发-构建-调试流程中所须要的工具结合在一块儿。Code 提供了全面的编辑和调试功能支持、一个可扩展的模型、和与现有工具的轻量化集成。html
这是 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 源码中使用到的的依赖注入模式。java
Electron 是一款能够前端使用 HTML、JavaScript 和 CSS 开发桌面应用程序的框架,关于 Electron 介绍的资料不少。咱们能够看看 Electron 官网提供的快速启动应用程序实例:node
其中package.json
定义以下,注意其中的main
字段和start
脚本:执行npm start
即启动这个 Electron 应用:git
{
"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
脚本:github
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
页面。web
在index.html
中,又经过 script 标签去加载了renderer.js
脚本:chrome
<!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
中的内容了。npm
咱们首先须要了解的是在上面 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 在其构造函数的参数中申明所依赖的 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 的源码中,大部分基础功能是被实现为服务对象,一个服务的定义分为两部分:
Client 在申明依赖的 Service 时,一样时在构造函数参数中申明,实例以下:
class Client {
constructor(
@IModelService modelService: IModelService,
@optional(IEditorService) editorService: IEditorService,
) {
// ...
this.modelService = modelService;
this.editorService = editorService;
}
}
复制代码
这里,申明的客户对象Client
,所依赖的Service
有IModelService
和IEditorService
,其中装饰器@IModelService
是 ModelService 的标识,后面的IModelService
只是 TypeScript 中的接口定义;@optional(IEditorService)
是 EditorService 的标识,同时经过optional
的装饰申明为可选的依赖。
最后,在代码是实际使用Client
对象时,须要经过注入器提供的instantiationService
来实例化的到 Client 的实例:
const myClient = instantiationService.createInstance(Client);
复制代码
在了解了 Electron 和依赖注入以后,咱们就能够来看看 VSCode 自身的源代码组织了。
首先 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 使用的基础服务 Servicessrc/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 代码仓库中,出了上述的src/vs
的Core
以外,还有一大块即 VSCode 内置的扩展,它们源代码位于extensions
内。
首先 VSCode 做为代码编辑器,但与各类代码编辑的功能如语法高亮、补全提示、验证等都时有扩展实现的。因此在 VSCode 的内置扩展内,一大部分都是各类编程语言的支持扩展,如:extensions\html
、extensions\javascript
、extensions\cpp
等等,大部分语言扩展中都会出现如.tmTheme
、.tmLanguage
等 TextMate 的语法定义。
还有一类内置的扩展是 VSCode 主体扩展,如 VSCode 默认主体extensions/theme-defaults
等。
本篇简单了介绍了在 VSCode 源码阅读以前的须要的一些准备工做,主要是 Electron 应用的结构、依赖注入设计模式、和 VSCode 源代码和大致组织状况。
下篇预告:从命令行输入code
命令到出现 VSCode 桌面应用程序,VSCode 的代码是的执行流程是怎样的?
对咱们团队感兴趣的能够关注专栏,关注github或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~