Lesson 0001 VS Code 扩展的解剖结构

每一个 VS Code 扩展都从一个 package.json 文件开始。这个文件不仅是 Node.js 项目的身份证,更是扩展向 VS Code 声明自己存在的方式。这一课我们以 claude-context-bar 的真实代码为标本,解剖扩展的骨架。

1. 扩展是什么?

VS Code 本身是用 Electron(Chromium + Node.js)构建的。一个扩展就是一个在 VS Code 进程内运行的 Node.js 程序,它通过 VS Code Extension API(一个名为 vscode 的模块)与编辑器交互。

扩展能做的事情包括但不限于:

claude-context-bar 只用了 VS Code Extension API 的一小部分——状态栏、配置、文件监听和命令注册。这正是它作为入门学习标本的价值:复杂度可以被完全掌控

2. package.json:扩展的宣言

打开 temp_repo/package.json,你会看到它是一个标准的 npm package.json,但多了几个 VS Code 扩展特有的字段。让我们逐段解剖:

2.1 基础元数据

// 这些是所有 npm 包都有的标准字段
"name": "claude-context-bar",        // npm 包名(全小写,短横线分隔)
"displayName": "Claude Context Bar",   // 在 VS Code 扩展面板中显示的人类可读名称
"description": "Real-time context...",  // 一句话描述
"version": "1.4.1",                    // 语义化版本(SemVer)
"publisher": "ezoosk",                 // VS Code Marketplace 发布者 ID
"license": "MIT",
"icon": "images/icon.png",            // 扩展图标(Marketplace 展示用)
📋 关于 "name" 字段:"全小写,短横线分隔"其实是两件事——"全小写"是 npm registry 的强制规则(含大写字母的新包会被拒绝),而"短横线"是社区约定_. 在包名内部也合法,只是不能放开头)。完整规则速查见 npm 包命名规则参考
🆕 对你来说:"publisher" 字段是 VS Code 扩展独有的。它必须匹配你在 VS Code Marketplace 上注册的发布者 ID。发布扩展时需要用到它。

2.2 引擎与激活事件

"engines": {
    "vscode": "^1.74.0"         // 扩展要求的最低 VS Code 版本
},
"activationEvents": [
    "onStartupFinished"        // 何时激活扩展
],

engines.vscode 声明了兼容的最低 VS Code 版本。^1.74.0 表示需要 1.74.0 或更高版本(但不能是 2.0)。

🔒 为什么不能是 2.0?^(caret)是 npm semver 语法——它锁定最左非零段^1.74.01 是第一段非零数字,所以 major 被锁定,覆盖范围是 >=1.74.0 <2.0.0。主版本号变化意味着不兼容的 API 变更,自动排除。完整 SemVer 语法速查 →

activationEvents 是关键的性能机制:它告诉 VS Code 什么时候才加载你的扩展。常见的激活事件有:

激活事件含义
onStartupFinishedVS Code 启动完成后立即激活(本扩展使用这个)
onCommand:xxx用户首次执行某个命令时激活(延迟加载)
onLanguage:python打开特定语言的文件时激活
*VS Code 一启动就激活(不推荐,影响启动性能)
设计决策:为什么 claude-context-bar 使用 onStartupFinished 而不是 *?因为它需要在状态栏显示信息,但又不想拖慢 VS Code 的启动速度。onStartupFinished 是两全之策——等编辑器就绪后再初始化,不影响用户的启动体验。

2.3 入口文件

"main": "./out/extension.js",   // 扩展的 JavaScript 入口(编译产物)

这是 VS Code 加载扩展时执行的第一个文件。注意路径是 ./out/ 而不是 ./src/——因为 TypeScript 必须先编译成 JavaScript。VS Code 的 Node.js 环境不直接运行 TypeScript。

VS Code 要求这个文件导出一个 activate 函数和一个可选的 deactivate 函数。

2.4 贡献点(Contribution Points)

"contributes": {
    "configuration": {
        "title": "Claude Context Bar",
        "properties": {
            "claudeContextBar.autoColor": {
                "type": "boolean",
                "default": true,
                "description": "..."
            }
            // ... 更多配置项
        }
    }
}

contributes 是扩展向 VS Code 注入能力的方式。claude-context-bar 只贡献了一种东西:用户配置项。每个配置项会自动出现在用户的 settings.json 中,提供自动补全和类型检查。

🔗 数据流:从声明到消费,三步走

claudeContextBar.warningThreshold 为例:

① 扩展声明package.json)→ 定义类型、默认值、描述
② 用户覆盖settings.json)→ 用户在 VS Code 设置面板中修改(不改则用默认值)
③ 代码读取extension.ts)→ vscode.workspace.getConfiguration('claudeContextBar').get('warningThreshold')

VS Code 负责了中间所有脏活:UI 渲染、类型校验(number 类型不允许填字符串)、默认值回退、搜索和自动补全。这是 VS Code 声明式扩展模型的核心——扩展只声明自己需要什么,VS Code 负责呈现和管理。

扩展可以贡献的其他东西(本扩展没用到,但你将来可能用到):

3. 编译脚本:TypeScript → JavaScript

"scripts": {
    "vscode:prepublish": "npm run compile",  // 发布前自动执行
    "compile": "tsc -p ./",                 // 编译 TypeScript
    "watch": "tsc -watch -p ./",            // 监听模式(开发时用)
    "package": "vsce package",              // 打包为 .vsix
    "publish": "vsce publish"               // 发布到 Marketplace
}
关键细节:vscode:prepublish 是一个约定脚本名称。当你运行 vsce publish 时,它会自动先执行这个脚本,确保发布的总是最新的编译产物。这是一个发布安全网——防止你忘了编译就把旧代码发出去。

4. 练习:映射 package.json → extension.ts

现在打开 temp_repo/src/extension.ts,用 Ctrl+F 搜索以下内容,观察 package.json 中的声明如何在代码中落地:

  1. 搜索 export function activate — VS Code 加载扩展时调用的入口
  2. 搜索 claudeContextBar.hideSession — 代码中注册的命令,对应点击状态栏的行为
  3. 搜索 claudeContextBar — 代码中读取配置的前缀,对应 package.json 中的配置项
  4. 搜索 StatusBarAlignment.Right — 状态栏位置控制

5. 知识检查

问题 1

如果我想让扩展只在用户打开 Python 文件时才加载(而不是 VS Code 启动时),应该把 activationEvents 改成什么?

问题 2

package.json 中的 "main": "./out/extension.js" 指向编译产物。VS Code 要求这个文件导出什么函数?

问题 3

vscode:prepublish 脚本的作用是什么?

6. 推荐深入阅读

💡 提问提示:如果你对任何概念有疑问——比如 "为什么用 onStartupFinished 而不是 onCommand?"、"contributes 还能做什么?"——直接在这个对话中提问。我是你的老师,可以随时解答。