一、快速了解 VS Code 插件
VS Code 插件本质上是一个带有"扩展清单"的 Node.js 项目。
它通常包含两部分:
package.json:静态声明插件的身份、命令、配置项、激活方式等。它像插件的"合同"。- 入口代码:通常是
src/extension.ts或extension.js。它负责在插件被激活后真正运行逻辑。
可以先建立这样一个心智模型:
package.json先告诉 VS Code:"我提供了哪些能力。"- 用户触发某个能力,比如执行命令、打开某类文件、修改某项设置。
- VS Code 在合适的时机激活插件。
- 插件入口代码开始执行,并调用 VS Code API 完成功能。
二、初始化一个插件项目
官方常见做法是使用 Yeoman 和 generator-code 来生成脚手架。
先按照以下命令全局安装:
css
npm install --global yo generator-code
按照完成后,就可以使用 yo 命令创建插件项目了:
css
yo code
创建一个最基础的 TypeScript 插件时,常见输入如下:
bash
# ? What type of extension do you want to create?
New Extension (TypeScript)
# ? What's the name of your extension?
HelloWorld
### 下面多数选项直接按回车使用默认值即可 ###
# ? What's the identifier of your extension?
helloworld
# ? What's the description of your extension?
LEAVE BLANK
# ? Initialize a git repository?
Y
# ? Which bundler to use?
unbundled
# ? Which package manager to use?
npm
生成完成后,你会得到一个可以直接运行和调试的最小插件项目。
三、把插件跑起来
进入项目后,直接按 F5,VS Code 会启动一个新的调试窗口。这个新窗口叫 Extension Development Host,可以理解为"专门拿来跑你插件的 VS Code 实验环境"。
然后在这个新窗口里:
- 按
Ctrl+Shift+P - 输入
Hello World - 执行命令
如果看到右下角弹出提示,说明你的插件已经成功运行。
这一步看起来简单,但它其实已经完成了一个插件的基本闭环:
- 插件被识别
- 插件被激活
- 命令被注册
- 命令逻辑被执行
四、生成后的目录结构怎么看
一个典型的 TypeScript 插件目录大致如下:
css
.
├── .vscode
│ ├── launch.json
│ └── tasks.json
├── src
│ └── extension.ts
├── package.json
├── tsconfig.json
├── README.md
└── .gitignore
这些文件里,新手最需要先理解的是下面几个:
.vscode/launch.json:调试插件时使用的启动配置。.vscode/tasks.json:通常定义 TypeScript 编译任务。src/extension.ts:插件代码入口。package.json:插件清单和 Node.js 项目配置。tsconfig.json:TypeScript 编译规则。
其中 launch.json 和 tasks.json 很多人一开始会直接跳过,但实际上它们决定了你按下 F5 时,VS Code 如何编译、如何启动调试窗口。
五、最重要的两个文件
1. 插件的静态声明中心
package.json 是插件的静态声明中心。每个 VS Code 插件都必须有 package.json。它不仅是 Node.js 项目的配置文件,也是 VS Code 识别插件的入口清单。
这里最值得先看懂的几个字段是:
name、publisher:VS Code 使用<publisher>.<name>作为扩展的唯一 ID。VS Code 通过该 ID 唯一标识你的扩展。engines.vscode:声明这个插件支持的 VS Code API 版本范围。main:插件运行时代码入口。activationEvents:插件在什么情况下被激活。contributes:插件向 VS Code "贡献"了什么能力,比如命令、菜单、配置项、主题、代码片段等。
如果要用一句话概括:
package.json 决定的是"插件对外提供什么";代码文件决定的是"这些能力具体怎么实现"。
2. 插件的运行时入口
src/extension.ts 是插件的运行时入口。Yeoman 生成的默认入口文件里,通常会导出两个函数:
activate(context):插件激活时执行。deactivate():插件停用前执行,可选。如果扩展需要在 VS Code 关闭、扩展被禁用或卸载时执行某些操作,则可通过该方法实现。
其中 activate 最重要,因为命令注册、事件监听、状态初始化,通常都从这里开始。
一个非常典型的最小示例是:
javascript
import * as vscode from "vscode";
export function activate(context: vscode.ExtensionContext) {
const disposable = vscode.commands.registerCommand(
"helloworld.helloWorld",
() => {
vscode.window.showInformationMessage("Hello World from HelloWorld!");
},
);
context.subscriptions.push(disposable);
}
export function deactivate() {}
这段代码真正做的事只有一件:把命令 ID helloworld.helloWorld 和一段处理逻辑绑定起来。
当用户执行这个命令时,VS Code 就会调用这个回调函数。
五、把 Hello World 拆开理解
可以从三个步骤去看它,逐步理解插件开发的核心机制。
1. 命令贡献
在 package.json 中,contributes.commands 会声明一个命令,让它出现在命令面板里,例如:
json
{
"contributes": {
"commands": [
{
"command": "helloworld.helloWorld",
"title": "Hello World"
}
]
}
}
这时候"Hello World " 命令就在命令面板中可用,并将其绑定到命令 ID helloworld.helloWorld。
这一步只是"声明这个命令存在",并不等于"命令已经能执行"。
2. 命令注册
在 activate 中使用 vscode.commands.registerCommand(...),把命令 ID 和运行逻辑(也就是你要用的函数)真正连起来。
可以把它理解成:
contributes.commands负责把按钮挂出去registerCommand负责把按钮接上电
3. 激活机制
注意:
从 VS Code 1.74.0 版本开始,在
package.json的commands部分声明的命令,在被调用时会自动激活扩展,无需在activationEvents中显式添加onCommand条目。如果你的扩展目标是 1.74 之前的 VS Code 版本,则必须在
activationEvents中显式列出onCommand:helloworld.helloWorld。
怎么理解上面这段话?旧版本中,命令在 contributes.commands 里声明后,还需手动去注册 onCommand 激活事件,这才能用。
较新的 VS Code 版本中,如果命令已经在 contributes.commands 里声明,通常不需再手动写 onCommand:xxx 到 activationEvents 中。
这是因为从 VS Code 1.74 开始,部分贡献点支持自动推断激活事件,commands 就属于其中之一。
但这里有两个专业上必须说清楚的前提:
- 如果你的插件需要兼容更旧的 VS Code 版本,就仍然要显式写上
onCommand:yourCommandId。 - 即使自动激活可用,也不代表可以忽略激活设计。插件何时启动、启动时做多少事,仍然直接影响性能和用户体验。
换句话说,自动推断是"少写一点配置",不是"可以不管激活策略"。
六、测试的理解和配置
VS Code 插件测试里,一个常见误区是把它完全当成普通 Node.js 单元测试。实际上,很多插件测试更接近 集成测试:
- 测试运行在一个专门的 VS Code 实例里
- 测试代码可以直接调用 VS Code API
- 测试目标通常不是纯函数,而是"插件和编辑器环境一起工作时是否正确"
如果想配置更复杂的自定义测试:自定义测试器示例
此处主要介绍项目使用默认的@vscode/test-cli进行测试:
1. 安装测试依赖
官方常见方案是:
scss
npm install --save-dev @vscode/test-cli @vscode/test-electron
然后在 package.json 中配置:
json
{
"scripts": {
"test": "vscode-test"
}
}
2. 新建测试配置文件
vscode-test 会自动读取项目根目录下的 .vscode-test.js、.cjs 或 .mjs 配置文件。一个常见示例如下:
arduino
import { defineConfig } from "@vscode/test-cli";
export default defineConfig({
// 测试标签,用于标识这组测试
label: "unitTests",
// 测试入口文件路径
files: "out/test/**.test.js",
// 使用的 VS Code 版本,stable 表示稳定版
version: "stable",
// 测试时使用的临时工作区文件夹
workspaceFolder: "./sampleWorkspace",
// Mocha 测试框架的配置
mocha: {
// 测试接口风格,tdd 表示测试驱动开发风格
ui: "tdd",
// 测试超时时间,单位毫秒(20秒)
timeout: 20000,
},
});
这里几个字段的含义分别是:
files:测试文件匹配路径。version:测试时使用的 VS Code 版本,stable表示稳定版。mocha:Mocha 的测试配置。
3. 写一个最小测试
例如:
dart
import * as assert from "assert";
import * as vscode from "vscode";
// 测试套件:可以包含多个测试用例
suite("Extension Test Suite", () => {
// 所有测试结束后执行(比如弹提示)
suiteTeardown(() => {
vscode.window.showInformationMessage("所有测试跑完啦!");
});
// 所有测试开始前执行(比如弹提示)
suiteSetup(() => {
vscode.window.showInformationMessage("Start all tests.");
});
// 单个测试用例
test("示例测试:数组查找", () => {
// 断言:[1,2,3] 里没有 5 和 0
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
// 测试 VS Code API(比如打开文件)
test("可以打开一个临时文档", async () => {
const doc = await vscode.workspace.openTextDocument({
content: "测试内容",
});
await vscode.window.showTextDocument(doc);
// 断言:当前打开的文档内容正确
assert.strictEqual(
vscode.window.activeTextEditor?.document.getText(),
"测试内容",
);
});
});
执行测试:
bash
npm test
首次运行时,工具通常会自动在根目录下的文件夹 .vscode-test下载一个用于测试的 VS Code 实例。
4. 插件调试,启动配置
修改 launch.json,配置以 .vscode-test 的内容为启动窗口
kotlin
// 一个启动配置,用于编译扩展,然后在新窗口中打开它
{
"version": "0.2.0",
"configurations": [
{
"name": "调试扩展测试",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/.vscode-test/vscode-win32-x64-archive-1.108.2/Code.exe", // 当前 VS Code 可执行文件
"args": [
"--user-data-dir=${workspaceFolder}/.vscode-test/user-data",
"--extensions-dir=${workspaceFolder}/.vscode-test/extensions",
"--extensionDevelopmentPath=${workspaceFolder}" // 扩展根目录
],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "${defaultBuildTask}"
}
]
}
如果插件有前置插件,那么可以通过安装在 .vscode-test/extensions 里来启动
css
# 比如说要安装一个 be5invis.vscode-custom-css
code --extensions-dir .vscode-test/extensions --install-extension be5invis.vscode-custom-css
注意启动时,会弹窗显示编辑器环境变化,要点确认。
七、容易忽略的问题
1. 激活越早,不一定越好
很多人一开始喜欢用 * 或 onStartupFinished 之类的激活方式,因为省事。但从插件工程实践来看,激活越早,越要对性能负责。
如果一个功能只在命令执行时才需要,就优先让它按需激活;如果一个功能必须在启动后常驻,再考虑更早的激活策略。
简单说:先保证正确,再追求"无感",不要一开始就把插件做成常驻后台进程。
2. 注册了资源,就要记得释放
registerCommand、事件监听、文件系统 watcher、状态订阅,这些通常都会返回 Disposable。
把它们放进 context.subscriptions,不是"写法讲究",而是插件生命周期管理的基本纪律。否则时间一长,就容易留下脏监听器或资源泄漏。
3. 插件不只是功能代码,也是产品接口
很多新手只盯着 extension.ts 写逻辑,却忽略了:
- 命令命名是否清楚
- 设置项描述是否明确
- README 是否说明限制条件
- 出错时是否给用户足够明确的提示
一个插件是否专业,不只看 API 用得熟不熟,也看用户是否知道怎么用、哪里会失败、失败后怎么处理。
八、后话
如果你已经完成最基础的命令插件,下一步建议按下面顺序练习:
- 增加一个配置项,让用户可以在 Settings 中控制插件行为。
- 读取当前编辑器内容,并对选中文本进行处理。
- 增加错误处理和用户提示,而不是默认"静默失败"。
- 给关键功能写至少一两个测试。
- 最后再考虑发布、依赖其他扩展、或者做更复杂的 UI。
当你写完一个插件功能后,可以用下面四个问题快速检查自己:
- 这个功能是如何被发现和触发的?
- 这个功能何时激活,是否有性能风险?
- 这个功能失败时,用户能不能看懂发生了什么?
- 这个功能是否至少有最基本的验证方式?
如果后面继续深入,一个很自然的学习路线是:
- 命令与菜单
- 配置项与设置页
- 编辑器 API
- 测试与调试
- 发布与版本维护
先把插件写对,再把插件写强。对 VS Code 插件开发来说,这个顺序很重要。