vscode-extension-expert について
VS Code 拡張機能を開発・保守する人向けのリファレンススキルです。API、設計、WebView と LSP の実装に加え、セキュリティ、パフォーマンス、テスト、公開の指針を扱います。
主な機能
- 01VS Code Extension API のガイド
- 02設計とアクティベーションのパターン
- 03WebView のセキュリティ対策
- 04LSP 実装ガイド
- 05パフォーマンスとテストの助言
- 06Marketplace 公開チェックリスト
活用シーン
- 01新しい VS Code 拡張機能の設計
- 02WebView や言語機能の追加
- 03起動や実行時の問題の調査
- 04拡張機能の公開準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ s-hiraoku/ vscode-sidebar-terminal --skill vscode-extension-expertインストール後、Claude Code で/vscode-extension-expertを入力して読み込まれたか確認します。
詳細
VS Code 拡張機能エキスパート
概要
このスキルは、VS Code Extension API、アーキテクチャパターン、セキュリティ要件、ベストプラクティスに関する包括的な知識を提供し、エキスパートレベルの VS Code 拡張機能開発を可能にします。新しい拡張機能の作成、既存の拡張機能への機能追加、WebView の実装、言語サポートの設計、パフォーマンスの最適化を行う際に使用してください。
このスキルを使用する場面
- VS Code 拡張機能の新機能を実装するとき
- 拡張機能のアーキテクチャと構成を設計するとき
- 適切なセキュリティを備えた WebView ベースの UI を作成するとき
- Language Server Protocol(LSP)の機能を実装するとき
- 拡張機能のアクティベーションや実行時の問題をデバッグするとき
- 拡張機能のパフォーマンスや起動時間を最適化するとき
- Marketplace への公開に向けて拡張機能を準備するとき
基本概念
拡張機能の構成
すべての VS Code 拡張機能には、次のものが必要です。
extension-name/
├── .vscode/ # Debug configurations
│ ├── launch.json
│ └── tasks.json
├── src/
│ └── extension.ts # Main entry point
├── package.json # Extension manifest (critical)
├── tsconfig.json # TypeScript config
└── .vscodeignore # Exclude from package
package.json の必須フィールド
{
"name": "extension-name",
"publisher": "publisher-id",
"version": "0.0.1",
"engines": { "vscode": "^1.80.0" },
"main": "./out/extension.js",
"activationEvents": [],
"contributes": {
"commands": [],
"configuration": {},
"views": {}
},
"extensionKind": ["workspace"]
}
拡張機能のエントリポイントパターン
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// Register commands, providers, listeners
const disposable = vscode.commands.registerCommand('ext.command', () => {
// Command implementation
});
context.subscriptions.push(disposable);
}
export function deactivate() {
// Cleanup resources
}
アクティベーションイベント
起動時の負荷を最小限に抑えるため、最も具体的なアクティベーションイベントを選択してください。
| イベント | 使用例 | 例 |
|---|---|---|
onLanguage:<lang> | 言語固有の機能 | onLanguage:python |
onCommand:<command> | コマンド駆動型の拡張機能 | onCommand:ext.showPanel |
onView:<viewId> | サイドバービューの展開 | onView:myTreeView |
workspaceContains:<glob> | プロジェクト固有の機能 | workspaceContains:**/.eslintrc* |
onFileSystem:<scheme> | カスタムファイルシステム | onFileSystem:sftp |
onStartupFinished | バックグラウンドタスク | (* よりも優先) |
重要:* は VS Code の起動ごとにアクティベートされるため、使用しないでください。
コントリビューションポイント
コマンド
{
"contributes": {
"commands": [{
"command": "ext.doSomething",
"title": "Do Something",
"category": "My Extension",
"icon": "$(symbol-method)"
}]
}
}
構成
{
"contributes": {
"configuration": {
"title": "My Extension",
"properties": {
"myExtension.enabled": {
"type": "boolean",
"default": true,
"description": "Enable the extension"
}
}
}
}
}
ビュー(ツリービュー)
{
"contributes": {
"views": {
"explorer": [{
"id": "myTreeView",
"name": "My View"
}]
},
"viewsContainers": {
"activitybar": [{
"id": "myContainer",
"title": "My Extension",
"icon": "resources/icon.svg"
}]
}
}
}
VS Code API 名前空間
window API
// Show messages
vscode.window.showInformationMessage('Hello!');
vscode.window.showErrorMessage('Error occurred');
// Quick picks
const item = await vscode.window.showQuickPick(['Option 1', 'Option 2']);
// Input boxes
const input = await vscode.window.showInputBox({ prompt: 'Enter value' });
// Active editor
const editor = vscode.window.activeTextEditor;
workspace API
// Read configuration
const config = vscode.workspace.getConfiguration('myExtension');
const value = config.get<boolean>('enabled');
// Watch files
const watcher = vscode.workspace.createFileSystemWatcher('**/*.ts');
watcher.onDidChange(uri => { /* handle change */ });
// Open documents
const doc = await vscode.workspace.openTextDocument(uri);
commands API
// Register
const disposable = vscode.commands.registerCommand('ext.cmd', (arg) => {
// Implementation
});
// Execute
await vscode.commands.executeCommand('ext.cmd', argument);
WebView 開発
セキュリティ要件(重要)
- コンテンツセキュリティポリシー(CSP) — 常に厳格な CSP を実装してください。
function getWebviewContent(webview: vscode.Webview): string {
const nonce = getNonce();
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
style-src ${webview.cspSource} 'unsafe-inline';
script-src 'nonce-${nonce}';
img-src ${webview.cspSource} https:;
">
</head>
<body>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
// Use vscode.postMessage() for communication
</script>
</body>
</html>`;
}
- 入力のサニタイズ — ユーザー入力は必ずサニタイズしてください。
- HTTPS のみ — 外部リソースには HTTPS を使用してください。
- 権限の最小化 —
localResourceRootsを制限してください。
メッセージの受け渡しパターン
// Extension → WebView
panel.webview.postMessage({ type: 'update', data: payload });
// WebView → Extension
panel.webview.onDidReceiveMessage(message => {
switch (message.type) {
case 'action':
handleAction(message.data);
break;
}
});
// In WebView JavaScript
window.addEventListener('message', event => {
const message = event.data;
// Handle message
});
vscode.postMessage({ type: 'action', data: result });
状態の永続化
// Simple state (survives webview hide/show)
const state = webview.getState() || { count: 0 };
webview.setState({ count: state.count + 1 });
// Full persistence (survives VS Code restart)
class MySerializer implements vscode.WebviewPanelSerializer {
async deserializeWebviewPanel(panel: vscode.WebviewPanel, state: any) {
panel.webview.html = getHtmlForWebview(panel.webview, state);
}
}
vscode.window.registerWebviewPanelSerializer('myWebview', new MySerializer());
Language Server Protocol(LSP)
アーキテクチャ
┌─────────────────────┐ ┌─────────────────────┐
│ Language Client │────│ Language Server │
│ (VS Code Extension)│ LSP │ (Separate Process) │
│ vscode-languageclient │ vscode-languageserver
└─────────────────────┘ └─────────────────────┘
クライアントの実装
import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
const serverOptions: ServerOptions = {
run: { module: serverPath, transport: TransportKind.ipc },
debug: { module: serverPath, transport: TransportKind.ipc }
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'mylang' }],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.mylang')
}
};
const client = new LanguageClient('mylang', 'My Language', serverOptions, clientOptions);
client.start();
サーバーの実装
import { createConnection, TextDocuments, ProposedFeatures } from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';
const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);
connection.onInitialize((params) => {
return {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
completionProvider: { resolveProvider: true },
hoverProvider: true
}
};
});
connection.onCompletion((params) => {
return [
{ label: 'suggestion1', kind: CompletionItemKind.Text }
];
});
documents.listen(connection);
connection.listen();
ツリービューの実装
class MyTreeDataProvider implements vscode.TreeDataProvider<MyItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<MyItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(element: MyItem): vscode.TreeItem {
return {
label: element.name,
collapsibleState: element.children ?
vscode.TreeItemCollapsibleState.Collapsed :
vscode.TreeItemCollapsibleState.None,
command: {
command: 'ext.selectItem',
title: 'Select',
arguments: [element]
}
};
}
getChildren(element?: MyItem): Thenable<MyItem[]> {
if (!element) {
return Promise.resolve(this.getRootItems());
}
return Promise.resolve(element.children || []);
}
}
// Register
const provider = new MyTreeDataProvider();
vscode.window.registerTreeDataProvider('myTreeView', provider);
パフォーマンスのベストプラクティス
遅延読み込み
// Delay expensive imports
let heavyModule: typeof import('./heavyModule') | undefined;
async function getHeavyModule() {
if (!heavyModule) {
heavyModule = await import('./heavyModule');
}
return heavyModule;
}
バンドル(VS Code Web では必須)
高速なバンドルには esbuild を使用してください。
// esbuild.config.js
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['./src/extension.ts'],
bundle: true,
outfile: './out/extension.js',
external: ['vscode'],
format: 'cjs',
platform: 'node',
minify: process.env.NODE_ENV === 'production',
sourcemap: true
});
リソースのクリーンアップ
export function activate(context: vscode.ExtensionContext) {
// Always add to subscriptions for automatic cleanup
context.subscriptions.push(
vscode.commands.registerCommand(...),
vscode.window.registerTreeDataProvider(...),
watcher,
client
);
}
export function deactivate() {
// Explicit cleanup for async resources
return client?.stop();
}
テスト戦略
@vscode/test-cli を使用した統合テスト
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');
module.exports = defineConfig({
files: 'out/test/**/*.test.js',
version: 'stable',
workspaceFolder: './test-fixtures',
mocha: {
timeout: 20000 // Note: @vscode/test-cli uses Mocha for VS Code extension host tests
}
});
テスト構成
import * as assert from 'assert';
import * as vscode from 'vscode';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start tests.');
test('Command registration', async () => {
const commands = await vscode.commands.getCommands();
assert.ok(commands.includes('ext.myCommand'));
});
test('Configuration access', () => {
const config = vscode.workspace.getConfiguration('myExtension');
assert.strictEqual(config.get('enabled'), true);
});
});
よくある落とし穴と解決策
拡張機能がアクティベートされない
原因:アクティベーションイベントがユーザーの操作と一致していません。
解決策:package.json の activationEvents が実際のトリガーと一致していることを確認してください。
WebView のセキュリティエラー
原因:CSP がないか、正しく設定されていません。
解決策:厳格な Content-Security-Policy の meta タグを必ず含めてください。
メモリリーク
原因:イベントリスナーや破棄可能なリソースが追跡されていません。
解決策:すべての破棄可能なリソースを context.subscriptions に追加してください。
起動が遅い
原因:activate() 内で負荷の高い処理を同期的に実行しています。
解決策:遅延読み込みを使用し、重要でない初期化は後回しにしてください。
コマンドがパレットに表示されない
原因:contributes.commands の宣言がありません。
解決策:コマンドが package.json で宣言され、かつ registerCommand で登録されていることを確認してください。
セキュリティチェックリスト
- WebView に厳格な Content Security Policy を実装する
- レンダリング前にすべてのユーザー入力をサニタイズする
- 外部リソースには HTTPS を使用する
- WebView からのすべてのメッセージを検証する
-
localResourceRootsを必要なパスに限定する - URL の検証には単語境界を含む正規表現を使用する(
includes()は使用しない) - 設定にシークレットを保存しない(
SecretStorageを使用する)
公開チェックリスト
- 名前と発行元の組み合わせが一意であること
- PNG アイコン(最小 128x128)
- 機能とスクリーンショットを含む完全な README.md
- バージョン履歴を記載した CHANGELOG.md
- LICENSE ファイル
- セマンティックバージョニング
- 開発用ファイルを除外する .vscodeignore
- Windows、macOS、Linux でテスト済みであること
- 必要に応じて、Web 互換性のためにバンドルすること
リソース
詳しいリファレンスドキュメントについては、以下を参照してください。
references/api-reference.md- VS Code API の完全なドキュメントreferences/webview-security.md- WebView のセキュリティガイドラインreferences/lsp-guide.md- Language Server Protocol の実装ガイド
動作するサンプルについては、公式サンプルを参照してください。