Dive Into Code: VSCode 源碼閱讀(一)

做者簡介 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

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,一般也負責創建客戶對象

而依賴注入的實現有幾種形態,其中常見的一種的構造函數式的依賴注入: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 源代碼和大致組織狀況。

下篇預告:從命令行輸入code命令到出現 VSCode 桌面應用程序,VSCode 的代碼是的執行流程是怎樣的?

對咱們團隊感興趣的能夠關注專欄,關注github或者發送簡歷至'tao.qit####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

相關文章
相關標籤/搜索