Read OSS

VS Code 的架构:探索一个拥有 5,600 个文件的 TypeScript 代码库

中级

前置知识

  • TypeScript 基础知识(泛型、模块)
  • 作为用户对 VS Code 的基本了解

VS Code 的架构:探索一个拥有 5,600 个文件的 TypeScript 代码库

VS Code 是目前体量最大的开源 TypeScript 项目之一。整个代码库包含约 5,700 个源文件、数百个服务,并同时支持桌面端 Electron、Web 浏览器和远程无界面环境——全部来自同一套代码。如此规模的项目,本可能是一场噩梦,但事实并非如此。原因在于,代码库执行了一套严格的、基于约定的架构,仅凭文件路径就能推断出其用途。本文将帮助你建立导航这个代码库的思维模型。

代码仓库概览:5,700 个文件里都有什么?

在深入 src/ 之前,先从顶层目录开始了解整体结构。仓库被组织为若干主要目录,各司其职:

目录 用途
src/ 全部应用源代码——编辑器、workbench、平台服务和入口点
extensions/ 随 VS Code 一同发布的内置扩展(TypeScript、Git、Markdown、主题等)
build/ 构建工具——Gulp 任务、分层检查器、代码规范脚本、打包工具
cli/ 基于 Rust 的 VS Code CLI(code tunnel 及远程服务器管理)
test/ 集成测试和冒烟测试(单元测试与源码并列放在 src/ 中)
resources/ 平台特定资源——图标、shell 脚本、.desktop 文件

我们最关心的代码几乎全部位于 src/vs/ 下。编辑器核心、workbench、所有平台服务以及各种胶水代码都在这里。src/vs/ 之外的内容,要么是基础设施,要么是打包进来的扩展。

提示: 如果你想了解某个功能的实现原理,从 src/vs/workbench/contrib/ 开始找准没错。终端、SCM、调试器、AI 对话等几乎所有用户可见的功能,都以独立 contribution 的形式存放在这里。

四大支柱:base、platform、editor、workbench

src/vs/ 目录被组织为四个主要层次,每一层都建立在下一层之上:

graph BT
    A["<b>base</b><br/>Generic utilities, data structures,<br/>UI primitives"] --> B["<b>platform</b><br/>Service interfaces, DI, configuration,<br/>files, logging, storage"]
    B --> C["<b>editor</b><br/>Monaco editor — text model,<br/>view, contributions"]
    C --> D["<b>workbench</b><br/>Full IDE shell — layout, parts,<br/>extensions, contrib features"]
    
    style A fill:#e8f5e9
    style B fill:#e3f2fd
    style C fill:#fff3e0
    style D fill:#fce4ec

依赖规则严格且单向:

  • base/ 不包含任何 VS Code 特有的依赖,只有通用的 TypeScript 工具:数据结构(LinkedListGraphTernarySearchTree)、异步原语(BarrierThrottlerRunOnceScheduler)、Disposable 生命周期模式、Event/Emitter 系统、浏览器 DOM 辅助工具以及 IPC 抽象。base/ 完全可以单独发布为一个 npm 包。

  • platform/ 基于 base/ 构建,定义了所有横切关注点的服务接口:依赖注入、配置、文件系统、日志、存储、遥测、主题、扩展管理。这些是编辑器和 workbench 所依赖的核心契约。

  • editor/ 基于 base/platform/ 构建,即 Monaco 编辑器——一个独立的、可嵌入的代码编辑器,拥有自己的 contribution 系统、文本模型(piece table)、视图层以及 40 余个内置功能。Monaco 以独立包的形式发布到 npm,并驱动着 Monaco Editor 演示站点。

  • workbench/ 基于以上三层构建,是完整的 IDE 外壳:布局、侧边栏、面板、活动栏、状态栏、编辑器分组,以及所有主要功能(终端、调试器、SCM、搜索、AI 对话、扩展市场)。正是这一层,将 Monaco 从「代码编辑器」升级为「完整 IDE」。

分层系统:common、browser、node、electron-*

在每个支柱内部,代码还会按照目标运行环境进一步划分,通过子目录约定来体现:

graph LR
    subgraph "Environment Layers"
        COMMON["<b>common/</b><br/>Pure TypeScript<br/>No DOM, no Node"]
        BROWSER["<b>browser/</b><br/>DOM APIs allowed<br/>Runs in browser or Electron renderer"]
        NODE["<b>node/</b><br/>Node.js APIs allowed<br/>Server-side only"]
        EM["<b>electron-main/</b><br/>Electron main process APIs"]
        EB["<b>electron-browser/</b><br/>Electron renderer + Node integration"]
    end

    COMMON --> BROWSER
    COMMON --> NODE
    COMMON --> EM
    COMMON --> EB
    NODE --> EM
    BROWSER --> EB

这些规则构成了一张约束矩阵:

层次 可以引入 不可引入
common/ common/ browser/node/electron-*
browser/ common/browser/ node/electron-*
node/ common/node/ browser/electron-*
electron-main/ common/node/electron-main/ browser/electron-browser/
electron-browser/ common/browser/electron-browser/ node/electron-main/

正是这套机制,使 VS Code 的 Web 版本(vscode.dev)成为可能。所有 common/browser/ 代码不依赖 Node.js 或 Electron,因此可以在纯浏览器环境中运行;node/electron-* 层只在构建桌面应用时才会被引入。

提示: 如果你编写的代码需要同时兼容桌面端和 Web 端,请放在 common/browser/ 中。一旦不小心引入了 Node.js API,分层检查器会立刻告警。

构建时的自动化检查

这套约定不只是规范建议——它由自动化工具强制执行。主要的检查机制是 build/checker/layersChecker.ts,它通过基于 glob 的规则,将文件路径映射到不允许使用的类型:

flowchart TD
    A["layersChecker.ts"] --> B["Load TypeScript program<br/>from src/tsconfig.json"]
    B --> C["For each source file,<br/>match against RULES"]
    C --> D{Rule matched?}
    D -->|Skip test files| E["Continue"]
    D -->|Check rule| F["Walk AST identifiers"]
    F --> G{Type in<br/>disallowedTypes?}
    G -->|Yes| H["Report violation<br/>Exit code 1"]
    G -->|No| E

检查器会扫描 TypeScript AST 中的每一个标识符,确保 NativeParsedArgsINativeEnvironmentServiceINativeHostService 等类型——它们虽然定义在 common/ 中,但在语义上代表原生平台概念——不会出现在 common/browser/worker/ 代码中:

build/checker/layersChecker.ts#L26-L35

第二道防线是针对各层的独立 TypeScript 配置。浏览器层的配置文件 build/checker/tsconfig.browser.json 只包含 common/browser/ 的源文件,并将 "types" 设为 [] 以完全排除 Node.js 类型定义。如果浏览器层代码试图引用 fschild_process,TypeScript 本身就会报错,根本不需要额外的自定义检查器。

flowchart LR
    subgraph "Layer Enforcement"
        LC["layersChecker.ts<br/>(AST type scanning)"]
        TC["tsconfig.browser.json<br/>(TypeScript type exclusion)"]
        CI["CI Pipeline"]
    end
    LC --> CI
    TC --> CI
    CI -->|Fail on violation| BLOCK["PR Blocked"]

electron-main/ 层还有一条专属规则:禁止直接使用 ipcMain,必须改用更安全的 validatedIpcMain 封装。这是一个很好的例子,说明检查器不仅能约束平台分层,还能落地项目特定的安全规范。

Barrel 文件与按需加载

面对数千个文件和严格的分层体系,VS Code 是如何知道在每个目标平台上应该加载哪些服务和功能的?答案是 barrel 文件——这些大型纯导入模块充当了功能清单的角色。

三个关键的 barrel 文件分别是:

  • src/vs/workbench/workbench.common.main.ts — 导入桌面端与 Web 端共享的所有内容:编辑器核心、workbench 操作、API 扩展点、workbench 各部件以及平台无关的服务。

  • src/vs/workbench/workbench.desktop.main.ts — 先导入 workbench.common.main.ts,再叠加 Electron 特有的服务:原生文件对话框、原生菜单、桌面生命周期服务、原生剪贴板、加密功能等。

  • src/vs/workbench/workbench.web.main.ts — 同样先导入 workbench.common.main.ts,再叠加浏览器特有的实现:Web 搜索服务、浏览器文本文件服务、Web 键盘布局、浏览器生命周期以及基于 Web 的扩展管理。

graph TD
    COMMON["workbench.common.main.ts<br/><i>Editor core, shared services,<br/>all contrib features</i>"]
    DESKTOP["workbench.desktop.main.ts<br/><i>Electron services:<br/>native dialogs, menus, lifecycle</i>"]
    WEB["workbench.web.main.ts<br/><i>Browser services:<br/>web search, browser lifecycle</i>"]
    
    DESKTOP --> COMMON
    WEB --> COMMON
    
    style COMMON fill:#e8f5e9
    style DESKTOP fill:#e3f2fd
    style WEB fill:#fff3e0

这种模式设计得相当优雅:公共 barrel 文件保证了功能的一致性,各平台 barrel 文件则负责替换为环境适配的具体实现。想了解桌面版和 Web 版 VS Code 的差异?直接对比两个平台 barrel 文件的内容即可。

目录速查:如何在 src/vs/ 中找到你想要的

以下是最重要子目录的快速参考:

路径 内容
src/vs/base/common/ Disposable、Event/Emitter、数据结构、异步工具
src/vs/base/browser/ DOM 辅助工具、UI 组件(grid、tree、list、sash)
src/vs/base/parts/ipc/ 支持所有传输类型的 IPC channel 抽象
src/vs/platform/instantiation/ 依赖注入——createDecoratorInstantiationService
src/vs/platform/files/ 文件系统服务接口与 provider
src/vs/platform/configuration/ 设置与配置系统
src/vs/editor/common/ 文本模型(piece table)、语言模式、光标逻辑
src/vs/editor/browser/ 编辑器视图、GPU 渲染、widget 系统
src/vs/editor/contrib/ 40 余个编辑器功能:查找、折叠、悬停提示、代码补全等
src/vs/workbench/browser/ Workbench 外壳:布局、各部件、Workbench
src/vs/workbench/contrib/ 主要 IDE 功能:终端、调试器、SCM、AI 对话、搜索
src/vs/workbench/api/ 扩展宿主协议与 vscode.* API 实现
src/vs/workbench/services/ Workbench 级服务:扩展、主题、生命周期
src/vs/code/electron-main/ Electron 主进程:CodeMainCodeApplication
src/vs/code/electron-browser/ Electron preload 脚本
src/vs/server/ 远程开发服务器

下一步

有了这份全局地图,接下来的文章将追踪从 src/main.ts 到第一个可见编辑器窗口的完整路径。我们将跨越进程边界,沿着启动序列从 Electron 主进程一路走到渲染进程,并深入观察刚才介绍的分层系统是如何决定各进程中服务实例化顺序的。