vscode 源码解析 - 源码组织
Visual Studio Code consists of a layered and modular core
(found as src/vs
) that can be extended using extensions. Extensions are run in a separate process referred to as the extension host.
Extensions are implemented by utilising the extension API. Built-in extensions can be found in the extensions
folder.
root directory
vscode # package name: code-oss-dev
├─.configurations # 编译 VS Code 所需要的系统配置说明
├─.devcontainer # Dev Containers 配置文件及文档,用于VS Code的远程开发环境
├─.eslintplugin # VS Code 专用的 ESLint 插件, 用于规范代码
├─.git # Git仓库
├─.github # GitHub 仓库配置
├─.vscode # VS Code的配置文件
├─build # gulp 构建脚本文件夹, package name: code-oss-dev-build
├─cli # Enhanced VS Code CLI, 增强的命令行工具源码 cargo name: code-cli
├─extensions # 扩展插件文件夹,内置插件 package name: vscode-extensions
├─node_modules # npm依赖包
├─out* # build 输出文件夹
├─remote # vscode-reh(Remote Extension Host) 插件, package name: vscode-reh
├─resources # 资源文件夹,可能包含图片、样式等
├─scripts # 脚本文件夹
├─src # 源代码文件夹
├─test # 测试文件夹
├─.editorconfig # 编辑器配置文件,确保代码风格一致性
├─.eslintignore # ESLint忽略文件列表
├─.eslintrc.json # ESLint配置文件
├─.git-blame-ignore-revs # 忽略提交记录
├─.gitattributes # Git属性文件
├─.gitignore # Git忽略文件
├─.lsifrc.json # 语言服务器索引格式配置文件
├─.mailmap # 主要开发者邮件映射文件,用于Git提交者邮箱地址的规范化
├─.mention-bot # `mention-bot` 配置文件
├─.npmrc # npm配置文件
├─.nvmrc # Node.js版本配置文件
├─.vscode-test.js # VS Code测试脚本
├─cglicenses.json # 许可证信息文件
├─cgmanifest.json # 依赖清单文件
├─CodeQL.yml # CodeQL安全扫描配置文件
├─CONTRIBUTING.md # 贡献者指南
├─gulpfile.js # Gulp任务配置文件
├─LICENSE.txt # 许可证文件
├─migrate.mjs # AMD-TO-ESM MIGRATION SCRIPT
├─package-lock.json # npm包锁定文件
├─package.json # npm包配置文件
├─product.json # 产品配置文件
├─README.md # 项目说明文档
├─SECURITY.md # 安全策略文档
├─ThirdPartyNotices.txt # 第三方依赖通知文件
└─tsfmt.json # TypeScript格式化配置文件
src directory
src
├─typings # 公共基础类型
├─vs # 分层和模块化的 core 源代码
├─vscode-dts # api 类型声明
├─bootstrap-amd.js # amd loader associated
├─bootstrap-cli.js # delete `VSCODE_CWD` env var
├─bootstrap-fork.js #
├─bootstrap-import.js
├─bootstrap-meta.js # 获取 `product.json` 和 `package.json` 元数据
├─bootstrap-node.js
├─bootstrap-server.js # delete `ELECTRON_RUN_AS_NODE` env var
├─bootstrap-window.js
├─cli.js
├─main.js # VS Code 启动入口
├─package.json
├─server-cli.js
├─server-main.js
├─tsconfig.base.json
├─tsconfig.json
├─tsconfig.monaco.json
├─tsconfig.tsec.json
├─tsconfig.vscode-dts.json
├─tsconfig.vscode-proposed-dts.json
└─tsec.exemptions.json
Layers
The core
is partitioned into the following layers:
base
: Provides general utilities and user interface building blocks that can be used in any other layer.platform
: Defines service injection support and the base services for VS Code that are shared across layers such asworkbench
andcode
. Should not includeeditor
orworkbench
specific services or code.editor
: The "Monaco" editor is available as a separate downloadable component.workbench
: Hosts the "Monaco" editor, notebooks and custom editors and provides the framework for panels like the Explorer, Status Bar, or Menu Bar, leveraging Electron to implement the VS Code desktop application and browser APIs to provide VS Code for the Web.code
: The entry point to the desktop app that stitches everything together, this includes the Electron main file, shared process, and the CLI for example.server
: The entry point to our server app for remote development.
src/vs
├─base # 基础 utils 和用户界面构建块
├─code # VSCode desktop app 应用主入口
├─editor # "Monaco" editor 组件
├─platform # 依赖注入服务支持和基础依赖注入服务
├─server # VSCode server app 应用主入口
├─workbench # 整体视图框架
├─amdX.ts #
├─css.build.ts
├─css.ts
├─loader.d.ts
├─loader.js
├─monaco.d.ts
├─nls.messages.ts
└─nls.ts
Target Environments
The core
of VS Code is fully implemented in TypeScript. Inside each layer the code is organised by the target runtime environment. This ensures that only the runtime specific APIs are used. In the code we distinguish between the following target environments:
common
: Source code that only requires basic JavaScript APIs and run in all the other target environmentsbrowser
: Source code that requires Web APIs, eg. DOM- may use code from:
common
- may use code from:
node
: Source code that requires Node.JS APIs- may use code from:
common
- may use code from:
electron-sandbox
: Source code that requires thebrowser
APIs like access to the DOM and a small subset of APIs to communicate with the Electron main process (anything exposed fromsrc/vs/base/parts/sandbox/electron-sandbox/globals.ts
- may use code from:
common
,browser
,electron-sandbox
- may use code from:
electron-utility
: Source code that requires the Electron utility-process APIs- may use code from:
common
,node
- may use code from:
electron-main
: Source code that requires the Electron main-process APIs- may use code from:
common
,node
,electron-utility
- may use code from:
src/vs/xxx
├─common # 仅使用 basic JavaScript APIs
├─browser # 使用了 Web API(DOM等). 依赖 common
├─node # 使用了 Node.JS APIs. 依赖 common
├─electron-main # 使用了 Electron main-process APIs. 依赖 common, node, electron-utility
├─electron-sandbox # 使用了 browser APIs 和 部分 Electron main process
├─electron-utility # 使用了 Electron utility-process APIs. 依赖 common, node.
└─test # Unit tests
Dependency Injection
Consuming a service
The code is organised around services of which most are defined in the platform
layer. Services get to its clients via constructor injection
.
A service definition is two parts: (1) the interface of a service, and (2) a service identifier - the latter is required because TypeScript doesn't use nominal but structural typing. A service identifier is a decoration (as proposed for ES7) and should have the same name as the service interface.
Declaring a service dependency happens by adding a corresponding decoration to a constructor argument. In the snippet below @IModelService
is the service identifier decoration and IModelService
is the (optional) type annotation for this argument.
class Client {
constructor(
@IModelService modelService: IModelService
) {
// use services
}
}
Use the instantiation service to create instances for service consumers, like so instantiationService.createInstance(Client)
. Usually, this is done for you when being registered as a contribution, like a Viewlet or Language.
Providing a service
The best way to provide a service to others or to your own components is the registerSingleton
-function. It takes a service identifier and a service constructor function.
registerSingleton(
ISymbolNavigationService, // identifier
SymbolNavigationService, // ctor of an implementation
InstantiationType.Delayed // delay instantiation of this service until is actually needed
);
Add this call into a module-scope so that it is executed during startup. The workbench will then know this service and be able to pass it onto consumers.
Editor
source organisation
- the
vs/editor
folder should not have anynode
orelectron-*
dependencies. vs/editor/common
andvs/editor/browser
- the code editor core (critical code without which an editor does not make sense).vs/editor/contrib
- code editor contributions that ship in both VS Code and the standalone editor. They depend onbrowser
by convention and an editor can be crafted without them which results in the feature brought in being removed.vs/editor/standalone
- code that ships only with the standalone editor. Nothing else should depend onvs/editor/standalone
vs/workbench/contrib/codeEditor
- code editor contributions that ship in VS Code.
src/vs/editor # should not have any `node` or `electron-*` dependencies.
├─browser # code editor core
├─common # code editor core
├─contrib # code editor contributions
├─standalone # code that ships only with the standalone editor
├─test # unit tests
├─editor.all.ts
├─editor.api.ts
├─editor.main.ts
└─editor.worker.ts
Workbench source organisation
The VS Code workbench (vs/workbench
) is composed of many things to provide a rich development experience. Examples include full text search, integrated git and debug. At its core, the workbench does not have direct dependencies to all these contributions. Instead, we use an internal (as opposed to real extension API) mechanism to contribute these contributions to the workbench.
In a nutshell, folders are organised as:
vs/workbench/{common|browser|electron-sandbox}
: workbench core that is as minimal as possiblevs/workbench/api
: the provider of thevscode.d.ts
API (both extension host and workbench implementations)vs/workbench/services
: workbench core services (should NOT include services that are only used invs/workbench/contrib
)vs/workbench/contrib
: workbench contributions (this is where most of your code should live, see below)
Contributions that are contributed to the workbench all live inside the vs/workbench/contrib
folder. There are some rules around the vs/workbench/contrib
folder:
- there cannot be any dependency from outside
vs/workbench/contrib
intovs/workbench/contrib
- every contribution should have a single
.contribution.ts
file (e.g.vs/workbench/contrib/search/browser/search.contribution.ts
) to be added to the main entry points for the product (see last paragraph for details)- if you add a new service which is only used from one
contrib
and not other components or workbench core, it is recommended to register the service from thecontrib
entrypoint file
- if you add a new service which is only used from one
- every contribution should expose its internal API from a single file (e.g.
vs/workbench/contrib/search/common/search.ts
) - a contribution is allowed to depend on the internal API of another contribution (e.g. the git contribution may depend on
vs/workbench/contrib/search/common/search.ts
) - a contribution should never reach into the internals of another contribution (internal is anything inside a contribution that is not in the single common API file)
- think twice before letting a contribution depend on another contribution: is that really needed and does it make sense? Can the dependency be avoided by using the workbench extensibility story maybe?
src/vs/workbench
├─api # the provider of workbench API in `vscode.d.ts`
├─browser # core code
├─common # core code
├─contrib # workbench contributions, most code lives here
├─electron-sandbox #
├─services # workbench core services
├─test
├─workbench.common.main.ts
├─workbench.desktop.main.css
├─workbench.desktop.main.ts
├─workbench.web.main.css
├─workbench.web.main.internal.ts
└─workbench.web.main.ts
VS Code for Desktop / VS Code for Web
We ship both to desktop via Electron and to the Web with the goal to share as much code as possible in both environments. Writing code that only runs in the one environment should be the exception, think twice before going down that path. Ideally the same code can run in both environments.
To distinguish the environments in the product we build, there are entry files that define all the dependencies depending on the environment:
src/vs/workbench/workbench.sandbox.main.ts
: for desktop only dependenciessrc/vs/workbench/workbench.web.main.ts
: for web only dependencies
Both depend on our core entry file:
src/vs/workbench/workbench.common.main.ts
: for shared dependencies
Here are some rules that apply:
- code that is shared should go into
workbench.common.main.ts
- code that is web only should go into
workbench.web.main.ts
- code that is desktop only should go into
workbench.desktop.main.ts
Be careful when introducing a service only for the desktop and not for the web: code that runs in the web that requires the service will fail to execute if you do not provide a related service for web. It is fine to ship two different implementations of a service when you use different strategies depending on the environment.
Note: Only code that is referenced from the main entry files is loaded into the product. If you have a file that is otherwise not referenced in your code, make sure to add the import to your .contribution.ts
file.