Skip to content

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 UI

Every 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.

  • You own everything under panels/, commands/, webview/, services/, models/, helpers/, and shared/api.ts. Edit freely.
  • Generatedsrc/extension/_registry.ts and package.json#contributes are rewritten by gen. Don’t hand-edit them.
  • Framework runtimesrc/shared/vsceasy/* and scripts/gen.ts are owned by vsceasy. Keep them current with vsceasy upgrade; don’t edit them.
  • Extension code → esbuilddist/extension.js (CJS, node target).
  • Each panel/subpanel UI → vitedist/webview/<kind>/<name>/.
  • bun run dev runs both in watch; F5 launches the dev host.
  • bun run package.vsix via @vscode/vsce.

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, or titleActions.
  • Edit contributes.extra.jsongen merges it into package.json#contributes on every run.

You don’t need gen when a hand edit only touches logic:

  • The body of an rpc, run, or getChildren handler.
  • A webview App.tsx (vite recompiles that, not gen).
  • 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.