VSCode 插件开发指北(一) 结构与规范
AI summary
date
Aug 5, 2024
slug
vscode-1
status
Published
tags
技术
summary
本文介绍了 VSCode 插件开发的基本结构与规范,包括 VSCode 的架构、插件的能力、配置文件的结构(如 package.json)及其重要字段,以及插件逻辑的实现。插件运行在独立进程中,开发者需注册基本信息和功能贡献,并实现激活和销毁逻辑。
type
Post
VScode 基本结构
在正式进入 VScode 插件开发之前,我们先简单了解一下 VScode 本身。

1.1 基于 Electron 的 VScode(架构)
VScode 是基于 electron 构建的项目。其宏观上的运行结构如下图:
- Chromium: Blink(渲染能力) + Web API
- Node.js: 操作系统级别能力(文件读写,进程管理,网络管理)
- Native API: 特定平台的操作系统交互能力(对话框,窗口事件...)
1.2 进程结构(运行)
Electron 程序是多进程的。
- 主进程 Main Process: 应用启动第一个进程是主进程,负责原生 GUI, 窗口管理,控制整个应用的生命周期,访问底层操作系统 API
- 渲染进程 Renderer Process: 被主进程管理,每个窗口通常对应一个渲染进程,负责前端渲染,页面脚本执行。通过 IPC 与主进程通信。
- GPU 进程 (可选): GPU 加速任务
在 VScode 这个应用的场景中:
其结构示意图如下:

- 主进程:整个应用的生命周期,处理菜单等全局UI,快捷键
- 渲染进程:VSCode 主窗口的渲染,处理用户交互,文本编辑,文件树视图
- 拓展主机进程 Extension Host:隔离运行插件(可以有多个该进程,用于不同的拓展插件)
- LSP 进程: 关于 LSP 又是一个庞大的内容,它抽象了对语言的支持,比如代码补全,快速跳转,语法错误诊断...
- ....
VScode 运行时,活动监控截图:

VScode 插件运行在单独的进程中。
这样做明显的好处是提高了安全性(因为插件的来源是非信任的,假设插件崩了,VScode 本身应该保持稳定)。
当插件激活时,新的插件进程启动,执行插件逻辑。
热身结束,现在,我们正式开始学习插件开发:
从宏观上看,学习插件开发的两个关键是:
- 边界: 所谓边界,就是我们的插件能增强 VScode 一些什么?(显然取决于 VScode 提供的 api 的边界)
- 规范: 所谓规范,就是我们如何告诉 VScode 我们要干嘛?( 不妨想想如何你是设计者,你会如何做?常见的就是特定格式的配置文件解析 + 回调函数注册 )
我们围绕上面两个两个问题展开。
边界
2.1 VScode 本身是什么?
下面这个图比较清晰的说明了 VScode 的产品定位:
- Editor:轻量,文件管理,语言高亮,快捷键
- IDE:项目,语言能力(跳转,提示),Debug,构建集成,代码片段 ...

值得一提的是,VScode 将其编辑器能力封装成单独的库
Monaco
2.2 VScode 插件能做些什么
在探讨如何编写 VScode 插件之前,先来考虑插件可能有哪些能力,即他能拓展 VScode 什么?

- 语言支持:代码补全提示,格式化,跳转,代码检查...
- 主题配色:语法高亮,主题配置
- 编辑器增强:扩充编辑器的功能,举例:Auto Import(自动导入引用),Image preview(预览导入的图片)...
- VScode 通用功能:快捷键,命令,消息通知...
- UI 扩充: 侧边栏 Activity Bar , 底部 Panel, Editor 视图 ...(见上图)
- ...
总的来看,插件主要可以:
- 增强代码编辑体验(围绕文本编辑)
- 扩充 UI(嵌入一些自定义的 UI,可以在 SideBar , Panel, Editor 三个位置)
规范
VScode 本身是一个庞大的程序。如何拓展一个已有的程序?根据我们的经验也可以想到:
- 编辑配置文件(基本信息,插件激活前就要被使用)
- 注册回调函数(逻辑,插件激活时实际运行脚本)

3.1 配置文件(基本信息)
插件的配置文件就是项目根目录下的
package.json
文件很多配置都是见名知意,请看这一份配置文件:
- 基本信息(插件名,作者,版本,描述,图标,分类,标签)
name, version, publisher, description, author, icon ...
- 引擎和依赖(引擎负责标识插件适用的 VScode 版本,依赖则是该项目的依赖文件)
engines, dependencies, devDependencies ...
- 激活事件
该字段告诉 VScode 该插件什么时候激活
为什么需要设置激活事件?我们的插件应该“按需”加载,节省资源,所谓的需就是通过这个字段配置
举个🌰,这段配置表示当打开 json , md ,ts 类型文件时,插件激活(详细语法在下方文档)
- 入口文件
main
(指定了插件脚本的入口文件,见下文)
- 贡献点
contributes
(即拓展了什么功能)(划重点)
如下图,是 pitaya 插件在商店中展示的截图,` 功能贡献` 中展示了该插件的激活事件,贡献点。
这俩信息说明了插件在什么情况下会被激活(打开特定类型的文件?还是点了插件图标,又或者是通过什么快捷键,命令...),以及该插件都拓展了什么(拓展UI?增强语言?)

这里掏出几种 contributes 配置带大家感受一下:
新增一个 Command:

新增一个 MenuItem

总而言之,
contributes
字段是整个配置文件的核心,是告诉 VScode 我这个插件都想干啥。当然,这里只是告诉了 VScode 我们想做什么?而具体怎么做的,是通过
main
字段指定的脚本实现的关于更加完整详细,请看官方文档:
package.json 配置详情:
https://code.visualstudio.com/api/references/extension-manifest
激活事件配置详情:
https://code.visualstudio.com/api/references/activation-events
Contributes 字段配置详情:
https://code.visualstudio.com/api/references/contribution-points
3.2 回调函数(插件逻辑)
前文提到,可以在 package.json 中的 `main` 字段可声明入口文件路径,如:
小学二年级学过,回调函数的关键有俩,1. 运行时机 2. 调用者传入的参数。
3.2.1 运行时机
VScode 会根据
main
字段读取该文件夹下的两个函数:- 入口函数
reactive
:在插件激活时调用,用来声明插件本身的逻辑
- 出口函数
deactivate
:在插件销毁时调用,用来清理一些内存资源
🌰 举个例子:
3.2.2 VScode API
在插件中,你需要使用的 API 主要来自两个地方
- 导入 vscode
import * as vscode from 'vscode'
: vscode 环境交互通用功能
- 回调参数
context: VScode.xtensionContext
: 当前拓展实例的一些信息
我们统称为 VScode API
某种程度上,学习 VScode 插件开发的本质就是熟悉 VScode API 提供了哪些能力。
作为 VScode API 的用户,不妨先想想自己可能有哪些需求需要被 VScode API 满足:
- 命令的注册(配置文件告诉 vscode 我要新增一个怎样的命令,脚本文件告诉 vscode 当命令触发时我要干嘛)
- UI 的注册(配置文件告诉 vscode 我想在某个位置注册一个视图,脚本文件负责告诉 vscode 这个视图具体显示什么)
- 文件操作能力(读写用户 Workspace 中的文件)
- Workspace 配置的读写(即修改和读取用户的 VScode 配置)
- Editor 内容的读写(比如检测当前光标的文本,单词,格式化文本的能力)
- ....
是的,vscode API 都会满足你。下面举一些简单的🌰
- 注册一个命令:
context.subscriptions
值得一提的是,上方的
context.subscriptions
是一个用来释放资源的栈,vscode 会在插件销毁的时候调用栈中的函数,因此将释放资源的函数 push 进去吧。
比如在上面的代码中, vscode.commands.registerCommand
这个函数就会返回释放资源的函数。- 注册 webview 视图
大致看个眼熟就行,下文“视图”章节会补充细节
- 操作 workspace 配置
案例:注册两个 command 来读取和更新 vscode 配置
关于 VS Code API 详情请看 VCR:
https://code.visualstudio.com/api/references/vscode-api#api-namespaces-and-classes
一些个人感想
无论是学习框架,语言,一套解决方案...
本质上,我们都是他们的用户,作为用户,可以先想想自己有哪些需求要满足,带着这些需求再审视学习的内容,一些内容就会 “对号入座”,减少学习难度。
3.3 一个值得思考的问题
为什么在
package.json
中需要 contributes
这个字段?再描述清楚一点问题,为什么需要先在 contributes 注册拓展功能,再在口入文件中实现?为什么不直接使用入口文件,省去注册。
下面是我的一些想法:
- 关注点分离
在 package.json 中声明我要干啥。
在 入口文件 中说明怎么干。
用户也可以通过阅读 package.json 大致了解这个插件拓展了哪些内容。
从这个角度来看,方便思考,方便概览。
- 自动集成
插件有一些视图是在插件激活前就需要有的,比如侧边的按钮,或者命令菜单中的选项。因此,他们一定不可能写在入口文件中(因为这个脚本只有插件激活时才初次运行)。
因此,有一些功能是 vscode 根据配置文件自动集成的,而不是通过我们的入口文件手动去写。
比如下文会提到的
viewContainer
, vscode 会自动在目标位置渲染在 package.json 中注册的按钮。再比如命令的视图,当你按下 cmd+shift+p
,会在命令菜单中看到我们在 package.json 中注册的命令 title, icon.从这个角度来看,这是必要的,同时也降低了开发人员的心智负担。
- 安全性
配置文件是静态的,vscode 分析这个静态能容来了解开发者申请了哪些功能,然后给到不同权限的沙箱。
而且,用户可以通过禁用 contributes 中的某些项,来禁用某部分拓展功能。
总结
我们了解了 VScode 的基本结构,VScode 插件运行在单独的进程中。
以及了解了编写插件时的基本规范,要编写一个 vscode 插件,主要做两件事
- 注册 package.json (基本信息,contributes, ...)。
- 暴露出入口函数 activate 实现插件逻辑, 出口函数 deactivate 在插件销毁时清理资源。