Ken Muse

Using React in Visual Studio Code Webviews


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 and react-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 the devDependencies section of the package.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’s src directory called views. 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 be index.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 the outfile entry (which handles a single file) with outdir (to support multiple outputs). The entryPoints 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 is dist and the view bundle is defined as views/index (which becomes dist/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 id root. 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!