插件迁移与兼容
插件写好了只是开始,让它长期健康运行还要面对三件事:API 版本演进(废弃与替代)、运行环境差异(VSCodium、Theia)、依赖关系管理(Extension Pack)。本篇把这套「维护期知识」一次讲透。
识别废弃 API
VS Code 每季度更新 API,废弃的成员会在 vscode.d.ts 里打上 @deprecated 标注。识别路径有三条:
| 方式 | 操作 |
|---|---|
| 编辑器悬停 | 光标悬停在 API 上,显示删除线样式与「Deprecated」说明 |
| 跳转到定义 | F12 进入 vscode.d.ts,读 JSDoc 中 @deprecated 之后的替代建议 |
| 编译提示 | TypeScript 语言服务会把废弃用法的调用处标记为删除线,悬停可见 |
典型废弃 API 与替代方案:
| 废弃用法 | 废弃原因 | 替代写法 |
|---|---|---|
workspace.rootPath | 只支持单根工作区 | workspace.workspaceFolders?.[0]?.uri.fsPath |
context.extensionPath | 路径字符串不便组合 | context.extensionUri.fsPath |
context.asAbsolutePath(rel) | 同上 | Uri.joinPath(context.extensionUri, rel).fsPath |
window.createTerminal(name, shellPath, shellArgs) | 参数式重载难扩展 | window.createTerminal({ name, shellPath, shellArgs })(TerminalOptions 对象) |
vscode.env.appRoot | 已被移除(历史版本) | 无替代,禁止使用 |
迁移示例——读取扩展目录下的 media/icon.svg:
import * as vscode from 'vscode';
// ❌ 旧写法(extensionPath + 字符串拼接,且已废弃)
// const icon = vscode.Uri.file(
// vscode.extensions.getExtension('me.ext')!.extensionPath + '/media/icon.svg'
// );
// ✅ 新写法:extensionUri + Uri.joinPath,跨平台安全
const icon = vscode.Uri.joinPath(context.extensionUri, 'media', 'icon.svg');Uri.joinPath 会自动处理路径分隔符与编码,比手拼字符串健壮得多。
engines.vscode 与 API 版本对应
package.json 里两处版本必须理解成一组联动:
| 字段 | 含义 | 约定 |
|---|---|---|
engines.vscode | 声明插件所需的最低 VS Code 版本 | 运行时版本低于它则拒绝安装 |
@types/vscode | 编译时的 API 类型定义版本 | 主版本应与 engines.vscode 一致 |
对应关系示例:
{
"engines": { "vscode": "^1.80.0" },
"devDependencies": {
"@types/vscode": "^1.80.0"
}
}两条硬规则:
- 运行版本 ≥
engines.vscode:插件在旧版本上不会被安装。 @types/vscode主版本 ≤engines.vscode主版本:类型声明不能比声明的最低版本更新,否则你调用的新 API 在旧环境里不存在。
API 版本检查(1.93+)
从 VS Code 1.93 起,扩展宿主会对「插件使用了超出 engines.vscode 声明版本的 API」做检查:相关 API 会被禁用,扩展加载时报「此扩展使用的 API 与当前 VS Code 版本不兼容」类提示。这是对 @types/vscode 与 engines.vscode 不一致问题的运行时兜底——所以升级 @types/vscode 时必须同步提高 engines.vscode。
升级 @types/vscode 的正确姿势:
# 1. 安装新版本类型(假设升级到 1.90)
npm install -D @types/vscode@^1.90.0
# 2. 修改 package.json 的 engines.vscode 同步提升{
"engines": { "vscode": "^1.90.0" }
}VSCodium / Theia 兼容
官方市场只服务微软版 VS Code。面向开源发行版发布插件,需要理解它们的差异:
| 发行版 | 本质 | API 兼容度 | 市场 |
|---|---|---|---|
| VSCodium | VS Code 开源构建,去遥测与品牌 | 与官方 API 完全一致 | open-vsx.org |
| Theia(Eclipse) | 独立 IDE 平台,实现 VS Code 扩展 API 兼容层 | 覆盖常用 API 子集,非全部 | open-vsx.org |
兼容性要点
{
"name": "my-extension",
"publisher": "mypublisher",
"engines": { "vscode": "^1.80.0" },
"license": "MIT"
}- license 必填:open-vsx.org 要求明确的许可证字段,缺失会被拒收。
- 禁止依赖遥测与微软服务:VSCodium 无遥测上报通道,依赖
vscode.env.telemetryEnabled或微软账号 API 的功能会失效,需做降级分支。 - Theia 部分 API 缺失:Theia 的兼容层对编辑器、TreeView、命令、语言服务覆盖较好;对
Terminal伪终端、调试器等支持有限。开发前查 Theia 文档确认能力边界。
测试与发布流程
# 1. 本地打包成 VSIX
npx @vscode/vsce package
# 2. 在 VSCodium 中手动安装验证(命令行)
codium --install-extension my-extension-1.0.0.vsix
# 3. 发布到 open-vsx.org(需要注册 open-vsx 账号)
npx ovsx publish my-extension-1.0.0.vsix -p <token>Extension Pack 依赖管理
一个插件要依赖另一个插件时,package.json 有两个字段,含义不同:
| 字段 | 语义 | 安装行为 | 卸载行为 |
|---|---|---|---|
extensionDependencies | 运行时依赖:功能真正需要它 | 安装本插件时自动安装 | 不联动卸载 |
extensionPack | 打包集合:推荐/捆绑同类插件 | 安装本插件时自动安装 | 卸载时提示可选卸载集合成员 |
典型场景:
{
"name": "frontend-toolkit",
"displayName": "前端工具箱(集合包)",
"extensionPack": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"formulahendry.auto-rename-tag"
],
"extensionDependencies": [
"mypublisher.shared-lib"
]
}版本约束的坑
extensionDependencies 和 extensionPack 都只认插件 ID,无法直接写版本号。依赖的版本升级不由你控制,可能悄悄破坏兼容。补救办法:运行时显式检查依赖版本。
import * as vscode from 'vscode';
/**
* 校验依赖插件的最低版本。
* 依赖通过 extensionDependencies 声明,但版本只能运行时检查。
*/
export async function assertDependencyVersion(
dependencyId: string,
minVersion: string
): Promise<void> {
const dep = vscode.extensions.getExtension(dependencyId);
if (!dep) {
vscode.window.showErrorMessage(
`缺少依赖插件 ${dependencyId},请先安装`,
'打开扩展视图'
).then((choice) => {
if (choice) {
vscode.commands.executeCommand(
'workbench.extensions.search', dependencyId
);
}
});
return;
}
const installed = dep.packageJSON.version as string;
if (compareVersions(installed, minVersion) < 0) {
vscode.window.showWarningMessage(
`依赖插件 ${dependencyId} 版本过低(当前 ${installed},需要 ≥ ${minVersion}),部分功能可能异常`
);
}
}
// 简易语义化版本比较:a < b 返回 -1,相等返回 0,a > b 返回 1
function compareVersions(a: string, b: string): number {
const pa = a.split('.').map(Number);
const pb = b.split('.').map(Number);
for (let i = 0; i < 3; i++) {
const x = pa[i] ?? 0;
const y = pb[i] ?? 0;
if (x !== y) return x < y ? -1 : 1;
}
return 0;
}依赖的版本约束示例
被依赖方(shared-lib)在 package.json 声明自己要求的 VS Code 版本,依赖方在 engines.vscode 里带上相同的下限,形成「互相约束」:
// shared-lib 的 package.json
{
"name": "shared-lib",
"version": "2.3.0",
"engines": { "vscode": "^1.85.0" }
}// 依赖方(frontend-toolkit)
{
"engines": { "vscode": "^1.85.0" },
"extensionDependencies": ["mypublisher.shared-lib"]
}依赖方把 engines.vscode 抬到和 shared-lib 相同或更高,避免在旧 VS Code 上装上不兼容组合。
升级迁移 Checklist
升级插件到新版 VS Code / 重构存量插件时,按顺序过一遍:
1. 升级 @types/vscode 并同步提升 engines.vscode
□ npm install -D @types/vscode@latest
□ package.json 中 engines.vscode 与之一致
2. 全量编译,收集编译错误与废弃用法
□ npx tsc -p ./
□ 悬停检查所有删除线标记的 API
3. 逐条替换废弃 API(对照上文替换表)
□ workspace.rootPath → workspaceFolders
□ extensionPath / asAbsolutePath → Uri.joinPath
□ createTerminal 参数式重载 → TerminalOptions
4. 检查破坏性行为变更
□ 查询该版本的 release notes 中的 Breaking Changes 章节
□ 重点:激活时机、事件语义、设置默认值
5. 检查 Proposed API 使用
□ 若用了 @vscode/proposed API,确认该版本仍支持且无签名变更
6. 回归测试
□ 在最低支持版本(engines.vscode)与最新版本各跑一遍
□ 覆盖:激活、主功能、设置读写、Webview 通信、终端/任务
7. 兼容发行版抽查(若面向开源用户)
□ VSCodium 安装 VSIX 验证
□ 检查 license 字段,准备 open-vsx.org 发布
8. 版本号与变更记录
□ 遵循 semver:破坏性变更升主版本,新增能力升次版本
□ CHANGELOG.md 记录迁移内容常见问题
| 问题 | 处理 |
|---|---|
| 新 API 在旧 VS Code 上崩溃 | engines.vscode 声明过低,或运行时版本不足,需要版本分叉或用能力探测降级 |
| 升级 @types/vscode 后大量报错 | 说明之前用的 API 在新版本改动,按编译错误逐个核对替代 API |
| 插件在 VSCodium 正常但 Theia 异常 | Theia 未实现该 API,去 Theia 兼容性文档确认并做降级分支 |
| 卸载集合包时误删用户配置 | extensionPack 卸载询问是 VS Code 行为,插件无需干预;确保集合内插件数据可独立恢复 |
| 依赖插件被禁用 | 监听 vscode.extensions.onDidChange,检测依赖不可用时提示并降级 |
API 会演进,环境会分叉,依赖会升级——这三件事是插件维护期的常态。把「废弃替换、版本声明、运行时检查」变成开发习惯,插件就能长期平稳地跑在官方版、VSCodium、Theia 的任意组合上。