VS Code Extension Development Skill
Quick Reference
| Topic | Reference File |
|---|---|
| TypeScript best practices & project setup | references/typescript-setup.md |
| VS Code API namespaces cheatsheet | references/vscode-api.md |
| WebView security & messaging | references/webview-security.md |
| Unit testing (commands, providers, WebView) | references/unit-testing.md |
Always read the relevant reference file(s) before writing extension code.
Core Workflow
1. Understand the Goal
Before writing any code, confirm:
- What VS Code namespace(s) are involved? (
window,workspace,commands,languages, etc.) - Does this feature need a WebView, or can it use the built-in UI? (Prefer built-in UI when possible.)
- What is the activation event? (Lazy activation = better performance.)
2. Project Structure (Canonical Layout)
my-extension/
├── src/
│ ├── extension.ts # activate() / deactivate() entry point
│ ├── commands/ # One file per command group
│ ├── providers/ # TreeDataProvider, CodeLensProvider, etc.
│ ├── webview/
│ │ ├── panel.ts # WebviewPanel lifecycle manager
│ │ └── media/ # HTML / CSS / JS for the WebView
│ └── utils/
├── package.json # Extension manifest (contributes, activationEvents)
├── tsconfig.json
└── .vscode/
└── launch.json # Extension Host debug config
3. Activation Events — Always Minimize Scope
// package.json
"activationEvents": [
"onCommand:myExt.doThing", // ✅ Lazy
"onLanguage:python", // ✅ Lazy
"onView:myTreeView" // ✅ Lazy
// ❌ Avoid: "*" (activates on every startup)
]
4. Disposable Management — The Golden Rule
Every subscription/listener must be pushed to context.subscriptions:
export function activate(context: vscode.ExtensionContext) {
const disposable = vscode.commands.registerCommand("myExt.hello", () => {
vscode.window.showInformationMessage("Hello!");
});
context.subscriptions.push(disposable); // ✅ Auto-cleaned on deactivate
}
5. Error Handling Pattern
async function myCommand(): Promise<void> {
try {
await doAsyncWork();
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage(`MyExt: ${msg}`);
// Log to output channel for diagnostics:
outputChannel.appendLine(`[ERROR] ${msg}`);
}
}
Decision Tree: Which API to Use?
Need UI?
├── Simple text input → vscode.window.showInputBox()
├── Pick from list → vscode.window.showQuickPick()
├── File picker → vscode.window.showOpenDialog()
├── Progress indicator → vscode.window.withProgress()
├── Structured tree data → TreeDataProvider + registerTreeDataProvider()
├── Rich HTML content → WebviewPanel (⚠ read webview-security.md first)
└── Status bar text → vscode.window.createStatusBarItem()
WebView — MANDATORY Security Checklist
Before writing any WebView code, read references/webview-security.md.
The non-negotiable rules (summary):
- Always set
localResourceRoots— never leave it as defaultundefined. - Default
retainContextWhenHidden: false— only set totruewhen state persistence is explicitly required. - Generate a fresh nonce per render — apply it to every
<script>and<style>tag. - Set strict CSP on every HTML response.
- Validate every message from the WebView in the extension host — never trust the renderer.
- Never pass raw user data into
webview.html— always sanitize/escape first.
TypeScript Strictness Requirements
Read references/typescript-setup.md for the full tsconfig. Non-negotiable settings:
{
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true
}
Never use any — use unknown and narrow with type guards.
Common Pitfalls
| Pitfall | Correct Pattern |
|---|---|
vscode.workspace.rootPath (deprecated) |
vscode.workspace.workspaceFolders?.[0].uri |
Hardcoded file paths with path.join in WebView src |
webview.asWebviewUri(vscode.Uri.joinPath(...)) |
| Forgetting to dispose event listeners | Push all to context.subscriptions |
postMessage without origin/nonce validation |
Always validate in both directions |
| Blocking the extension host with sync I/O | Use async/await + vscode.workspace.fs |
Direct fs module in WebView scripts |
WebView has no Node.js — use message passing |
Testing
# Unit tests (Vitest — preferred)
npm run test:unit
# Integration tests (requires VS Code window)
npm run test:integration
Test file pattern: src/test/unit/**/*.test.ts
Publishing Checklist
-
publisherfield set inpackage.json -
engines.vscodespecifies minimum version -
README.mddescribes all features -
CHANGELOG.mdexists -
icon.png128×128px - Run
vsce packageand inspect the.vsixbefore publishing -
vsce publish(requires PAT from marketplace.visualstudio.com)
Source: bobosun0713/skills — distributed by TomeVault.