Reactception : extending a VS Code extension with Webviews and React
VS Code has become quite popular among coders these days. Being very open to modifications using extensions really helped to expand the possibilities of what you can do with VS Code.
Lately I was experimenting with VS Code Webviews and I had one of these “what if” moments : what if we put a React application inside a Webview inside an extension inside VS Code ?
Here begins this little Frankenstein experiment, a small walkthrough of what we can do (but probably shouldn’t, more on that later) with VS Code extensions, Webviews and React.
Setting up the project
Every journey has a starting point. Ours will be editing a small config file in Json. We would like to provide a small GUI to edit the file, make some checks and so on.
Here is our sample config file :
We can now begin to code and create our project. The initial setup is quite easy:
- first install VS Code and node.js if you don’t already have them
- in order to create a default extension you can use Yeoman templates : npm install -g yo generator-code
- create the extension project with : yo code
You will be asked several questions to prepare everything, I chose Typescript all along this project project but this up to you.
? What type of extension do you want to create? New Extension (TypeScript)
? What’s the name of your extension? vscode-react
? What’s the identifier of your extension? vscode-react
? What’s the description of your extension? Inception
? Initialize a git repository? Yes
? Which package manager to use? npm
Open the project in VS Code and here we have a basic “Hello world !” extension that you can test by pressing F5 to enter debug mode which will launch a new VS Code window where the extension is available, just press Ctrl+maj+P to display the command palette and type in Hello World.
Adding a language support to the extension
The goal of our extension is to edit config.json files. Therefore we want to be able to use the extension only with Json files.
We will first edit the package.json file which is the global declaration of our extension to VS Code.
In the “contributes” section we add the language part to declare our dummy config language.
As we are no longer making a Hello World extension we can rename the command that we will use replace it with the following declaration
We also need to rename the corresponding activationEvents
With this configuration we can now begin to code our extension. First we change the action to which our code will be attached in the src/extension.ts file.
Now in our extension, instead of displaying
Hello World, we will prompt the user to select a dummyConfig Json file and display the path of this file as an information message. The VS Code API exposes a function named
window.showOpenDialog which takes an
openDialogOptions object to define the dialog properties. See documentation here.
You can test the new command as we did before by launching debug but this time using the modified command name Webview React.
Displaying a basic webview
As we are now able to select the file we want to edit, we can start adding a GUI using the Webviews.
The api documentation for Webviews is very well done and could help you if you need more details for this part.
For now we will begin by creating a folder named
view and a typescript file named
ViewLoader.ts where we will manage our Webview.
In this class we use
createWebviewPanel to create the Webview and then set the content of the Webview with “_panel.webview.html”.
Before testing the basic Webview we just need to call the ViewLoader in our extension where we handle the command instead of calling
And now we can debug our extension and see the webview content.
Creating a React application inside our extension project
Here is the tricky part : how to manage an extension for VS Code and a React application independently inside the same project ?
All the magic will come from npm and Webpack with the help of some plugins ;-)
First thing to do is to include in our project all the libraries that we will need :
- React, obviously :
npm install react react-dom
- As I use Typescript I need to include the types but you might not if you use classic JS :
npm install — save-dev @types/react @types/react-dom
- In order to build our React application we will use Webpack :
npm install — save-dev webpack webpack-cli
- And some tools: ts-loader obviously for Typescript, style and css-loader for… style and css.
- Finally npm-run-all will allow us to chain the build task for the extension and for the react app — npm install — save-dev ts-loader style-loader css-loader npm-run-all
We can now create the folder for our react application, let’s name it “app” and place it inside the view folder. Then create the files “index.css” “index.tsx” and “tsconfig.json” in the “app” folder.
In the react application tsconfig.json file we declare that we use react and to use a “configViewer” folder as an output directory at the root of our extension project, this folder will be useful later to bind the extension and the react application.
We need to update the package.json of our extension to declare the two compilations tasks we now have, one for the extension and one for the react application.
We change the “compile” and “watch” tasks and add several sub-tasks
At the root of our extension we add the configuration file for webpack, “webpack.config.js”. No tricky part here, we declare the usage of our different loader, we define the entry point of the react application and we set up again the output folder of our react application as the “configViewer” folder.
Edit the root “tsconfig.json” to exclude the react application from the scope : “**/view/app/**”
Edit the .gitignore to add an entry for “configViewer/configViewer.js” in order to ignore the compiled file of our react application and finally add the content in the “index.tsx” to have a basic react application.
Use the react application inside the webview
As I am using Typescript you can guess that I like to type everything. That is true also for the configuration file we are trying to edit.
We will introduce a model.ts file which will contain the interfaces for our configuration types and place it in the view/app folder.
As we will be using this file inside the React application but also inside our extension we have to tell our typescript compiler to include this file. In the tsconfig.json for the extension (in the root folder) we have to edit the exclusion part to exclude content of the view/app folder but not the model.ts file, this will allow us to use it in the extension.
In our Viewloader we can now load our file and deserialize its content in our class constructor:
First we need to tell the webview where the js file to load is located, to do so we need to have the current path of the extension and pass it to the getWebviewContent function (we get it from the context of our extension).
Enable script is a parameter when we create the webview where we also give the authorized path to load the scripts from.
We also need to declare the file types authorization inside the webview template by defining in the header of the webview the Content-Security-Policy
Notice that we also defined a variable window.acquireVsCodeApi which is a function exposed by VS Code to allow communication between VS Code and the webview. Indeed we have to manage the lifecycle of the webview as everytime the tab lose the focus in VS Code the webview will be destroyed and recreated when the focus comes back.
The “vscodeApi” in our small application will allow us to manage a state of the webview which allow us to persist data when the webview is destroyed and recreated.
To manage this lifecycle we must pass these variables to our application.
In the index.tsx we add an interface to declare these variables and execute the acquireVsCodeApi function.
These variables will be passed in the component that we will now create to display the content of the configuration, let’s call it “config.tsx”.
The first thing we want this component to do is to manage the state and display some basic content so in the constructor of our component we look at the initial data from the props or the VS Code api state for data to use and set it in the component state.
And finally we can put something in our render function to display the list of users and other data from our configuration file and use the component in our index.tsx file.
To make things a little bit prettier let’s add somme CSS in our index.css file
How to make the React application communicate
VS Code api exposes a way to send “commands” to and from the webview. It is also possible to call specific VS Code action using URI but we will not talk about this part here.
The action we would like VS Code to do for us is obviously to save the configuration once we are done editing it but for now we can only display the configuration and not edit it, let’s solve this.
We will focus only on user management and add to behavior in our React application : manage roles and users.
In our configuration file we have a list of users so we could add a user. For each user we have a “isActive” boolean already mapped to a checkbox so we can edit it and we have a list of roles, here again we will be able to add a role to a user.
We add a function onChangeUserActiveState to handle… change on the isActive checkbox and two text inputs and their corresponding function to add auser or a role.
As we previously talk about, to manage webviews lifecycle we need to update a second “state” so let’s create a function for this:
Now that we can edit our configuration saving it is quite simple, we need to define a type for the messages we will exchange, send the message when we hit a “save” button and then intercept the message in our ViewLoader to execute the command.
In our common model we add an interface ICommand which will tell us what type of command we want to do (saving the file) and also contain the corresponding data. To know the type of command to execute we also declare a CommandAction enum which is pretty straightforward.
In the React application we add a “save” button and the corresponding event handler saveConfig which create the command adn just call VS Code Api with function postMessage, quite easy.
And the last part is of course to handle the command. In the ViewLoader we “subscribe” to message from the webview by calling the function onDidReceiveMessage on the webview class.
When calling the “save” command we will write the new content of the file and display an information message to confirm the action.
The webviews implementation in VS Code make it really easy to add a custom interface for your extension and with the magic tricks with webpack even using React is really simple.
But as I warned at the beginning of this short journey, and as Professor Ian Malcolm said in Jurassic Park: “You were so preoccupied whether or not you could that you didn’t stop to think if you should”.
And that is the main problem, webviews will take a lot of resources inside VS Code so we should be really careful when using it, especialy if we inject a React application inside it. Always ask if what you are doing can’t already be done in VS Code using the API.
Anyway here you have just an example of what can be done with VS Code and webviews but many others ideas are possible like displaying a unified view to edit i18n localisation in an Angular application which could extend the extension created by Oleksandr Reznichenko (https://medium.com/younited-tech-blog/my-way-to-vs-code-extension-cdf84cdb36ba).
Source code of every step can be found here https://github.com/Ciaanh/reactception