Simplifying VS Code Webview Development with vscode-page
Tags: vscode, typescript
Published: 2020-03-15 05:58:08

With Webview, you can build a complex UI for users of your extension. And its development is not a hard work:

1. Design html page
1. Create a WebviewPanel
1. Handle messaging between html and WebviewPanel

In [Webview API Document](https://code.visualstudio.com/api/extension-guides/webview), you can find all you want to kick off this job.

However, as doing that 3-step development cycle again and again, you may find there are some repetive tasks:

- local resource root setting
  - Need to transform relative paths to "vscode-resource" scheme.
- createOrShow logic
- retainContextWhenHidden setting
- the boilerplate code for messaging handling
  - In html page, adding an event listener to change partial content in the same page based on response from WebviewPanel.
  - In WebviewPanel, creating a message processor for all messages from html page.

Based on DRY, if we could abstract away all these tasks, we could spend more time writing business logic. That's why [vscode-page](https://github.com/DTeam-Top/vscode-page) comes.

## What is vscode-page

vscode-page is a light-weight page micro framework for vscode webview, it could accelerate Vs Code Extension Webview development.

Its features:

- abstract away communication between html page and WebviewPanel.
- built-in template engine, with [handlebars.js](https://www.npmjs.com/package/handlebars).
- message mapping, a simple way to organize message handler and view.
- baseUrl support, relative paths can be used in html page.

And here is its archtecture:

![vscode-page architecture](https://raw.githubusercontent.com/DTeam-Top/vscode-page/master/images/architecture.png)

Now, it is time to see how to use it to simplify our vscode Webview development.

## How to use vscode-page

Let's create a "hello world" extension project to show its usage: this extension will load a html page by a command, at the same time a "hello world" will be passed from WebviewPannel to that html page which will show it.

First, create an extension project and install its dependencies:

```shell
yo code hello-page

...

cd hello-page
npm install --save vscode-page
npm install --save handlebars.js
```

Then, replace all lines in extension.ts with the followings:

```typescript
import * as vscode from "vscode";
import { createOrShowPage } from "vscode-page";

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "hello-page" is now active!');

  let disposable = vscode.commands.registerCommand(
    "extension.helloWorld",
    () => {
      createOrShowPage(
        "name", // name
        "ext.home", // viewType
        "Hello Page", // title
        "pages", // localResourceRoot
        "hello.html", // html
        context, // vscode.ExtensionContext
        [
          // Message Mappings
          {
            command: "ready", // command from js function in html
            // message handler
            handler: async () => {
              return {
                body: "hello world"
              };
            },
            // views changed by handler returning
            // [{element-id, handlebars-template}]
            templates: [{ id: "content", content: "{{body}}" }]
          }
        ]
      );
    }
  );

  context.subscriptions.push(disposable);
}

export function deactivate() {}
```

Finally, create hello.html in pages directory which is at the same level as src directory. And its content:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- MUST be added -->
    <base href="{{base}}" />
    <!-- MUST be added -->
    <script type="text/javascript">"{{init}}"</script>
    <title>Hello Page</title>
  </head>
  <body>
    <h1>vsode-page Hello World</h1>
    <!-- element id must be the same template id in MessageMapping -->
    <div id="content"></div>
    <script type="text/javascript">
      initEventListener(); // initialize
      const vscode = acquireVsCodeApi();
      vscode.postMessage({
        command: "ready" // command sent to MessageMappings
      });
    </script>
  </body>
</html>
```

That's it! Now, you can build and start this extension to check result.

## Conclusion

With vscode-page, you don't bother repeating yourself, only focus on core business logics:

- Message format between html page and WebviewPanel
- Fine-graded handlers for each message
- HTML page layout, js function and view templates

Although is a toy project shown in this post, you can find [a more complex example in its repository](https://github.com/DTeam-Top/vscode-page/blob/master/example). Also, you can find more details in [README](https://github.com/DTeam-Top/vscode-page/blob/master/README.md).