使用 Vue.js 构建 VS Code 扩展

使用 Vue.js 构建 VS Code 扩展

在这里插入图片描述

Visual Studio (VS) Code 是开发人员在日常任务中最喜欢使用的代码编辑器之一。它的构建考虑到了可扩展性。在某种程度上,VS Code 的大部分核心功能都是作为扩展构建的。您可以查看 VS Code 扩展存储库 ( github.com/microsoft/v… ) 以了解我在说什么。

VS Code 在底层是一个电子 ( www.electronjs.org/ ) 跨环境应用程序,可以在 UNIX、Mac OSX 和 Windows 操作系统上运行。因为它是一个电子应用程序,你可以通过编写 JavaScript 插件来扩展它。事实上,任何可以转换为 JavaScript 的语言都可以用来构建扩展。例如,VS Code 文档网站提示使用 TypeScript ( www.typescriptlang.org/ ) 来编写 VS Code 扩展。VS Code 团队提供的所有代码示例 ( github.com/microsoft/v… ) 都是使用 TypeScript 构建的。

VS Code 支持非常广泛的 API,您可以在 VS Code API ( code.visualstudio.com/api )上查看和阅读。

VS Code 允许您扩展它支持的几乎所有功能。您可以构建自定义命令、创建新的颜色主题、在 WebView 中嵌入自定义 HTML、通过添加新视图为活动栏做出贡献、利用树视图在侧边栏上显示分层数据以及许多其他可扩展性选项。扩展功能概述页面 ( code.visualstudio.com/api/extensi… ) 详细介绍了所有 VS Code 扩展功能。如果您想跳过概述并直接转到有关如何构建具有功能的实际扩展的详细信息,请查看扩展指南页面 ( code.visualstudio.com/api/extensi… )。

在 VS Code 中构建扩展是一个巨大的话题,可以在许多书籍和无数文章中详细介绍。在这篇文章中,我将重点关注:

  • 创建 VS Code 命令
  • 使用 Webview API 在 Webview 面板和视图中嵌入 Vue.js 应用程序
  • 将视图容器添加到活动栏

VS Code UI 架构

在我深入研究构建扩展之前,了解构成 VS Code UI 的部分和部分很重要。

我将从 VS Code 网站借用两张图表来帮助说明这些概念。图 1说明了 VS Code UI 的主要部分。

在这里插入图片描述

图 1: VS Code 主要部分

VS Code 有以下主要部分:

  • 活动栏:活动栏上的每个图标都代表一个视图容器。反过来,这个容器在里面承载一个或多个视图。此外,您也可以扩展现有的。例如,您可以将新视图添加到资源管理器视图中。
  • 侧边栏:侧边栏是托管视图的容器。例如,您可以向侧边栏添加树视图或 Web 视图视图。
  • 编辑器:编辑器托管 VS Code 使用的不同类型的编辑器。例如,VS Code 使用文本编辑器来允许您读/写文件。另一种编辑器允许您编辑工作区和用户设置。例如,您还可以使用 Webview 贡献您自己的编辑器。
  • 面板:面板允许您添加带有视图的视图容器。
  • 状态栏:状态栏承载可以使用文本和图标显示的状态栏项目。您还可以将它们视为单击它们时触发操作的命令。

图 2说明了 VS Code UI 的主要部分的内容。

在这里插入图片描述

图 2: VS Code 部分详细信息

  • Activity Bar 承载 View Containers,而 View Containers 又承载着 Views。
  • 视图具有视图工具栏。
  • 侧边栏有一个侧边栏工具栏。
  • 编辑器有一个编辑器工具栏。
  • 面板托管视图容器,而后者又托管视图。
  • 面板有一个面板工具栏。

VS Code 允许我们使用其 API 扩展任何主要和次要部分。

VS Code API 足够丰富,允许开发人员扩展它提供的几乎所有功能。

你的第一个 VS Code 扩展

要开始构建您自己的自定义 VS Code 扩展,请确保您的计算机上安装了Node.js ( nodejs.org/en/ ) 和 Git ( git-scm.com/ )。不用说,您的计算机上也需要安装 VS Code(code.visualstudio.com/download)。

我将使用 Yeoman ( yeoman.io/ ) CLI 生成一个新的 VS Code 扩展项目。Microsoft 提供并支持 Yo Code ( www.npmjs.com/package/gen… ) Yeoman 生成器,以在 TypeScript 或 JavaScript 中构建完整的 VS Code 扩展。

开始吧!

步骤1

通过运行以下命令安装 Yeoman CLI 和 Yo 代码生成器:

npm install -g yo generator-code
复制代码

第2步

运行以下命令以搭建准备开发的 TypeScript 或 JavaScript 项目。

yo code
复制代码

在创建新的 VS Code 扩展项目的过程中,代码生成器会询问一些问题。我将通过它们来创建应用程序。

图 3显示了生成器的起点。

在这里插入图片描述

图 3启动扩展项目脚手架

您可以选择 TypeScript 或 JavaScript。您在网上找到的大多数示例都是用 TypeScript 编写的。在编写和创作扩展程序时,使用 TypeScript 使您的生活更轻松,这将是明智之举。

接下来,您需要提供扩展的名称,如图 4所示。

在这里插入图片描述

图 4命名 VS Code 扩展

现在,您指定扩展的标识符 (ID)。您可以保留默认设置或提供您自己的设置。我倾向于不使用空格或破折号 (-) 来分隔标识符名称。

然后,您可以提供扩展程序的描述。

接下来的三个问题如图 5所示。

  • 初始化 Git 存储库?是的
  • 使用 Webpack 捆绑扩展?是的
  • 使用哪个包管理器?新产品经理

在这里插入图片描述

图 5完成代码生成器脚手架

生成器获取您所有的答案并为您的应用程序搭建支架。完成后,进入新的扩展文件夹并通过运行以下命令打开 VS Code:

cd vscodeexample && code .
复制代码

第 3 步

让我们快速探索扩展项目。

图 6列出了 Yo Code 为您生成的所有文件。

在这里插入图片描述

图 6项目文件

  • /.vscode/目录包含帮助我们轻松测试扩展的配置文件。
  • /dist/目录包含扩展的编译版本。
  • /src/目录包含您为构建扩展而编写的源代码。
  • package.json文件是默认的 NPM 配置文件。您可以使用此文件来定义您的自定义命令、视图、菜单等等。
  • vsc-extension-quickstart.md文件包含对扩展项目的介绍以及有关如何开始构建 VS Code 扩展的文档。

Microsoft 提供了 Yo Code Yeoman 生成器来帮助您快速轻松地构建 VS Code 扩展项目。

步骤4

打开package.json文件,让我们探索构建此扩展所需的重要部分。

"contributes": {
    "commands": [
        {
            "command": "vscodeexample.helloWorld",   
            "title": "Hello World"
        }
    ]
},
复制代码

您可以在贡献部分中定义自定义命令。定义新命令时,您至少要提供命令和标题。该命令应唯一标识您的命令。默认情况下,使用的命令是您在搭建扩展时指定的扩展标识符与代表您提供的命令的任意字符串的串联。新命令现在会自动显示在命令面板中。

VS Code 定义了许多内置命令,您甚至可以以编程方式使用它们。例如,您可以执行workbench.action.newWindow命令来打开一个新的 VS Code 实例。

这是VS Code中内置命令的完整列表 ( code.visualstudio.com/api/referen… )。

该命令暂时不执行任何操作。您仍然需要将此命令绑定到我将很快定义的命令处理程序。VS Code 提供了registerCommand()为你做关联的功能。

您应该定义一个激活事件,当用户触发命令时将激活扩展。是激活事件让 VS Code 定位命令并将其绑定到命令处理程序。请记住,默认情况下并不总是激活扩展。例如,当您打开具有特定文件扩展名的文件时,可能会激活扩展名。这就是为什么需要确保在运行任何命令之前激活扩展。

package.json文件定义了 activationEvents 部分:

"activationEvents": [ "onCommand:vscodeexample.helloWorld" ],
复制代码

当用户从命令面板或通过键绑定调用命令时,扩展将被激活并且registerCommand()函数将绑定(vscodeexample.helloWorld)到正确的命令处理程序。

第 5 步

现在是探索扩展源代码并将命令与命令处理程序一起注册的时候了。扩展源代码位于/src/extension.ts文件中。我已经清理了这个文件如下:

import * as vscode from 'vscode';

export function activate(
    context: vscode.ExtensionContext) {
        context.subscriptions.push(...);
    }

export function deactivate() {}
复制代码

VS Codeactivate()在想要激活扩展时调用该函数。同样,当它调用该deactivate()函数时,它想停用它。请记住,扩展仅在您声明的激活事件之一发生时激活。

如果您在命令处理程序中实例化对象并希望 VS Code 稍后为您释放它们,请将新的命令注册推送到context.subscriptions数组中。VS Code 维护这个数组并将代表你进行垃圾收集。

让我们注册Hello World命令如下:

context.subscriptions.push(
    vscode.commands.registerCommand(
       'vscodeexample.helloWorld',
       () => {
           vscode.window.showInformationMessage('...');
        }
    )
);
复制代码

vscode对象是访问整个 VS Code API 的关键。注册命令处理程序的方式与在 JavaScript 中注册 DOM 事件的方式类似。该代码将相同的命令标识符(您之前在package.json文件中的 commands 和 activationEvents 部分下声明的那个标识符)绑定到命令处理程序。

当用户触发命令时,VS Code 会显示一条信息消息。

让我们通过单击 来测试扩展F5。VS Code 打开一个加载了新扩展的新实例。要触发命令,请打开命令面板并开始输入“hello”。

图 7显示了 VS Code 如何将可用命令过滤为您想要的命令。

在这里插入图片描述

图 7过滤的命令面板

现在单击该命令,图 8显示了信息消息如何出现在编辑器的右下方。

在这里插入图片描述

图 8显示信息消息

恭喜!你刚刚完成了你的第一个 VS Code 扩展!

使用 Vue CLI 使用 Vue.js 构建 VS Code 扩展

让我们用你新学到的知识来创造更有趣的东西吧!

在本节中,您将使用 Vue CLI 创建一个新的 Vue.js 应用程序并将其作为单独的编辑器托管在 Webview 中。

Webview API 允许您在 VS Code 中创建完全可自定义的视图。我喜欢将 Webview 视为 VS Code 中的 iframe。它可以在此框架内呈现任何 HTML 内容。它还支持扩展和加载的 HTML 页面之间的双向通信。视图可以向扩展发布消息,反之亦然。

Webview API 支持我将在本文中探讨的两种类型的视图:

  • WebviewPanel 是 Webview 的包装器。它用于在 VS Code 的编辑器中显示 Webview。
  • WebviewView 是 Webview 的包装器。它用于在侧边栏中显示 Web 视图。

在这两种类型中,Webview 都承载 HTML 内容!

Webview API 文档内容丰富,包含使用它所需的所有详细信息。在 Webview API ( code.visualstudio.com/api/extensi… ) 上查看。

让我们开始构建 Vue.js 示例应用程序并将其托管在 VS Code 的编辑器中。

Webview 允许您通过将 HTML 内容与 JavaScript 和 CSS 资源文件一起嵌入来丰富您的 VS Code 扩展。

步骤1

使用 Yeoman 生成一个新的 VS Code 扩展项目,如上所述。

第2步

添加一个新命令来打开 Vue.js 应用程序。找到该package.json文件并添加以下内容:

"contributes": { 
    "commands": [ 
        {
            "command": "vscodevuecli:openVueApp", 
            "title": "Open Vue App"
        }
    ]
},
复制代码

该命令的标识符为vscodevuecli:openVueApp

然后你声明一个激活事件如下:

"activationEvents": ["onCommand:vscodevuecli:openVueApp"],
复制代码

第 3 步

切换到extension.js文件并在activate()函数内部注册命令处理程序。

context.subscriptions.push(   
    vscode.commands.registerCommand(
        'vscodevuecli:openVueApp', () => 
            {
                WebAppPanel.createOrShow(context.extensionUri);
            }
    )
);
复制代码

在命令处理程序中,您正在实例化一个新的WebAppPanel类实例。它只是一个 WebviewPanel 的包装器。

步骤4

在这一步中,您将使用 Vue CLI 生成一个新的 Vue.js 应用程序。按照本指南 ( cli.vuejs.org/guide/creat… )/web/在扩展项目的根目录中搭建一个新的 Vue.js 应用程序。

确保将您使用的任何图像放在/web/img/目录中。稍后,您将dist在编译应用程序时将此目录复制到该目录中。

通常,托管 Vue.js 应用程序的 HTML 页面会请求从服务器上的本地文件系统渲染图像。但是,当 Webview 加载应用程序时,它不能只是请求和访问本地文件系统。出于安全原因,Webview 应该仅限于项目本身内的几个目录。

此外,VS Code 使用特殊的 URI 来加载 Webview 中的任何资源,包括 JavaScript、CSS 和图像文件。因此,您需要一种基于所有图像的方法,因此您使用 VS Code 用于访问本地资源的 URI。正如您将在第 5 步中看到的,该扩展将 VS Code 基 URI 注入到 Webview 的 HTML 正文中,以便 Vue.js 应用程序可以使用它来构建其图像。

因此,要使用注入的基本 URI,您将添加一个 Vue.js mixin,它从 HTML DOM 读取基本 URI 的值,并使其可用于 Vue.js 应用程序。

请注意,如果您想在 Webview 之外运行 Vue.js 应用程序,则需要将以下内容放入/web/public/index.html文件中:

<body>   
    <input hidden data-uri="">
    ...
</body>
复制代码

/web/src/mixins/ExtractBaseUri.js文件中,定义一个新的 Vue.js mixin。它使baseUri任何 Vue.js 组件都可以使用data 选项:

data() {
    return {
        baseUri: '',   
    };
},
复制代码

然后它使用 Vue.jsmounted()生命周期钩子来提取值:

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

    this.baseUri = dataUri.getAttribute('data-uri');
},
复制代码

如果它找到具有名为 data-uri 的数据属性的输入字段,它会读取该值并将其分配给该baseUri属性。

下一步是在/web/src/main.js文件中提供mixin :

Vue.mixin(ExtractBaseUri);
复制代码

切换到 App.vue 组件并将图像元素替换为以下内容:

<img alt="Vue logo" :src="`${baseUri}/img/logo.png`">
复制代码

现在应用程序已准备好在本地和 Webview 内运行,让我们通过 Vue.js 配置文件自定义编译过程。

创建一个新文件。清单 1显示了该文件的完整源代码。/web/vue.config.js

清单 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]    
            })
    },
}
复制代码

基本上,您正在执行以下操作:

  • 从编译的文件名中删除哈希。编译后的 JavaScript 文件看起来像 app.js,只是文件名中没有任何哈希值。
  • 将输出目录设置为/dist-web/. Vue CLI 使用此属性来决定放置已编译应用程序文件的位置。
  • /web/img/目录及其所有内容复制到目标目录。

接下来,让我们修复 NPM 脚本,以便您可以使用单个脚本同时编译扩展文件和 Vue.js 应用程序。

首先,Concurrently通过运行以下命令安装NPM 包:

npm i --save-dev concurrently
复制代码

然后,找到该package.json文件并将监视脚本替换为:

"watch": "concurrently \"npm --prefix web run serve\" \"webpack --watch\"",
复制代码

现在,每次更改两个文件夹中的任何文件时,监视脚本都会编译 Vue.js 应用程序和扩展文件。

运行以下命令编译两个应用程序并生成/dist-web/目录:

npm run watch
复制代码

就这样吧!Vue.js 应用程序已准备好托管在 Web 视图中。

第 5 步

/src/目录中添加一个新的 TypeScript 文件并将其命名为WebAppPanel.ts. 清单 2包含该文件的完整源代码。让我们剖析它并解释它最相关的部分。

清单 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'),
        ]
    };
}
复制代码

您将WebAppPanel类定义为单例以确保它始终只有一个实例。这是通过添加以下内容来完成的:

public static currentPanel: WebAppPanel | undefined;
复制代码

它包装了 WebviewPanel 的一个实例并通过定义以下内容来跟踪它:

private readonly _panel: vscode.WebviewPanel;
复制代码

createOrShow()功能的核心WebAppPanel类。它检查是否currentPanel已经实例化,并立即显示WebviewPanel

if (WebAppPanel.currentPanel) {   
    WebAppPanel.currentPanel._panel.reveal(column);
    return;
}
复制代码

否则,它WebviewPanel使用该createWebviewPanel()函数实例化一个新的,如下所示:

const panel = vscode.window.createWebviewPanel(   
    WebAppPanel.viewType,
    'Web App Panel',    
    column || vscode.ViewColumn.One,    
    getWebviewOptions(extensionUri),
);
复制代码

该函数接受以下参数:

  • viewType:指定视图类型的唯一标识符WebviewPanel
  • 标题:标题WebviewPanel
  • showOptions:在Webview编辑器中的显示位置
  • 选项:新的设置Panel

选项是在getWebviewOptions()函数内部准备好的。

function getWebviewOptions(   
    extensionUri: vscode.Uri
): vscode.WebviewOptions {
    return {    
        enableScripts: true,
        localResourceRoots: [
            vscode.Uri.joinPath(extensionUri, 'media'),
            vscode.Uri.joinPath(extensionUri, 'dist-web'),
        ]
    };
}
复制代码

它返回一个具有两个属性的对象:

  • enableScripts:控制是否在 Webview 内容中启用脚本
  • localResourceRoots:指定 Webview 可以使用 URI(表示磁盘上的文件或任何其他资源的通用资源标识符)加载本地资源的根路径。这保证了扩展不能访问您指定的路径之外的文件。

WebviewPanel 包装了一个 Webview 以在 VS 代码编辑器中呈现。

createOrShow()函数通过调用其私有构造函数将 的值设置currentPanel为 的新实例而结束WebAppPanel

构造函数最重要的部分是设置Webview的HTML内容如下:

this._panel.webview.html = this._getHtmlForWebview(webview);
复制代码

_getHtmlForWebview()函数准备并返回 HTML 内容。

您将在几乎每个创建的 Web 视图中嵌入两个 CSS 文件。该reset.css文件会重置 Web 视图中的一些 CSS 属性。尽管该vscode.css文件包含 VS Code 的默认主题颜色和 CSS 属性。这对于使您的 Webview 具有与 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"));
复制代码

_extensionUri属性表示包含当前扩展的目录的 URI。WebviewasWebviewUri()函数将本地文件系统的 URI 转换为可在 Webviews 中使用的 URI。它们不能使用file: URI从工作区或本地文件系统直接加载资源。该asWebviewUri()函数采用本地文件:URI 并将其转换为可在 Webview 中使用以加载相同资源的 URI。

然后,该函数为其他资源准备 URI,包括在步骤 5 中由 Vue CLI 编译的js/app.jsjs/chunk-vendors.js文件。

记住在步骤 4 中,Vue CLI 复制/dist-web/img/目录中的所有图像。Vue.js 应用程序中的所有图像路径都使用一个基本 URI,该 URI 在 Webview 中运行时指向 VS Code URI,或者在独立模式下运行时指向 file: URI。

在此阶段,您需要生成一个 VS Code 基础 URI 并将其注入到 Vue.js 通过 Vue.js mixin 加载和读取的隐藏输入字段中。

WebAppPanel 使用以下代码生成扩展的 VS Code 基 URI:

const baseUri = 
    webview.asWebviewUri(
        vscode.Uri.joinPath(
            this._extensionUri, 'dist-web'
        )
    ).toString().replace('%22', '');
复制代码

它通过在同时加载 Vue.js 应用程序的 HTML 页面内的隐藏输入字段上设置 data-uri 数据属性值,将此 URI 传达给 Vue.js 应用程序。

最后,该函数将所有 CSS 和 JavaScript URI 嵌入 HTML 页面内容并返回它。

而已!

让我们通过单击F5键来运行扩展,并开始在刚刚打开的 VS Code 实例的命令面板中键入“Open Vue App”,如图 9所示。

在这里插入图片描述

图 9打开 Vue App 命令

单击该命令以在新编辑器窗口的 Web 视图中加载 Vue.js 应用程序,如图 10所示。

在这里插入图片描述

图 10:在 VS Code 扩展中加载 Vue 应用程序

这就是在 VS Code 扩展中加载由 Vue CLI 生成的 Vue.js 应用程序所需的全部内容。

使用 Rollup.js 使用 Vue.js 构建 VS Code 扩展

在本节中,我将扩展您迄今为止所构建的内容,并介绍一个新场景,其中 Vue CLI 可能不是该工作的正确工具。

如您所知,Vue CLI 将整个 Vue.js 应用程序编译为一个app.js文件。让我们暂时搁置 CLI 提供的分块功能。

在构建 VS Code 扩展时,有时您需要在编辑器的 WebviewPanel 中加载一个 HTML 页面。同时,您可能需要WebviewView在侧边栏中的a 内加载另一个 HTML 页面。当然,您可以使用纯 HTML 和 JavaScript 来构建您的 HTML,但是因为您想使用 Vue.js 来构建您的 HTML 页面,所以在这种情况下 Vue CLI 不是一个选项。

您需要创建一个 Vue.js 应用程序,其中包含多个独立的小型 Vue.js 组件,这些组件被单独编译成单独的 JavaScript 文件,而不仅仅是合并到一个app.js文件中。

我想出了一个解决方案,该解决方案涉及使用至少两个文件创建微型 Vue.js 应用程序。一个 JavaScript 文件和一个或多个 Vue.js 组件(具有许多子组件的根组件)。JavaScript 文件导入 Vue.js 框架并将相应的 Vue.js 根组件挂载到 HTML 页面内的 DOM 中。

对于这个解决方案,我决定使用 Rollup.js ( rollupjs.org/ ) 来编译文件。

让我们通过构建一个新的 VS Code 扩展来一起探索这个解决方案,它可以做两件事:

  • 使用 WebviewPanel 将 Vue.js 应用程序(或根组件)托管到新编辑器中
  • 使用 WebviewView 将 Vue.js 应用程序(或根组件)托管到侧边栏中

步骤1

像之前一样,使用 Yeoman 生成一个新的 VS Code 扩展项目。

第2步

添加一个新命令来打开 Vue.js 应用程序。找到该package.json文件并添加以下内容:

"contributes": {
    "commands": [ 
        { 
            "command": "vscodevuerollup:openVueApp", 
            "title": "Open Vue App", 
            "category": "Vue Rollup"    
        }
    ]
},
复制代码

该命令的标识符为vscodevuerollup:openVueApp

然后你声明一个激活事件:

"activationEvents": ["onCommand:vscodevuerollup:openVueApp"],
复制代码

此外,定义一个新的 View Container 以在 Activity Bar 内加载。清单 3显示了您需要在package.json文件中添加的部分。

清单 3:添加一个视图容器

    "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"  
            }
        ]
    },
复制代码

活动栏条目的 ID 为vscodevuerollup-sidebar-view。此 ID 与将托管在此视图容器内且在视图部分中定义的视图集合的 ID 相匹配。

"views": {"vscodevuerollup-sidebar-view": [...]}
复制代码

( vscodevuerollup-sidebar-view) 条目表示视图的集合。每个视图都有一个 ID。

{   
    "type": "webview",  
    "id": "vscodevuerollup:sidebar",   
    "name": "vue with rollup", 
    "icon": "$(remote-explorer)",  
    "contextualTitle": "vue app"
}
复制代码

记下这个 ID vscodevuerollup:sidebar,向上滚动到 activatinEvents 部分,并添加以下条目:

"onView:vscodevuerollup:sidebar"
复制代码

使用onView声明时,VS Code 会在具有指定 ID 的视图在侧边栏上展开时激活扩展。

第 3 步

切换到extension.js文件并在activate()函数内部注册命令处理程序。

首先,注册vscodevuerollup:openVueApp命令:

context.subscriptions.push(
    vscode.commands.registerCommand(
        'vscodevuerollup:openVueApp', async (args) => {
            WebAppPanel.createOrShow(context.extensionUri); 
        }
    )
);
复制代码

然后注册vscodevuerollup:sendMessage命令:

const sidebarProvider = new SidebarProvider(context.extensionUri);

context.subscriptions.push(
    vscode.window.registerWebviewViewProvider(
        SidebarProvider.viewType,
        sidebarProvider
    )
);
复制代码

您正在实例化该类的一个新实例SidebarProvider并使用该vscode.window.registerWebviewViewProvider()函数来注册此提供程序。

在这里,您正在处理我之前提到的第二种类型的 Web 视图,WebviewView. 要将 Webview 加载到侧边栏中,您需要创建一个实现该WebviewViewProvider接口的类。它只是一个WebviewView.

WebviewViewProvider 包装了一个WebviewView,而后者又包装了一个 Webview。WebviewViewVS Code 中侧边栏内的渲染。

步骤4

在这一步中,您将创建一个自定义 Vue.js 应用程序。首先在扩展的根文件夹中创建 /web/ 目录。

在此目录中,创建三个不同的子目录:

  • pages:此目录包含所有 Vue.js 页面。
  • 组件:这包含所有 Vue.js 单文件组件 (SFC)。
  • img:这包含您在 Vue.js 组件中使用的所有图像。

让我们通过创建/web/pages/App.js文件并将以下代码粘贴到其中来添加第一个 Vue.js 页面:

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

new Vue({render: h => h(App)}).$mount("#app");
复制代码

这里没有魔法!它与 Vue CLI 在main.js文件中用于在 HTML DOM 上加载和挂载 Vue.js 应用程序的代码相同。但是,在这种情况下,我只是安装了一个 Vue.js 组件。将此组件视为可能在树层次结构中使用其他 Vue.js 组件的根组件。

请注意,我App.vueCLI您之前创建的 Vue文件中借用了相同的文件。

让我们通过创建/web/pages/Sidebar.js文件并将此代码粘贴到其中来添加另一个页面:

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

new Vue({render: h => h(Sidebar)}).$mount("#app");
复制代码

此页面加载并挂载 Sidebar.vue 组件。

清单 4显示了 Sidebar.vue 组件的完整内容。它定义了以下 UI 部分:

  • 显示从分机收到的消息。
  • 允许用户从 Vue.js 应用程序内向扩展程序发送消息。
  • 在扩展程序上执行命令以在编辑器内的 Web 视图中加载 App.js 页面。

清单 4:Sidebar.vue 组件

<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.2rem;
    }
    span {  
        display: inline-block;
        margin-top: 5px;  
        font-size: 1rem;
        color: orange;
    }
    hr {
        display: inline-block;  
        width: 100%;  
        margin: 10px 0;
    }
</style>
复制代码

导航到扩展根目录并添加一个新rollup.config.js文件。

清单 5显示了该文件的完整内容。

清单 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';

const production = !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: '@',
                    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/**'],     
            },
        };
    });
复制代码

该文件最重要的部分:

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',
},
...
复制代码

代码片段遍历/web/pages/目录中的所有 *.js 页面,并将每个页面分别编译为目录中的新 JavaScript 文件/dist-web/

让我们Concurrently通过运行以下命令来安装NPM 包:

npm i --save-dev concurrently
复制代码

然后,找到该package.json文件并将监视脚本替换为:

"watch": "concurrently \"rollup -c -w\" \"webpack --watch\"",
复制代码

现在,每次更改两个文件夹中的任何文件时,监视脚本都会编译 Vue.js 页面和扩展文件。

运行此命令以编译两个应用程序并生成/dist-web/目录:

npm run watch
复制代码

您现在可以看到在/dist-web/目录中创建的四个新文件:

  • 应用程序.js
  • 应用程序.css
  • 侧边栏.js
  • 侧边栏.css

每个页面都会生成两个文件,特别是 JavaScript 和 CSS 文件。

就这样吧!Vue.js 页面已准备好托管在 Web 视图中。

第 5 步

我们首先WebAppPanel.ts从使用 Vue CLI 的扩展项目中复制文件。然后将资源文件更改为包含/dist-web/app.js/dist-web/app.css.

清单 6显示了更改后该文件的整个源代码。

清单 6:WebAppPanel.ts 加载单个 Vue.js 根组件

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'),
        ]
    };
}
复制代码

添加一个新/src/SidebarProvider.ts文件并将清单 7的内容粘贴到其中。

清单 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;
            }
        }
    });
    }

    public revive(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,});      
            }
        });
    }
    private postWebviewMessage(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>   
    `;
    }
}
复制代码

SidebarProvider实现了WebviewViewProvider接口。它包装了 的一个实例WebviewView,而后者又包装了一个Webview包含实际 HTML 内容的 。

resolveWebviewView()功能位于此提供程序的核心。VS Code 使用它来WebviewSidebar. 正是在此函数中,您可以设置Webviewfor VS Code的 HTML 内容以将其显示在Sidebar. 提供程序加载资源文件/dist-web/sidebar.js/dist-web/sidebar.cssHTML 内部。

现在的 HTMLWebview包含以下代码:

<script>       
    const vscode = acquireVsCodeApi();
</script>
复制代码

vscode对象将成为 Vue.js 应用程序可用于向扩展程序发布消息的桥梁。

而已!让我们按F5键运行扩展程序。VS Code 的一个新实例打开。

找到并单击活动栏上添加的最后一个图标。图 11显示了 Sidebar.vue 组件是如何加载到 Sidebar 部分中的。

在这里插入图片描述

图 11: Sidebar 内的 Sidebar.vue 组件

第 6 步

当用户单击侧边栏上的打开按钮时,让我们在编辑器中加载 App.vue 组件。

转到/web/components/Sidebar.vue文件并将按钮绑定到事件处理程序:

<button @click="openApp">Open</button>
复制代码

然后,定义openApp()函数如下:

openApp() {
    vscode.postMessage({
        type: 'openApp',   
    });
},
复制代码

该代码使用该vscode.postMessage()函数通过传递消息有效负载将消息发布到扩展程序。在这种情况下,有效负载仅指定消息的类型。

切换到SidebarProvider.ts文件并在resolveWebviewView()函数内部监听您刚刚定义的消息类型。您可以onDidReceiveMessage()按如下方式收听函数内发布的消息:

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

当用户单击侧边栏上的“打开”按钮时,提供程序会通过执行命令vscodevuerollup:openVueApp并传递有效负载(如果需要)来做出反应。

而已!让我们按F5键运行扩展程序。VS Code 的一个新实例打开。

单击活动栏上添加的最后一个图标。然后单击打开按钮。图 12显示了在编辑器的 Web 视图中加载的 App.vue 组件。Sidebar.vue 组件加载在侧边栏的 Web 视图中。

在这里插入图片描述

图 12: VS Code 扩展中的 Sidebar.vue 和 App.vue 组件

Webview API 允许扩展和 HTML 内容之间的双向通信。

第 7 步

让我们添加一个命令,允许扩展从 VS Code 中向 Sidebar.vue 组件发布消息。

首先在文件中定义vscodevuerollup:sendMessage命令package.json,如下所示:

{   
    "command": "vscodevuerollup:sendMessage",   
    "title": "Send message to sidebar panel",  
    "category": "Vue Rollup"
}
复制代码

然后,在extension.ts文件中注册此命令:

context.subscriptions.push(
    vscode.commands.registerCommand(
        'vscodevuerollup:sendMessage', async () => {
            if (sidebarProvider) { 
                await sidebarProvider.sendMessage();
            }
        }
    )
);
复制代码

当用户触发 sendMessage 命令时,命令处理程序调用类sendMessage()上的实例函数SidebarProvider

清单 8显示了该sendMessage()函数。它通过内置vscode.window.showInputBox()函数提示用户输入消息。然后使用Webview.postMessage()内置函数将用户输入的消息发布到 Sidebar.vue 组件。

清单 8:sendMessage() 函数

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,
            });
        }
    });
}
复制代码

Sidebar.vue 组件通过注册一个事件监听器来处理从扩展接收到的消息,如下所示:

mounted() {
    window.addEventListener(
        'message', this.receiveMessage
    );
},
复制代码

receiveMessage()当用户在 VS Code 中触发命令时,该函数就会运行。

您可以receiveMessage()按如下方式定义函数:

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

    const envelope = event.data;
    switch (envelope.command) { 
        case 'message': {  
            this.message = envelope.data;  
            break;
        }
    };
},
复制代码

它验证命令是消息类型**。**然后它提取命令的有效负载并将其分配给组件在 UI 上显示的局部变量。

让我们运行扩展程序!

找到并导航到 Sidebar 内托管的 Sidebar.vue 组件。

打开命令面板,开始输入“Send message to sidebar panel” **。**VS Code 会提示您输入消息,如图 13所示。输入您选择的任何消息,然后按 Enter。

在这里插入图片描述

图 13:促进用户输入

该消息将显示在侧栏上,如图 14所示**。**

在这里插入图片描述

图 14: Sidebar.vue 组件接收来自扩展的消息。

恭喜!到目前为止,您已经完成了第三个 VS Code 扩展。

猜你喜欢

转载自juejin.im/post/7042564715171561509