In my last post, I provide links to some tutorials for creating Webviews and WebviewViewProviders. The tutorials keep the sample code simple and use HTML, CSS, and vanilla JavaScript. No frameworks are involved, so the code relies on native APIs. This can make it easier to understand the code without needing to understand a framework.
You aren’t limited to that approach, however. In fact, you can use any preferred framework such as React, Angular, or Vue. It just requires updating the extension to support it. The community has built quite a few examples. In addition, many extensions are open source, enabling you to see exactly how their code is built. What most of those examples won’t show you is how to move an existing extension from vanilla JavaScript to a framework like React.
The process isn’t as difficult as it sounds. There are only two real challenges. The first challenge is realizing that because a Webview is sandboxed, all of the code it needs must be provided by the extension. That means that any framework code needs to be compiled and bundled, then provided to the Webview. The second challenge is understanding where to make the changes.
As an example, I want to add support for a React .tsx
view to my extension. The view will rely on a CategoryList.tsx
component that renders the checkboxes and provides the logic discussed in the previous post. To do this, I need to make the following changes:
Add the packages
react
andreact-dom
to the package.json file. This provides the extension with the necessary libraries to run React. Depending on your project, you may need other packages as well, but this is the minimum set.Add the
@types/react
and@types/react-dom
packages to thedevDependencies
section of thepackage.json
file. This provides TypeScript support.Update the
tsconfig.json
to include the “DOM” library and JSX support. Having the DOM library is necessary for validation and provides the supporting types definitions for React. Enabling JSX support ensures that the compiler can handle the view file syntax and compile it correctly.1"lib": [ "ES2022", "DOM" ], 2"jsx": "react-jsx"
Create the
.tsx
files. In this example, I will put them in a subdirectory of the extension’ssrc
directory calledviews
. I’ll use that to organize the components. The.tsx
files in that directory will provide the React components for the view. The root view component will beindex.tsx
. A basic implementation might look like this:1import { createRoot} from "react-dom/client"; 2import CategoryList from "./CategoryList.jsx"; 3 4const container = document.getElementById("root"); 5const root = createRoot(container!); 6root.render( 7 <CategoryList/> 8);
Update the
esbuild.js
file to compile your views. Since all of the required code has to be injected into the Webview, we need to replace the.tsx
files with the compiled React application. To do that, just replace theoutfile
entry (which handles a single file) withoutdir
(to support multiple outputs). TheentryPoints
then needs to be changed to use the object syntax. For this example, I need an entry point for the extension so VS Code can load it. I also need a separate entry point for the React application. That code can then be loaded dynamically to be injected into the Webview.1 entryPoints:[ 2- 'src/extension.ts', 3+ { out: 'extension', in: 'src/extension.ts' }, 4+ { out: 'views/index', in: 'src/view/index.tsx' } 5 ], 6- outfile 'dist/extension.js', 7+ outdir: 'dist',
Update the extension to load the compiled React application code into the Webview. In the bullet above, the
outdir
above isdist
and the view bundle is defined asviews/index
(which becomesdist/views/index.js
). Knowing that, we can use this code to load the file:1// Get the output folder that esbuild is creating with the compiled view(s) 2const viewPath = posix.join('dist', 'views'); 3const scriptUri = webview.asWebviewUri( 4 vscode.Uri.joinPath(this._extensionUri, viewPath, 'index.js') 5);
This creates a variable,
scriptUri
that will point to the compiled React application code. That URI can then be used in the Webview’s HTML to load that content.Finally, we need to update the Webview HTML to use the compiled React application code from the previous step by referencing the
scriptUri
variable. The React code above expects to find an element with the idroot
. It will use that to create the application. We need to make sure to also include that element in the HTML. The body of the HTML should look like this:1<body> 2 <div id="root"></div> 3 <script nonce="${nonce}" src="${scriptUri}"></script> 4</body>
Making those changes will allow you to use React in your Webview. It’s worth mentioning that any time the context changes, the Webview can be fully reloaded. While that can mean some unnecessary refreshes, it can also make it easy to make changes and see them reflected in the Webview. Any time the code changes, the ESBuild configuration will automatically recompile the code. The Webview will then load the latest version of that code each time the Webview is created.
To be clear, this isn’t the only way to work with Webviews and React. There are lots of great samples out there, including some that show a great separation-of-concerns, allowing the React app to be developed outside of the IDE and hot-loaded into the extension at development time. This gets you started and hopefully makes it a bit easier to see how little effort is involved to upgrade an existing extension to use a framework like React.
Happy DevOping!