Build VS Code extensions with vue.js

Visual Studio (VS) Code is one of the most popular Code editors developers use for their daily tasks. It is built with scalability in mind. In some ways, most of VS Code’s core features are built as extensions. You can check out VS Code Extension repository (github.com/microsoft/v…) To understand what I’m talking about.

Underneath VS Code is an electronic (www.electronjs.org/) cross-environment application that runs on UNIX, Mac OSX, and Windows operating systems. Because it is an electronic application, you can extend it by writing JavaScript plug-ins. In fact, any language that can be converted to JavaScript can be used to build extensions. For example, the VS Code documentation website prompts you to write VS Code extensions using TypeScript (www.typescriptlang.org/). All Code examples from the VS Code team (github.com/microsoft/v…) All built in TypeScript.

VS Code supports a wide range of API, you can in VS Code API (code.visualstudio.com/api) to see and read.

VS Code allows you to extend almost everything it supports. You can build custom commands, create new color themes, embed custom HTML in webViews, contribute to the active bar by adding new views, use tree views to display hierarchical data on the sidebar, and many other extensibility options. Extensions overview page (code.visualstudio.com/api/extensi…). All the VS Code extensions are covered in detail. If you want to skip the overview and go directly to the function of how to build a practical extension of detailed information, please see the extension guide page (code.visualstudio.com/api/extensi…). .

Building extensions in VS Code is a huge topic that could be covered in many books and countless articles. In this article, I will focus on:

  • Create the VS Code command
  • Embed vue.js applications in Webview panels and views using the Webview API
  • Add the view container to the activity bar

VS Code UI architecture

Before I delve into building extensions, it’s important to understand the parts and pieces that make up VS Code’s UI.

I’ll borrow two diagrams from the VS Code website to help illustrate these concepts. Figure 1 illustrates the main parts of the VS Code UI.

Figure 1: VS Code main section

VS Code has the following main parts:

  • Active bar: Each icon on the active bar represents a view container. This container, in turn, hosts one or more views within it. In addition, you can extend existing ones. For example, you can add a new view to the Explorer view.
  • Sidebar: The sidebar is the container that hosts the view. For example, you can add a tree view or Web view view to the sidebar.
  • Editor: The editor hosts different types of editors that VS Code uses. For example, VS Code uses a text editor to allow you to read/write files. Another editor allows you to edit workspace and user Settings. For example, you can also contribute your own editor using Webview.
  • Panels: Panels allow you to add view containers with views.
  • Status bar: The status bar holds status bar items that can be displayed using text and ICONS. You can also think of them as commands that trigger actions when you click on them.

Figure 2 illustrates the main parts of the VS Code UI.

Figure 2: VS Code partial details

  • The Activity Bar hosts the View Containers, which in turn hosts Views.
  • A view has a view toolbar.
  • The sidebar has a sidebar toolbar.
  • The editor has an editor toolbar.
  • The panel hosts the view container, which in turn hosts the view.
  • The panel has a panel toolbar.

VS Code allows us to extend any major and minor part using its API.

The VS Code API is rich enough to allow developers to extend almost everything it offers.

Your first VS Code extension

To begin building your own custom VS Code extension, make sure you have Node.js (nodejs.org/en/) and Git (git-scm.com/) installed on your computer. Needless to say, you also need to be installed on a computer VS Code (code.visualstudio.com/download).

I will use Yeoman (yeoman.io/) CLI to generate a new VS Code extension project. Provide and support Microsoft yukio okamoto, Code (www.npmjs.com/package/gen.) Yeoman generator to build full VS Code extensions in TypeScript or JavaScript.

Let’s go!

Step 1

Install the Yeoman CLI and Yo code generator by running the following command:

npm install -g yo generator-code
Copy the code

Step 2

Run the following command to build a TypeScript or JavaScript project ready for development.

yo code
Copy the code

During the creation of a new VS Code extension project, the Code generator asks questions. I’ll use them to create the application.

Figure 3 shows the starting point for the generator.

Figure 3. Start the extension project scaffolding

You can choose TypeScript or JavaScript. Most of the examples you find on the web are written in TypeScript. It would be wise to use TypeScript to make your life easier when writing and authoring extensions.

Next, you need to provide the name of the extension, as shown in Figure 4.

Figure 4 Naming the VS Code extension

Now you specify the identifier (ID) for the extension. You can leave the default Settings or provide your own. I prefer not to use Spaces or dashes (-) to separate identifier names.

You can then provide a description of the extension.

The next three questions are shown in Figure 5.

  • Initialize the Git repository? Yes.
  • Bundle extensions with Webpack? Yes.
  • Which package manager to use? New Product Manager

Figure 5 completes the code generator scaffolding

The generator takes all your answers and scaffolding your application. Once done, go to the new extensions folder and open VS Code by running the following command:

cd vscodeexample && code .
Copy the code

Step 3

Let’s quickly explore the extension project.

Figure 6 lists all the files Yo Code has generated for you.

Figure 6 project file

  • the/.vscode/The directory contains configuration files that help us easily test extensions.
  • the/dist/The directory contains the compiled version of the extension.
  • the/src/The directory contains the source code you wrote to build the extension.
  • thepackage.jsonFile is the default NPM configuration file. You can use this file to define your custom commands, views, menus, and more.
  • thevsc-extension-quickstart.mdThe file contains an introduction to the extension project and documentation on how to start building VS Code extensions.

Microsoft provides the Yo Code Yeoman generator to help you quickly and easily build VS Code extension projects.

Step 4

Open the package.json file and let’s explore the important parts needed to build this extension.

"contributes": {
    "commands": [{"command": "vscodeexample.helloWorld"."title": "Hello World"}},Copy the code

You can define custom commands in the contribution. When defining a new command, you must at least provide the command and its title. This command should uniquely identify your command. By default, the command used is a concatenation of the extension identifier you specified when constructing the extension with any string representing the command you provided. New commands are now displayed automatically in the command panel.

VS Code defines a number of built-in commands that you can even use programmatically. For example, you can perform the workbench. Action. NewWindow command to open a new VS Code examples.

This is a list of built-in commands in the VS Code complete (code.visualstudio.com/api/referen…). .

This command does not perform any operation temporarily. You still need to bind this command to the command handler I’ll define soon. VS Code provides the ability for registerCommand() to do associations for you.

You should define an activation event that activates the extension when the user fires the command. It is the activation event that lets VS Code locate the command and bind it to the command handler. Keep in mind that extensions are not always activated by default. For example, when you open a file with a specific file extension, the extension might be activated. This is why you need to make sure that extensions are activated before running any commands.

The package.json file defines the activationEvents section:

"activationEvents": [ "onCommand:vscodeexample.helloWorld"].Copy the code

When the user invokes a command from the command palette or through a key binding, the extension is activated and the registerCommand() function binds (vscodeexample.helloWorld) to the correct command handler.

Step 5

Now it’s time to explore the extended source code and register the command with the command handler. The extension source code is in the/SRC /extension.ts file. I have cleaned up this file as follows:

import * as vscode from 'vscode';

export function activate(
    context: vscode.ExtensionContext) { context.subscriptions.push(...) ; }export function deactivate() {}
Copy the code

VS Codeactivate() calls this function when you want to activate the extension. Also, when it calls the deactivate() function, it wants to deactivate it. Remember that the extension is activated only when one of the activation events that you declare occurs.

If you instantiate objects in a command handler and want VS Code to release them for you later, push the new command registration to the Context.Subscriptions array. VS Code maintains this array and will do garbage collection on your behalf.

Let’s register the Hello World command as follows:

context.subscriptions.push(
    vscode.commands.registerCommand(
       'vscodeexample.helloWorld'.() = > {
           vscode.window.showInformationMessage('... '); }));Copy the code

This VS Code object is key to accessing the entire VS Code API. Registering command handlers is similar to registering DOM events in JavaScript. This code binds the same command identifier (the one you declared earlier under the Commands and activationEvents sections in the package.json file) to the command handler.

VS Code displays an informational message when the user triggers a command.

Let’s test the extension F5 by clicking. VS Code opens a new instance with the new extension loaded. To trigger a command, open the command panel and begin typing “Hello”.

Figure 7 shows how VS Code filters the available commands to the ones you want.

Figure 7 Command panel for filtering

Now click the command, and Figure 8 shows how the information message appears in the lower right corner of the editor.

Figure 8 shows the information message

A: congratulations! You just finished your first VS Code extension!

Build VS Code extensions with vue.js using Vue CLI

Let’s use your new knowledge to create something even more interesting!

In this section, you will use the Vue CLI to create a new vue.js application and host it as a separate editor in a Webview.

The Webview API allows you to create fully customizable views in VS Code. I like to think of webViews as iframes in VS Code. It can render any HTML content within this framework. It also supports two-way communication between the extension and the loaded HTML page. Views can publish messages to extensions and vice versa.

The Webview API supports two types of views THAT I’ll explore in this article:

  • WebviewPanel is a wrapper around a Webview. It is used to display webViews in VS Code’s editor.
  • A WebviewView is a wrapper around a Webview. It is used to display the Web view in the sidebar.

In both types, the Webview hosts HTML content!

The Webview API documentation is rich and contains all the detailed information you need to use it. In the Webview API (code.visualstudio.com/api/extensi…). On the view.

Let’s start building the vue.js sample application and hosting it in VS Code’s editor.

Webview allows you to enrich your VS Code extensions by embedding HTML content along with JavaScript and CSS resource files.

Step 1

Use Yeoman to generate a new VS Code extension project, as described above.

Step 2

Add a new command to open the vue.js application. Find the package.json file and add the following:

"contributes": { 
    "commands": [{"command": "vscodevuecli:openVueApp"."title": "Open Vue App"}},Copy the code

The identifier of this command is vscodevuecli:openVueApp.

You then declare an activation event as follows:

"activationEvents": ["onCommand:vscodevuecli:openVueApp"].Copy the code

Step 3

Switch to the extension.js file and register the command handler inside the activate() function.

context.subscriptions.push( vscode.commands.registerCommand( 'vscodevuecli:openVueApp', () => { WebAppPanel.createOrShow(context.extensionUri); }));Copy the code

In the command handler, you are instantiating a new instance of the WebAppPanel class. It’s just a wrapper around a WebviewPanel.

Step 4

In this step, you will generate a new vue.js application using the Vue CLI. In accordance with the guidelines (cli.vuejs.org/guide/creat…). /web/ Build a new vue.js application in the root directory of the extension project.

Be sure to place any images you use in the /web/img/ directory. Later, you will copy dist into this directory when compiling the application.

Typically, the HTML page hosting the vue.js application requests to render the image from the local file system on the server. However, when the Webview loads the application, it can’t just request and access the local file system. For security reasons, webViews should be limited to a few directories within the project itself.

In addition, VS Code uses special URIs to load any resources in the Webview, including JavaScript, CSS, and image files. Therefore, you need an approach based on all images, so you use VS Code’s URIs for accessing local resources. As you’ll see in Step 5, this extension injects the VS Code base URI into the HTML body of the Webview so that vue.js applications can use it to build their images.

So, to use the injected base URI, you add a vue.js mixin that reads the value of the base URI from the HTML DOM and makes it available to the vue.js application.

Please note that if you want to run vue.js applications outside of a Webview, you need to put the following in the /web/public/index.html file:

<body>   
    <input hidden data-uri="">.</body>
Copy the code

In/web/SRC/mixins/ExtractBaseUri js file, define a new Vue. Js mixins. It enables any vue.js component of baseUri to use the data option:

data() {
    return {
        baseUri: ' '}; },Copy the code

It then uses the vue.jsmounted () lifecycle hook to extract the value:

mounted() {
    const dataUri = document.querySelector('input[data-uri]'); 
    if(! dataUri)return;

    this.baseUri = dataUri.getAttribute('data-uri');
},
Copy the code

If it finds an input field with a data attribute named data-URI, it reads the value and assigns it to the baseUri attribute.

The next step is to provide mixins in the /web/ SRC /main.js file:

Vue.mixin(ExtractBaseUri);
Copy the code

Switch to the app.vue component and replace the image element with the following:

<img alt="Vue logo" :src="`${baseUri}/img/logo.png`">
Copy the code

Now that the application is ready to run locally and within the Webview, let’s customize the compilation process through the vue.js configuration file.

Create a new file. Listing 1 shows the complete source code for this file. /web/vue.config.js

Listing 1:vue.config.js

const path = require('path');

module.exports = {
    filenameHashing: false.outputDir: path.resolve(__dirname, ".. /dist-web"),  
    chainWebpack: config= > {   
        config.plugin('copy') 
            .tap(([pathConfigs]) = > {
                const to = pathConfigs[0].to
                // so the original `/public` folder keeps priority
                pathConfigs[0].force = true

                // add other locations.
                pathConfigs.unshift({ 
                    from: 'img'.to: `${to}/img`,})return [pathConfigs]    
            })
    },
}
Copy the code

Basically, you are doing the following:

  • Removes the hash from the compiled file name. The compiled JavaScript file looks like app.js, but without any hash values in the filename.
  • Set the output directory to/dist-web/Vue CLI uses this property to determine where to place compiled application files.
  • will/web/img/The directory and all its contents are copied to the destination directory.

Next, let’s fix the NPM script so that you can compile both the extension file and the vue.js application using a single script.

First, Concurrently install the NPM package by running the following command:

npm i --save-dev concurrently
Copy the code

Then, locate the package.json file and replace the monitor script with:

"watch": "concurrently \"npm --prefix web run serve\" \"webpack --watch\"".Copy the code

Now, the monitor script compiles the vue.js application and extension files every time you change any files in either folder.

Run the following command to compile both applications and generate the /dist-web/ directory:

npm run watch
Copy the code

That’s it! The vue.js application is ready to be hosted in a Web view.

Step 5

Add a new TypeScript file to the/SRC/directory and name it webapppanel.ts. Listing 2 contains the complete source code for this file. Let’s dissect it and explain its most relevant parts.

Listing 2: webapppanel.ts

import * as vscode from "vscode";
import { getNonce } from "./getNonce";

export class WebAppPanel {

    public static currentPanel: WebAppPanel | undefined;

    public static readonly viewType = "vscodevuecli:panel";

    private readonly _panel: vscode.WebviewPanel;  
    private readonly _extensionUri: vscode.Uri;  
    private _disposables: vscode.Disposable[] = [];

    public static createOrShow(extensionUri: vscode.Uri) { 
        const column = vscode.window.activeTextEditor
        ? vscode.window.activeTextEditor.viewColumn: undefined;

        // If we already have a panel, show it.      
        if (WebAppPanel.currentPanel) {
            WebAppPanel.currentPanel._panel.reveal(column);
            return;     
        }
        
        // Otherwise, create a new panel. 
        const panel = vscode.window.createWebviewPanel(
            WebAppPanel.viewType,
            'Web App Panel',
            column || vscode.ViewColumn.One,
            getWebviewOptions(extensionUri),
        );

        WebAppPanel.currentPanel = new WebAppPanel(panel, extensionUri);    
    }

    public static kill(){ WebAppPanel.currentPanel? .dispose(); WebAppPanel.currentPanel =undefined; 
    }

    public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {    
        WebAppPanel.currentPanel = new WebAppPanel(panel, extensionUri);  
    }

    private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {    
            this._panel = panel;    
            this._extensionUri = extensionUri;

        // Set the webview's initial html content    
            this._update();

            this._panel.onDidDispose(() = > this.dispose(), 
                null.this._disposables);
            
        // Update the content based on view changes 
            this._panel.onDidChangeViewState(  
                e= > {
                    if (this._panel.visible) {  
                        this._update(); }},null.this._disposables
            );

            // Handle messages from the webview  
            this._panel.webview.onDidReceiveMessage(    
                message= > {
                    switch (message.command) {
                        case 'alert': vscode.window.showErrorMessage(message.text); 
                        return; }},null.this._disposables 
            );
        }

        public dispose() {    
            WebAppPanel.currentPanel = undefined;  

            // Clean up our resources  
            this._panel.dispose();

            while (this._disposables.length) {
                const x = this._disposables.pop(); 
                    if (x) {
                    x.dispose();
                    }
            }
        }

        private async _update() {
            const webview = this._panel.webview;    
            this._panel.webview.html = this._getHtmlForWebview(webview);  
        }
        
        private _getHtmlForWebview(webview: vscode.Webview) {    
            const styleResetUri = webview.asWebviewUri(      
                vscode.Uri.joinPath(this._extensionUri, "media"."reset.css"));const styleVSCodeUri = webview.asWebviewUri(    
                vscode.Uri.joinPath(this._extensionUri, "media"."vscode.css"));const scriptUri = webview.asWebviewUri( 
                vscode.Uri.joinPath(this._extensionUri, "dist-web"."js/app.js"));const scriptVendorUri = webview.asWebviewUri(
                vscode.Uri.joinPath(this._extensionUri, "dist-web"."js/chunk-vendors.js"));const nonce = getNonce();  
            const baseUri = webview.asWebviewUri(vscode.Uri.joinPath(
                this._extensionUri, 'dist-web')
                ).toString().replace('% 22'.' ');

            return ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="${styleResetUri}" rel="stylesheet">
                    <link href="${styleVSCodeUri}" rel="stylesheet">
                    <title>Web App Panel</title>
                </head>
                <body>
                <input hidden data-uri="${baseUri}">
                    <div id="app"></div>  
                    <script type="text/javascript"
                        src="${scriptVendorUri}" nonce="${nonce}"></script>  
                    <script type="text/javascript"
                        src="${scriptUri}" nonce="${nonce}"></script>
                </body>
                </html> 
            `; }}function getWebviewOptions(extensionUri: vscode.Uri) :vscode.WebviewOptions {    
    return {
        // Enable javascript in the webview
        enableScripts: true.localResourceRoots: [  
            vscode.Uri.joinPath(extensionUri, 'media'),  
            vscode.Uri.joinPath(extensionUri, 'dist-web')]}; }Copy the code

You define the WebAppPanel class as a singleton to ensure that it always has only one instance. This is done by adding the following:

public static currentPanel: WebAppPanel | undefined;
Copy the code

It wraps an instance of a WebviewPanel and tracks it by defining the following:

private readonly _panel: vscode.WebviewPanel;
Copy the code

The core of this createOrShow() function is the WebAppPanel class. It checks to see if currentPanel has been instantiated and displays a WebviewPanel immediately.

if (WebAppPanel.currentPanel) {   
    WebAppPanel.currentPanel._panel.reveal(column);
    return;
}
Copy the code

Otherwise, it instantiates a new WebviewPanel using the createWebviewPanel() function as follows:

const panel = vscode.window.createWebviewPanel(   
    WebAppPanel.viewType,
    'Web App Panel',    
    column || vscode.ViewColumn.One,    
    getWebviewOptions(extensionUri),
);
Copy the code

This function takes the following arguments:

  • viewType: Specifies the unique identifier of the view typeWebviewPanel
  • The title: the titleWebviewPanel
  • showOptionsIn:WebviewDisplay position in the editor
  • options: New SettingsPanel

The options are prepared inside the getWebviewOptions() function.

function getWebviewOptions(   
    extensionUri: vscode.Uri
) :vscode.WebviewOptions {
    return {    
        enableScripts: true.localResourceRoots: [
            vscode.Uri.joinPath(extensionUri, 'media'),
            vscode.Uri.joinPath(extensionUri, 'dist-web')]}; }Copy the code

It returns an object with two attributes:

  • EnableScripts: Controls whether scripts are enabled in Webview content
  • LocalResourceRoots: Specifies the root path through which the Webview can load local resources using urIs (common resource identifiers that represent files on disk or any other resource). This ensures that the extension cannot access files outside the path you specify.

A WebviewPanel wraps a Webview to render in the VS code editor.

The createOrShow() function ends WebAppPanel by calling its private constructor to set its value to a new instance of currentPanel.

The most important part of the constructor sets the HTML content of the Webview as follows:

this._panel.webview.html = this._getHtmlForWebview(webview);
Copy the code

The _getHtmlForWebview() function prepares and returns HTML content.

You will embed two CSS files in almost every Web view you create. The reset.css file resets some CSS properties in the Web view. Although the vscode.css file contains VS Code’s default theme colors and CSS properties. This is critical to giving your Webview the same look and feel as any other editor in VS Code.

const styleResetUri = webview.asWebviewUri(   
    vscode.Uri.joinPath(this._extensionUri, "media"."reset.css"));

const styleVSCodeUri = webview.asWebviewUri(   
    vscode.Uri.joinPath(this._extensionUri, "media"."vscode.css"));
Copy the code

The _extensionUri property represents the URI of the directory containing the current extension. The WebviewasWebviewUri() function converts the URI of the local file system to a URI that can be used in Webviews. They cannot use file: URIs to load resources directly from a workspace or local file system. The asWebviewUri() function takes a local file: URI and converts it to a URI that can be used in a Webview to load the same resource.

This function then prepares urIs for other resources, including the js/app.js and js/chunk-vendors. Js files compiled by the Vue CLI in Step 5.

Remember that in Step 4, the Vue CLI copies all the images in the /dist-web/img/ directory. All image paths in the vue.js application use a base URI that points to the VS Code URI when run in Webview or file: URI when run in standalone mode.

At this stage, you need to generate a VS Code base URI and inject it into the hidden input fields that vue.js loads and reads through vue.js mixins.

WebAppPanel generates the extended VS Code base URI using the following Code:

const baseUri = 
    webview.asWebviewUri(
        vscode.Uri.joinPath(
            this._extensionUri, 'dist-web'
        )
    ).toString().replace('% 22'.' ');
Copy the code

It communicates this URI to the vue.js application by setting the data-URI data attribute value on a hidden input field in the HTML page that loads the vue.js application at the same time.

Finally, the function inserts all CSS and JavaScript URIs into the HTML page content and returns it.

!

Let’s run the extension by clicking the F5 key and start typing “Open Vue App” in the command panel of the VS Code instance you just opened, as shown in Figure 9.

Figure 9 Opening the Vue App command

Click the command to load the vue.js application in the Web view of the new editor window, as shown in Figure 10.

Figure 10: Loading the Vue application in the VS Code extension

This is all you need to load the vue.js application generated by the Vue CLI in the VS Code extension.

Build VS Code extensions with vue.js using rollup.js

In this section, I’ll expand on what you’ve built so far and introduce a new scenario where the Vue CLI might not be the right tool for the job.

As you know, the Vue CLI compiles the entire vue.js application into a single app.js file. Let’s shelve the chunking functionality provided by the CLI for a moment.

When building a VS Code extension, sometimes you need to load an HTML page in the editor’s WebviewPanel. At the same time, you might need the WebviewView to load another HTML page inside a in the sidebar. Of course, you can build your HTML using pure HTML and JavaScript, but since you want to build your HTML pages using vue.js, Vue CLI is not an option in this case.

You need to create a vue.js application that contains multiple independent small vue.js components that are compiled into separate JavaScript files, not just merged into a single app.js file.

I came up with a solution that involved creating miniature vue.js applications with at least two files. A JavaScript file and one or more vue.js components (the root component with many child components). The JavaScript file imports the vue.js framework and mounts the corresponding vue.js root component into the DOM within the HTML page.

For this solution, I decided to use rollup.js (rollupjs.org/) to compile the file.

Let’s explore this solution together by building a new VS Code extension that does two things:

  • Use a WebviewPanel to host the vue.js application (or root component) in the new editor
  • Use WebviewView to host the vue.js application (or root component) in the sidebar

Step 1

Use Yeoman as before to generate a new VS Code extension project.

Step 2

Add a new command to open the vue.js application. Find the package.json file and add the following:

"contributes": {
    "commands": [{"command": "vscodevuerollup:openVueApp"."title": "Open Vue App"."category": "Vue Rollup"}},Copy the code

The command identifier for vscodevuerollup: openVueApp.

Then you declare an activation event:

"activationEvents": ["onCommand:vscodevuerollup:openVueApp"].Copy the code

In addition, define a new View Container to load within the Activity Bar. Listing 3 shows what you need to add to the package.json file.

Listing 3: Adding a view container

    "viewsContainers": {
        "activitybar": [{"id": "vscodevuerollup-sidebar-view"."title": "Vue App"."icon": "$(remote-explorer)"}},"views": {
        "vscodevuerollup-sidebar-view": [{"type": "webview"."id": "vscodevuerollup:sidebar"."name": "vue with rollup"."icon": "$(remote-explorer)"."contextualTitle": "vue app"}},Copy the code

The ID of the active bar entry is vscodevuerollup-sidebar-view. This ID matches the ID of the collection of views that will be hosted in this view container and defined in the View section.

"views": {"vscodevuerollup-sidebar-view": [...]. }Copy the code

The (vscodevuerollup-sidebar) entry represents a collection of views. Each view has an ID.

{   
    "type": "webview"."id": "vscodevuerollup:sidebar"."name": "vue with rollup"."icon": "$(remote-explorer)"."contextualTitle": "vue app"
}
Copy the code

Make a note of the ID vscodevuerollup:sidebar, scroll up to the activatinEvents section and add the following entries:

"onView:vscodevuerollup:sidebar"
Copy the code

When using the onView declaration, VS Code activates the extension when a view with the specified ID is expanded on the sidebar.

Step 3

Switch to the extension.js file and register the command handler inside the activate() function.

First of all, the registered vscodevuerollup: openVueApp command:

context.subscriptions.push(
    vscode.commands.registerCommand(
        'vscodevuerollup:openVueApp'.async(args) => { WebAppPanel.createOrShow(context.extensionUri); }));Copy the code

Then register vscodevuerollup: sendMessage command:

const sidebarProvider = new SidebarProvider(context.extensionUri);

context.subscriptions.push(
    vscode.window.registerWebviewViewProvider(
        SidebarProvider.viewType,
        sidebarProvider
    )
);
Copy the code

You are to instantiate a new instance of the class SidebarProvider and use the vscode. Window. RegisterWebviewViewProvider () function to register this provider.

Here, you’re dealing with the second type of Webview I mentioned earlier, WebviewView. To load a Webview into the sidebar, you need to create a class that implements the WebviewViewProvider interface. It’s just a WebviewView.

The WebviewViewProvider wraps a WebviewView, which wraps a Webview. Render in the sidebar of WebviewViewVS Code.

Step 4

In this step, you will create a custom vue.js application. Start by creating the /web/ directory in the extension’s root folder.

Within this directory, create three different subdirectories:

  • Pages: This directory contains all vue.js pages.
  • Components: This contains all vue.js single-file components (SFC).
  • Img: This contains all the images you use in the vue.js component.

Let’s add the first vue.js page by creating a /web/pages/ app.js file and pasting the following code into it:

import Vue from "vue";
import App from "@/components/App.vue";

new Vue({render: h= > h(App)}).$mount("#app");
Copy the code

There is no magic here! It is the same code that the Vue CLI uses in the main.js file to load and mount vue.js applications on the HTML DOM. However, in this case, I just installed a vue.js component. Think of this component as the root component that might use other vue.js components in a tree hierarchy.

Note that I app.vue borrowed the same file from the CLI vue file you created earlier.

Let’s add another page by creating /web/pages/Sidebar. Js file and pasting this code into it:

import Vue from "vue";
import Sidebar from "@/components/Sidebar.vue";

new Vue({render: h= > h(Sidebar)}).$mount("#app");
Copy the code

This page loads and mounts the Sidebar. Vue component.

Listing 4 shows the complete Sidebar. Vue component. It defines the following UI sections:

  • Displays messages received from an extension.
  • Allows users to send messages to extensions from vue.js applications.
  • Execute commands on the extender to load the app.js page in the Web view within the editor.

Listing 4: sidebar.vue component

<template>  
    <div> 
        <p>Message received from extension</p>  
        <span>{{ message }}</span>

        <p>Send message to extension</p>
        <input type="text" v-model="text">
        <button @click="sendMessage">Send</button>

        <p>Open Vue App</p>
        <button @click="openApp">Open</button>
    </div>
</template>

<script> export default {
    data() {
        return {     
            message: ' '.text: ' '}; },mounted() {
        window.addEventListener('message'.this.receiveMessage);
    },
    beforeDestroy() {    
        window.removeEventListener('message'.this.receiveMessage); 
    },  
    methods: {     
        openApp() {     
            vscode.postMessage({
                type: 'openApp'});this.text = ' ';  
        },
        sendMessage() { 
            vscode.postMessage({
                type: 'message'.value: this.text,
            }); 
            this.text = ' ';  
        },
        receiveMessage(event) {
            if(! event)return;    
            
            const envelope = event.data;
            switch (envelope.command) {
                case 'message': { 
                    this.message = envelope.data;  
                    break; }}; }},}</script>

<style scoped>
    p {
        margin: 10px 0; 
        padding: 5px 0;
        font-size: 1.2 rem;
    }
    span {  
        display: inline-block;
        margin-top: 5px;  
        font-size: 1rem;
        color: orange;
    }
    hr {
        display: inline-block;  
        width: 100%;  
        margin: 10px 0;
    }
</style>
Copy the code

Navigate to the extension root and add a new rollup.config.js file.

Listing 5 shows the complete contents of the file.

Listing 5:rollup.config.js

import path from "path";
import fs from "fs";

import alias from '@rollup/plugin-alias';
import commonjs from 'rollup-plugin-commonjs';
import esbuild from 'rollup-plugin-esbuild';
import filesize from 'rollup-plugin-filesize';
import image from '@rollup/plugin-image';
import json from '@rollup/plugin-json';
import postcss from 'rollup-plugin-postcss';
import postcssImport from 'postcss-import';
import replace from '@rollup/plugin-replace';
import resolve from '@rollup/plugin-node-resolve';
import requireContext from 'rollup-plugin-require-context';
import { terser } from 'rollup-plugin-terser';
import vue from 'rollup-plugin-vue';

constproduction = ! process.env.ROLLUP_WATCH;const postCssPlugins = [  
    postcssImport(),
];

export default fs  
    .readdirSync(path.join(__dirname, "web"."pages"))  
    .map((input) = > {   
        const name = input.split(".") [0].toLowerCase();  
        return {     
            input: `web/pages/${input}`.output: {
                file: `dist-web/${name}.js`.format: 'iife'.name: 'app'.sourcemap: false,},plugins: [
                commonjs(),
                json(),
                alias({
                    entries: [{ find: The '@'.replacement: __dirname + '/web/' }],
                }),
                image(),
                postcss({ extract: `${name}.css`.plugins: postCssPlugins 
                }),
                requireContext(),
                resolve({  
                    jsnext: true.main: true.browser: true.dedupe: ["vue"],
                }),
                vue({ 
                    css: false
                }),
                replace({ 
                    'process.env.NODE_ENV': production ? 
                        '"production"' : '"development"'.preventAssignment: true,
                }),
                esbuild({ 
                    minify: production, 
                    target: 'es2015',
                }),
                production && terser(),
                production && filesize(),
            ],
            watch: {
                clearScreen: false.exclude: ['node_modules/**'],}}; });Copy the code

The most important part of the document:

export default fs  
    .readdirSync(      
        path.join(__dirname, "web"."pages")   
    )
    .map((input) = > {
        const name = input.split(".") [0].toLowerCase();
        
    return {     
        input: `web/pages/${input}`.output: {
            file: `dist-web/${name}.js`.format: 'iife'.name: 'app',},...Copy the code

The code snippet iterates through all *.js pages in the /web/pages/ directory and compiles each page separately into a new JavaScript file in the directory, /dist-web/.

Let’s Concurrently install the NPM package by running the following command:

npm i --save-dev concurrently
Copy the code

Then, locate the package.json file and replace the monitor script with:

"watch": "concurrently \"rollup -c -w\" \"webpack --watch\"".Copy the code

Now, the monitor script compiles the vue.js page and extension files every time you change any files in either folder.

Run this command to compile both applications and generate the /dist-web/ directory:

npm run watch
Copy the code

You can now see the four new files created in the /dist-web/ directory:

  • App.js
  • Application. CSS
  • The sidebar. Js
  • The sidebar. CSS

Each page generates two files, especially JavaScript and CSS files.

That’s it! The vue.js page is ready to be hosted in the Web view.

Step 5

We start with webapppanel. ts copying files from extension projects that use Vue CLI. Then change the resource file to include /dist-web/app.js and /dist-web/app.css.

Listing 6 shows the entire source code for the file after the changes.

Listing 6: Webapppanel.ts loads a single vue.js root component

import * as vscode from "vscode";
import { getNonce } from "./getNonce";

export class WebAppPanel {
    public static currentPanel: WebAppPanel | undefined;

    public static readonly viewType = "vscodevuerollup:panel";

    private readonly _panel: vscode.WebviewPanel; 
    private readonly _extensionUri: vscode.Uri;   
    private _disposables: vscode.Disposable[] = [];

    public static createOrShow(extensionUri: vscode.Uri) {      
        const column = vscode.window.activeTextEditor?
        vscode.window.activeTextEditor.viewColumn: undefined;

        // If we already have a panel, show it.
        if (WebAppPanel.currentPanel) {
            WebAppPanel.currentPanel._panel.reveal(column);
            return;      
        }

        // Otherwise, create a new panel.  
        const panel = vscode.window.createWebviewPanel(
            WebAppPanel.viewType,
            'Web App Panel',
            column || vscode.ViewColumn.One,
            getWebviewOptions(extensionUri),      
        );

        WebAppPanel.currentPanel = new WebAppPanel(panel, extensionUri);    
    }

    public static kill(){ WebAppPanel.currentPanel? .dispose(); WebAppPanel.currentPanel =undefined; 
    }

    public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
            WebAppPanel.currentPanel = new WebAppPanel(panel, extensionUri);  
        }
        
    private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
            this._panel = panel;
            this._extensionUri = extensionUri;

            // Set the webview's initial html content
            this._update();
            this._panel.onDidDispose(() = > this.dispose(), 
                null.this._disposables);

            // Update the content based on view changes 
            this._panel.onDidChangeViewState(  
                e= > {
                    if (this._panel.visible) {
                        this._update();}
                },
                null.this._disposables    
            );
            
            // Handle messages from the webview
            this._panel.webview.onDidReceiveMessage(    
                message= > {
                    switch (message.command) {
                        case 'alert': vscode.window.showErrorMessage(message.text);  
                        return; }},null.this._disposables
            );
        }

        public dispose() {    
            WebAppPanel.currentPanel = undefined; 
            
            // Clean up our resources 
            this._panel.dispose();

            while (this._disposables.length) {      
                const x = this._disposables.pop();
                if (x) {
                    x.dispose();    
                }
            }
        }

        private async _update() { 
            const webview = this._panel.webview;     
            this._panel.webview.html = this._getHtmlForWebview(webview);  
        }

        private _getHtmlForWebview(webview: vscode.Webview) {
            const styleResetUri = webview.asWebviewUri(
                vscode.Uri.joinPath(this._extensionUri, "media"."reset.css"));const styleVSCodeUri = webview.asWebviewUri(      
                vscode.Uri.joinPath(
                    this._extensionUri, "media"."vscode.css"));const scriptUri = webview.asWebviewUri(
                vscode.Uri.joinPath(
                    this._extensionUri, "dist-web"."app.js"));const styleMainUri = webview.asWebviewUri(
                vscode.Uri.joinPath(
                    this._extensionUri, "dist-web"."app.css"));const nonce = getNonce();
            
            return ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="${styleResetUri}" rel="stylesheet">
                    <link href="${styleVSCodeUri}" rel="stylesheet">
                    <link href="${styleMainUri}" rel="stylesheet">
                    <title>Web Pages Panel</title>  
                </head> 
                <body>
                    <div id="app"></div>
                    <script src="${scriptUri}" nonce="${nonce}">
                </body>
                </html> 
            `; }}function getWebviewOptions(extensionUri: vscode.Uri) :vscode.WebviewOptions {
    return {
        // Enable javascript in the webview
        enableScripts: true.localResourceRoots: [
            vscode.Uri.joinPath(extensionUri, 'media'), 
            vscode.Uri.joinPath(extensionUri, 'dist-web')]}; }Copy the code

Add a new/SRC/sideBarprovider.ts file and paste the contents of Listing 7 into it.

Listing 7: SideBarprovider.ts

import * as vscode from "vscode";
import { getNonce } from "./getNonce";

export class SidebarProvider implements vscode.WebviewViewProvider {
    public static readonly viewType = 'vscodevuerollup:sidebar'; private _view? : vscode.WebviewView;constructor(private readonly _extensionUri: vscode.Uri) {}

    public resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken) {
        this._view = webviewView;

    webviewView.webview.options = {      
        // Allow scripts in the webview
        enableScripts: true.localResourceRoots: [
            this._extensionUri
        ],
    };

    webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);

    webviewView.webview.onDidReceiveMessage(async (data) => {     
        switch (data.type) {
            case "message": {
                if(! data.value) {return;
                }
                vscode.window.showInformationMessage(data.value);  
                break;
            }
            case "openApp": {  
                await vscode.commands.executeCommand(
                    'vscodevuerollup:openVueApp', { ...data }
                );
                break;
            }
            case "onInfo": {
                if(! data.value) {return; 
                }
                vscode.window.showInformationMessage(data.value);  
                break;
            }
            case "onError": {
                if(! data.value) {return; 
                } 
                vscode.window.showErrorMessage(data.value); 
                break; }}}); } publicrevive(panel: vscode.WebviewView) {
        this._view = panel; 
    }

    public sendMessage() {
        return vscode.window.showInputBox({
            prompt: 'Enter your message'.placeHolder: 'Hey Sidebar! '
        }).then(value= > {      
            if (value) {
                this.postWebviewMessage({  
                    command: 'message'.data: value,}); }}); } privatepostWebviewMessage(msg: { command: string, data? : any }) {
    vscode.commands.executeCommand(
                    'workbench.view.extension.vscodevuerollup-sidebar-view');  
    vscode.commands.executeCommand('workbench.action.focusSideBar');
    
    this._view? .webview.postMessage(msg); } private_getHtmlForWebview(webview: vscode.Webview) 
    { 
        const styleResetUri = webview.asWebviewUri(
            vscode.Uri.joinPath(
                this._extensionUri, "media"."reset.css"));const styleVSCodeUri = webview.asWebviewUri(      
            vscode.Uri.joinPath(
                this._extensionUri, "media"."vscode.css"));const scriptUri = webview.asWebviewUri(      
            vscode.Uri.joinPath(
                this._extensionUri, "dist-web"."sidebar.js"));const styleMainUri = webview.asWebviewUri( 
            vscode.Uri.joinPath(
                this._extensionUri, "dist-web"."sidebar.css"));const nonce = getNonce();

    return ` <! DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="${styleResetUri}" rel="stylesheet">
                <link href="${styleVSCodeUri}" rel="stylesheet">
                <link href="${styleMainUri}" rel="stylesheet">  
                <title>Web Pages Panel</title>
                <script nonce="${nonce}">    
                    const vscode = acquireVsCodeApi();
                </script>
        </head>     
        <body>
            <div id="app"></div>
            <script src="${scriptUri}" nonce="${nonce}">
        </body>
        </html>   
    `; }}Copy the code

The SidebarProvider implements the WebviewViewProvider interface. It wraps an instance of WebviewView, which in turn wraps a Webview containing the actual HTML content.

The resolveWebviewView() function is at the heart of this provider. VS Code uses it to Webview the Sidebar. It is in this function that you can set Webviewfor VS Code’s HTML content to display it in Sidebar. The provider loads resource files /dist-web/sidebar. Js and /dist-web/sidebar. CssHTML inside.

HTMLWebview now contains the following code:

<script>       
    const vscode = acquireVsCodeApi();
</script>
Copy the code

This vscode object will be the bridge through which vue.js applications can be used to publish messages to extensions.

! Let’s press F5 to run the extension. A new instance of VS Code opens.

Locate and click the last icon added on the activity bar. Figure 11 shows how the Sidebar. Vue component is loaded into the Sidebar section.

Figure 11: Sidebar. Vue component in Sidebar

Step 6

When the user clicks the Open button on the sidebar, let’s load the app.vue component in the editor.

Go to/web/components/Sidebar. Vue file and the button is bound to the event handler:

<button @click="openApp">Open</button>
Copy the code

Then, define the openApp() function as follows:

openApp() {
    vscode.postMessage({
        type: 'openApp'}); },Copy the code

The code uses the vscode.postMessage() function to publish messages to the extender by passing a message payload. In this case, the payload only specifies the type of message.

Switch to the sideBarProvider.ts file and listen inside the resolveWebviewView() function for the message type you just defined. You can listen to messages published within the function onDidReceiveMessage() as follows:

webviewView.webview.onDidReceiveMessage(
    async (data) => {
        switch (data.type) {
            case "openApp": {
                await vscode.commands.executeCommand(
                        'vscodevuerollup:openVueApp',
                        { ...data }
                    );
                break;
            }
            // more}});Copy the code

When the user clicks on the “open” button on the sidebar, the provider will by executing the command vscodevuerollup: openVueApp and pass the payload (if required) to respond.

! Let’s press F5 to run the extension. A new instance of VS Code opens.

Click the last icon added on the activity bar. Then click the Open button. Figure 12 shows the app.vue component loaded in the editor’s Web view. The Sidebar. Vue component is loaded in the Web view of the Sidebar.

Figure 12: Sidebar. Vue and app. vue components in the VS Code extension

The Webview API allows for two-way communication between extensions and HTML content.

Step 7

Let’s add a command that allows the extension to publish messages to the Sidebar. Vue component from VS Code.

First defined in the file vscodevuerollup: sendMessage command package. The json, as shown below:

{   
    "command": "vscodevuerollup:sendMessage"."title": "Send message to sidebar panel"."category": "Vue Rollup"
}
Copy the code

Then, register this command in the extension.ts file:

context.subscriptions.push(
    vscode.commands.registerCommand(
        'vscodevuerollup:sendMessage'.async() = > {if (sidebarProvider) { 
                awaitsidebarProvider.sendMessage(); }}));Copy the code

When the user triggers sendMessage, the command handler calls SidebarProvider, an instance function on the class sendMessage().

Listing 8 shows the sendMessage() function. It through the built-in vscode. Window. ShowInputBox () function to prompt the user input message. The user input message is then published to the Sidebar. Vue component using the webView.postMessage () built-in function.

Listing 8: The sendMessage() function

public sendMessage() { 
    return vscode.window.showInputBox({
        prompt: 'Enter your message'.placeHolder: 'Hey Sidebar! '}
    ).then(value= > {   
        if (value) {
            this._view? .webview.postMessage({command: 'message'.data: value, }); }}); }Copy the code

The Sidebar. Vue component handles messages received from the extension by registering an event listener, as shown below:

mounted() {
    window.addEventListener(
        'message'.this.receiveMessage
    );
},
Copy the code

ReceiveMessage () This function is run when the user triggers a command in VS Code.

You can receiveMessage() define functions as follows:

receiveMessage(event) {
    if(! event)return;

    const envelope = event.data;
    switch (envelope.command) { 
        case 'message': {  
            this.message = envelope.data;  
            break; }}; },Copy the code

It verifies that the command is of message type **. ** It then extracts the command’s payload and assigns it to local variables that the component displays on the UI.

Let’s run the extension!

Find and navigate to the Sidebar. Vue component hosted within Sidebar.

Open the command panel and start typing “Send message to sidebar Panel” **. **VS Code prompts you for a message, as shown in Figure 13. Enter any message you choose, and then press Enter.

Figure 13: Facilitating user input

The message will appear on the sidebar, as shown in Figure 14 **. **

Figure 14: Sidebar. Vue component receives messages from the extension.

A: congratulations! So far, you have completed the third VS Code extension.