网站首页 > 技术文章 正文
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,所依赖的Service有IModelService和IEditorService,其中装饰器@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/vs的Core之外,还有一大块即 VSCode 内置的扩展,它们源代码位于extensions内。
首先 VSCode 作为代码编辑器,但与各种代码编辑的功能如语法高亮、补全提示、验证等都时有扩展实现的。所以在 VSCode 的内置扩展内,一大部分都是各种编程语言的支持扩展,如:extensions\html、extensions\javascript、extensions\cpp等等,大部分语言扩展中都会出现如.tmTheme、.tmLanguage等 TextMate 的语法定义。
还有一类内置的扩展是 VSCode 主体扩展,如 VSCode 默认主体extensions/theme-defaults等。
希望本文能帮助到您!
点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注 {我},享受文章首发体验!
每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”
原文链接:https://github.com/ProtoTeam/blog/blob/master/201804/3.md
作者:蚂蚁金服数据体验技术团队
猜你喜欢
- 2024-10-11 JavaScript实现的转盘抽奖html页面前端源码
- 2024-10-11 前端录屏 + 定位源码,帮你快速定位线上 bug
- 2024-10-11 css+JavaScript实现的二级导航菜单html页面前端源码
- 2024-10-11 交互问卷表单设计html页面前端源码
- 2024-10-11 web前端实战项目(免费送源码+视频)
- 2024-10-11 html5+css3做的响应式企业网站前端源码
- 2024-10-11 小程序源代码:实现一个简易版QQ的前端页面,文末有代码
- 2024-10-11 web前端:vue源码解析,vue-cli父子组件传递模板
- 2024-10-11 一个月学会web前端(免费送视频+源码)
- 2024-10-11 通过阅读源码,提高你的 JS 水平 如何快速阅读源码
你 发表评论:
欢迎- 498℃几个Oracle空值处理函数 oracle处理null值的函数
- 494℃Oracle分析函数之Lag和Lead()使用
- 493℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 481℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 471℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 467℃【数据统计分析】详解Oracle分组函数之CUBE
- 453℃Oracle有哪些常见的函数? oracle中常用的函数
- 446℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 最近发表
-
- Spring Boot跨域难题终结者:3种方案,从此告别CORS噩梦!
- 京东大佬问我,SpringBoot为什么会出现跨域问题?如何解决?
- 在 Spring Boot3 中轻松解决接口跨域访问问题
- 最常见五种跨域解决方案(常见跨域及其解决方案)
- Java Web开发中优雅应对跨域问题(java跨域问题解决办法)
- Spring Boot解决跨域最全指南:从入门到放弃?不,到根治!
- Spring Boot跨域问题终极解决方案:3种方案彻底告别CORS错误
- Spring Cloud 轻松解决跨域,别再乱用了
- Github 太狠了,居然把 "master" 干掉了
- IntelliJ IDEA 调试 Java 8,实在太香了
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端富文本编辑器 (47)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)