Project layout
A freshly scaffolded project:
my-extension/├── src/│ ├── extension/│ │ ├── extension.ts # bootstrap(registry) — wires VS Code on activate│ │ └── _registry.ts # AUTO-GENERATED by `bun run gen`│ ├── panels/<name>.ts # one file = one webview panel (definePanel)│ ├── commands/<name>.ts # one file = one palette command (defineCommand)│ ├── menus/<name>.ts # activity-bar container + items (defineMenu)│ ├── treeViews/<name>.ts # data-driven tree view (defineTreeView)│ ├── subpanels/<name>.ts # inline webview section inside a menu│ ├── statusBars/<name>.ts # status bar item (defineStatusBar)│ ├── jobs/<name>.ts # scheduled / event-triggered task (defineJob)│ ├── completions/<name>.ts # IntelliSense provider (defineCompletion)│ ├── inlineCompletions/<name>.ts # ghost text (defineInlineCompletion)│ ├── hovers/<name>.ts # hover panel (defineHover)│ ├── typingGuards/<name>.ts # keystroke / paste / delete guard│ ├── decorations/<name>.ts # editor overlays (defineDecoration)│ ├── terminals/<name>.ts # exec + visible terminal (defineTerminal)│ ├── webview/│ │ ├── panels/<name>/ # React UI per panel (App.tsx, main.tsx)│ │ └── components/ # shared themed components (after `components add`)│ ├── services/ # business logic (e.g. <Model>Service.ts)│ ├── models/ # typed entities (after `model add`)│ ├── helpers/ # db.ts, secrets.ts, … (after `db init` / `helper add`)│ └── shared/│ ├── api.ts # RPC contracts (interface per panel)│ └── vsceasy/ # framework runtime — synced via `vsceasy upgrade`├── scripts/gen.ts # registry + contributes generator├── contributes.extra.json # optional — contributions gen doesn't own├── .vscode/launch.json # Extension Development Host launch├── vite.config.ts # webview build└── package.json # esbuild for extension, vite for UIEvery convention directory is optional — gen only writes what it finds. A
project scaffolded with --type language or --type empty has no webview/,
no vite.config.ts and no React dependencies; a language project adds
syntaxes/, snippets/, fileicons/ and language-configuration.json at the
root instead. See Language extensions.
Owned vs generated
Section titled “Owned vs generated”- You own everything under
panels/,commands/,webview/,services/,models/,helpers/, andshared/api.ts. Edit freely. - Generated —
src/extension/_registry.tsandpackage.json#contributesare rewritten bygen. Don’t hand-edit them. - Framework runtime —
src/shared/vsceasy/*andscripts/gen.tsare owned by vsceasy. Keep them current withvsceasy upgrade; don’t edit them.
Build pipeline
Section titled “Build pipeline”- Extension code → esbuild →
dist/extension.js(CJS, node target). - Each panel/subpanel UI → vite →
dist/webview/<kind>/<name>/. bun run devruns both in watch; F5 launches the dev host.bun run package→.vsixvia@vscode/vsce.
When do I need to run gen?
Section titled “When do I need to run gen?”gen rewrites two things: src/extension/_registry.ts (what panels / commands /
menus / jobs / etc. exist) and package.json#contributes (how they’re declared to
VS Code). So:
Run gen when a hand edit changes what exists or how it’s contributed:
- Add, delete, or rename a file in any convention directory —
src/panels/,src/commands/,src/menus/,src/statusBars/,src/subpanels/,src/treeViews/,src/jobs/,src/completions/,src/inlineCompletions/,src/hovers/,src/typingGuards/,src/decorations/,src/terminals/. - Change a panel/command/menu’s
id,title,command,menu,icon,keybinding,when,order, ortitleActions. - Edit
contributes.extra.json—genmerges it intopackage.json#contributeson every run.
You don’t need gen when a hand edit only touches logic:
- The body of an
rpc,run, orgetChildrenhandler. - A webview
App.tsx(vite recompiles that, notgen). - A model, service, store, or helper — those aren’t in the registry.
The vsceasy generators run gen for you. And bun run dev, build, and
launch all run it first — so if you use those, you rarely run gen by hand.