Skip to content

VSCode 插件开发实践

调试、测试与发布

目录


开发环境

创建项目

bash
# 使用 Yeoman 生成器
npm install -g yo generator-code
yo code

# 选择选项
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? my-extension
? What's the identifier of your extension? my-extension
? What's the description? My first extension
? Enable strict TypeScript checking? Yes
? Setup linting using ESLint? Yes
? Initialize a git repository? Yes
? Which package manager to use? npm

调试配置

json
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Run Extension",
      "type": "extensionHost",
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}"
      ],
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "preLaunchTask": "npm: watch"
    },
    {
      "name": "Extension Tests",
      "type": "extensionHost",
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}",
        "--extensionTestsPath=${workspaceFolder}/out/test"
      ],
      "outFiles": ["${workspaceFolder}/out/**/*.js"]
    }
  ]
}

调试技巧

Console 输出

typescript
// 输出通道(推荐)
const outputChannel = vscode.window.createOutputChannel('My Extension');
outputChannel.appendLine('Debug info');
outputChannel.show();

// 开发者工具 Console
console.log('Debug message');

// 状态栏临时信息
vscode.window.setStatusBarMessage('Processing...', 2000);

断点调试

typescript
// 1. 在源码中设置断点
// 2. 按 F5 启动调试
// 3. 在 Extension Development Host 中触发命令
// 4. 断点命中后可查看变量、调用栈

常见问题排查

typescript
// 检查激活
export function activate(context: vscode.ExtensionContext) {
  console.log('Extension activated');
  
  try {
    // 初始化代码
  } catch (error) {
    console.error('Activation failed:', error);
    vscode.window.showErrorMessage(`Activation failed: ${error}`);
  }
}

// 检查命令注册
const disposable = vscode.commands.registerCommand('myCommand', () => {
  console.log('Command executed');
});

if (!disposable) {
  console.error('Command registration failed');
}

测试策略

单元测试

typescript
// src/utils.ts
export function formatText(text: string): string {
  return text.trim().toLowerCase();
}

// test/utils.test.ts
import * as assert from 'assert';
import { formatText } from '../src/utils';

suite('Utils Test Suite', () => {
  test('formatText should trim and lowercase', () => {
    assert.strictEqual(formatText('  Hello World  '), 'hello world');
  });
});

集成测试

typescript
// test/extension.test.ts
import * as vscode from 'vscode';
import * as assert from 'assert';

suite('Extension Test Suite', () => {
  vscode.window.showInformationMessage('Start all tests.');

  test('Extension should be present', () => {
    assert.ok(vscode.extensions.getExtension('publisher.my-extension'));
  });

  test('Command should be registered', async () => {
    const commands = await vscode.commands.getCommands(true);
    assert.ok(commands.includes('myExtension.helloWorld'));
  });

  test('Command should execute', async () => {
    await vscode.commands.executeCommand('myExtension.helloWorld');
    // 验证结果
  });
});

测试运行器配置

typescript
// test/runTest.ts
import * as path from 'path';
import { runTests } from '@vscode/test-electron';

async function main() {
  try {
    const extensionDevelopmentPath = path.resolve(__dirname, '../../');
    const extensionTestsPath = path.resolve(__dirname, './suite/index');

    await runTests({
      extensionDevelopmentPath,
      extensionTestsPath,
      launchArgs: ['--disable-extensions']
    });
  } catch (err) {
    console.error('Failed to run tests');
    process.exit(1);
  }
}

main();

打包发布

安装 vsce

bash
npm install -g @vscode/vsce

打包

bash
# 生成 .vsix 文件
vsce package

# 指定版本
vsce package --pre-release

发布到 Marketplace

bash
# 1. 创建 Azure DevOps 组织
# 2. 获取 Personal Access Token (PAT)
# 3. 创建 publisher

vsce create-publisher <publisher-name>

# 4. 登录
vsce login <publisher-name>

# 5. 发布
vsce publish

# 或指定版本
vsce publish minor  # 1.0.0 -> 1.1.0
vsce publish major  # 1.0.0 -> 2.0.0
vsce publish patch  # 1.0.0 -> 1.0.1

package.json 检查清单

json
{
  "name": "my-extension",
  "displayName": "My Extension",
  "description": "Description here",
  "version": "1.0.0",
  "publisher": "your-publisher-name",
  "repository": {
    "type": "git",
    "url": "https://github.com/user/repo"
  },
  "icon": "images/icon.png",
  "categories": ["Other"],
  "keywords": ["keyword1", "keyword2"],
  "engines": {
    "vscode": "^1.80.0"
  },
  "license": "MIT"
}

CI/CD 配置

yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      
      - run: npm ci
      - run: npm run test
      
      - name: Publish
        run: npx vsce publish -p ${{ secrets.VSCE_PAT }}

性能优化

延迟加载

typescript
// 按需导入
export async function activate(context: vscode.ExtensionContext) {
  // 延迟加载大型模块
  const command = vscode.commands.registerCommand('myExtension.analyze', async () => {
    const analyzer = await import('./analyzer');
    analyzer.run();
  });
  
  context.subscriptions.push(command);
}

减少激活事件

json
{
  "activationEvents": [
    // ❌ 避免使用 * 
    "*",
    
    // ✅ 精确触发
    "onCommand:myExtension.start",
    "onLanguage:javascript"
  ]
}

缓存策略

typescript
class DataCache {
  private cache = new Map<string, { data: any; timestamp: number }>();
  private ttl = 5 * 60 * 1000; // 5 分钟

  get(key: string): any | undefined {
    const entry = this.cache.get(key);
    if (!entry) return undefined;
    
    if (Date.now() - entry.timestamp > this.ttl) {
      this.cache.delete(key);
      return undefined;
    }
    
    return entry.data;
  }

  set(key: string, data: any): void {
    this.cache.set(key, { data, timestamp: Date.now() });
  }
}

高频面试题

Q1: 如何调试 Extension Host 崩溃?

  1. 查看日志:Help → Toggle Developer Tools → Console
  2. 输出面板:View → Output → Extension Host
  3. 禁用其他插件--disable-extensions 排除干扰

Q2: 如何减少插件体积?

  1. 排除不必要文件.vscodeignore
  2. 生产构建:使用 esbuild/webpack 打包
  3. 依赖分析:移除未使用的 npm 包
# .vscodeignore
.vscode/**
src/**
node_modules/**
*.ts
tsconfig.json

Q3: 发布前的检查项?

  • [ ] publisher 已配置
  • [ ] repository 已填写
  • [ ] icon 图片存在且符合规范 (128x128 PNG)
  • [ ] README.md 内容完整
  • [ ] CHANGELOG.md 已更新
  • [ ] 版本号已更新
  • [ ] 所有测试通过

Q4: 如何进行灰度发布?

bash
# 预发布版本
vsce publish --pre-release

# 用户可选择安装 Pre-Release 版本
# Marketplace 上会显示 Pre-Release 标签

资源推荐

资源链接
官方文档https://code.visualstudio.com/api
API 参考https://code.visualstudio.com/api/references/vscode-api
示例仓库https://github.com/microsoft/vscode-extension-samples
Marketplacehttps://marketplace.visualstudio.com

前端面试知识库