-
Notifications
You must be signed in to change notification settings - Fork 37
Add React webview system with pnpm workspaces #761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Introduce a webview architecture for building rich UI panels using React 19, Vite, and @vscode-elements/react-elements. Webviews are organized as pnpm workspace packages under packages/. Architecture: - packages/shared: Shared types and React hooks for VS Code API - packages/tasks: Example Tasks panel webview - src/webviews/: Extension-side WebviewViewProvider implementations - vite.config.base.ts: Shared Vite config factory for webviews Key features: - Type-safe message passing between extension and webviews - CSP-compliant HTML generation with nonce-based script loading - Vite with SWC for fast development builds - Webview watch via `pnpm dev:webviews` Build commands: - `pnpm build` - Build webviews + extension - `pnpm watch` - Watch extension - `pnpm dev:webviews` - Rebuild webviews on change
7cc1aaf to
487407b
Compare
code-asher
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, looks like a good base for webviews.
| export function postMessage(message: WebviewMessage): void { | ||
| getVsCodeApi().postMessage(message); | ||
| } | ||
|
|
||
| export function getState<T>(): T | undefined { | ||
| return getVsCodeApi().getState() as T | undefined; | ||
| } | ||
|
|
||
| export function setState<T>(state: T): void { | ||
| getVsCodeApi().setState(state); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not seen how this is used yet but do we need the wrappers? Seems like callers could grab the API via getVsCodeApi() and then call whatever they need, without having to duplicate everything.
Not a big deal either way.
If we keep these wrappers though there is probably no need to export getVsCodeApi() (it seems to be unused externally right now). Maybe the wrappers are better actually because it could be confusing why there is both a getVsCodeApi() and an acquireVsCodeApi() with such similar names.
| // Singleton - acquireVsCodeApi can only be called once | ||
| let vscodeApi: WebviewApi<unknown> | undefined; | ||
|
|
||
| declare function acquireVsCodeApi(): WebviewApi<unknown>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we throw a comment on here saying VS Code provides this inside webviews?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually do we need it? I removed it and still get completion and docs for acquireVsCodeApi, but maybe something is just cached.
| @@ -0,0 +1,25 @@ | |||
| import type { WebviewApi } from "vscode-webview"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bit of a nit, but this is in react yet has no React-related code. It feels more webview related than React related to me.
Also instead of calling this @coder/shared what do you think about calling it @coder/webview?
|
|
||
| import type { WebviewMessage } from "../index"; | ||
|
|
||
| // Singleton - acquireVsCodeApi can only be called once |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we expand on this a little, say something like trying to acquire the API more than once will throw an error.
| // No sourcemaps in production (not needed in packaged extension) | ||
| sourcemap: !production, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be nice to have sourcemaps for stack traces.
| pnpm watch # Rebuild extension on changes | ||
| pnpm dev:webviews # Rebuild webviews on changes (run in separate terminal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah bummer is there no way to watch everything with one command?
| }, | ||
| }, | ||
| rules: { | ||
| // Only add React-specific rules, TS rules already applied via **/*.ts config above |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
**/*.ts would not match .tsx would it? Or wait does it?
| "build": "pnpm build:webviews && tsc --noEmit && node esbuild.mjs", | ||
| "build:production": "NODE_ENV=production pnpm build:webviews && tsc --noEmit && node esbuild.mjs --production", | ||
| "build:webviews": "pnpm -r --filter \"./packages/*\" build", | ||
| "dev:webviews": "pnpm -r --filter \"./packages/*\" --parallel dev", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For parity should this be watch:webviews?
| vscode.Uri.joinPath(baseUri, "index.js"), | ||
| ); | ||
| const styleUri = webview.asWebviewUri( | ||
| vscode.Uri.joinPath(baseUri, "index.css"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tasks example has no stylesheet, should we add a blank one for now?
| "tasks", | ||
| ); | ||
|
|
||
| // Re-initialize when visibility changes (webview may have been restored) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the webview is restored does the HTML not get loaded again? Which means it would send ready and we would send back init and would not need this block. Actually having this block could mean we would send init before the plugin said it was ready.
But, if the HTML does stay loaded when the webview is not visible, maybe we should send a visible event instead, so the plugin can tell the difference between a fresh load and being toggled.
Summary
Adds a React-based webview system to the extension using
pnpmworkspaces. This provides the foundation for building rich UI panels (like the Tasks panel) with React while maintaining type safety between the extension and webviews.Architecture
Key Features
WebviewMessagetype shared between extension and webviewsuseMessage()for receiving messages,useVsCodeState()for persisted state@coder/shared(not React code) viaextension.d.tsChanges
@coder/sharedpackage with types and React hooks@coder/tasks-webviewpackage (placeholder Tasks panel)src/webviews/util.tswithgetWebviewHtml()helpersrc/webviews/tasks/TasksPanel.tsWebviewViewProviderpackage.json(visible when authenticated + devMode)CONTRIBUTING.md