VSCode插件开发经验小结

news2024/11/26 16:29:41

a4669249655244b461bb549f6074fb75.gif

从零基础接手DX扩展开发维护,到完成DX扩展从O2平台迁移到 VSCode 平台,现在也积累了一些经验,本文将对这一过程中的学习经历做一个简单小结,也希望可以通过本文帮助想要开发 VSCode 扩展的同学可以更快速的上手。

VSCode (Visual Studio Code) 是微软开发的一款免费、开源的代码编辑器。它基于 Electron 框架构建,提供了丰富的开发者工具,支持多种编程语言,可以进行代码调试、版本控制、智能提示等功能,是很多开发者日常使用的工具。

4565f8aef238bb84cc71d47455aff0cb.png

Electron

理解 vscode,我们首先要谈的是 Electron。

Electron 的核心技术主要包括以下几个方面:

  • Chromium: Electron 使用了 Chromium 浏览器作为其渲染引擎。Chromium是 Google Chrome 的开源版本,负责处理和渲染应用程序的用户界面,包括 HTML、CSS 和 JavaScript。这使得开发者可以利用Web开发技术来构建应用的界面。

  • Node.js: Electron 集成了 Node.js,使得开发者可以在应用程序的主进程(后台)中运行 JavaScript 代码。Node.js 提供了对文件系统、网络、进程等系统级 API 的访问,增强了应用程序的功能和交互性。

  • Native API: Electron 提供了一套 API,允许主进程和渲染进程之间进行通信,以及调用操作系统级别的功能。这些 API 包括 ipcRenderer 和 ipcMain(用于进程间通信)、webContents(用于控制页面内容)等。

a033569fef99f2b6aad11d07a8c00eab.jpeg

Electron 还有一个很大特点就是多进程。主要的有以下两个进程:

  • 主进程

    • Electron 中运行 package.json 中的 main 脚本的进程被称为主进程,即 main.js 就是运行在主进程。

    • 一个 electron 应用有且只有一个主进程。

    • 只有主进程可以直接进行 GUI 相关的原生 API 操作。

  • 渲染进程

    • 运行在 Chromium 的 web 页面姑且叫渲染进程,即运行 index.html 的环境就是渲染进程。

    • 一个 electron 应用可以有多个渲染进程。

    • 渲染进程在引入 Node.js 模块的前提下,可以在页面中和操作系统进行一些底层交互(如 fs 模块)。

综上来看:在 Electron 应用中,web 页面可以通过渲染进程将消息转发到主进程中,进而调用操作系统的 native api。相比普通 web 应用,可开发扩展的能力更加灵活、丰富。

了解了 vscode 的底层设计,下面我们就以真实的需求(创建模板)来一步步探索 vscode 扩展开发。

756e670d93e28e9ff4104b4cd99df73b.jpeg

需求分析

在 vscode 活动栏提供视图容器,透出创建模板入口,点击后打开可视化界面,进行简单配置后完成模板创建(注册模板信息到模板平台并生成对应的模板文件)。

要实现以上功能,需要先提炼出几个和 vscode 相关功能:

  • 通过 vscode 指令系统,注册一个命令到菜单栏。

  • 创建一个用于配置的 web 页面。

  • 完成配置后上传配置信息并创建文件。

  • 完成配置后关闭 web 页面。

逻辑实现
  注册指令

b8888a8412da5087927b2beeef74f095.jpeg

初始化一个插件项目后,暴露在最外面的文件中包含 activate 和 deactvate 两个方法,这俩方法属于 vscode 插件的生命周期,最终会被 export 出去给 vscode 主动调用。而 onXXX 等事件是声明在插件 package.json 文件中的 Activation Events。声明这些 Activation Events 后,vscode 就会在适当的时机回调插件中的 activate函数。vscode 之所以这么设计,是为了节省资源开销,只在必要的时候才激活你的插件。

// package.json
  "activationEvents": [
    "onCommand:dinamicx.createTemplate",
    ...
  ],
 "commands": [
      {
        "command": "dinamicx.createTemplate",
        "title": "DX: 创建模板"
      },
      ...
  ],
    "menus": {
      "view/title": [
        {
          "command": "dinamicx.createTemplate",
          "group": "navigation@0",
          "when": "view == dinamicx.views.main"
        }
        ...
      ]
    }

也可以在插件激活时注册命令:

import { createTemplate } from './commands/createTemplate';


export function activate(context: vscode.ExtensionContext) {
  // 注册命令
  vscode.commands.registerCommand('dinamicx.createTemplate', (info: any) => {
    createTemplate(context, info.path);
  })
    ...
}

上面这段代码的含义是将dinamicx.createTemplate这个命令和函数绑定,具体的逻辑部分应该在createTemplate这个方法中实现。

  创建WebView

如果要创建一个页面,可以使用 vscode 提供的 

api——vscode.window.createWebviewPanel:

export function createTemplate(
  context: vscode.ExtensionContext,
  dirPath: string,
) {
  const panel = vscode.window.createWebviewPanel(
    'createTemplate', // viewType
    '创建模板页面', // 视图标题
    vscode.ViewColumn.One, // 显示在编辑器的哪个部位
    // 启用JS,默认禁用 // webview被隐藏时保持状态,避免被重置
    { enableScripts: true, retainContextWhenHidden: true },
  );
  ...
  const htmlContent = this.getHtmlContent(panel.webview, htmlPath);
  panel.webview.html = htmlContent;
  panel.reveal();
  return panel;
}

具体渲染的页面可以通过 html 属性指定,但是 html 属性接收的参数是字符串!那么我们无法使用 vue/react 进行编码,只能写模板字符串了吗?

当然不是!我们可以先编写 react 代码,再打包成 js,套在 index.html 模板中 return 出来,问题就迎刃而解。处理这件事情的就是getHtmlContent

function getHtmlContent(webview, htmlPath) {
    /*
    各种资源的绝对路径
    const getHTMLDependencies = () => (`
    <!-- Dependencies -->
    <script src="${highlightJs}"></script>
    <script src="${reactJs}"></script>
    <script src="${reactDomJs}"></script>
    <script src="${antdJs}"></script>
  `);
   */
    const { getHTMLLinks, getHTMLDependencies } = useWebviewBasic(context);
    return `
  <!DOCTYPE html>
  <html>
      <head>
          <meta charset="UTF-8" />
          ${getHTMLLinks()}
      </head>
      <style>
        body {
          background-color: transparent !important;
        }
</style>
      <body>
          <div id="root"></div>
          ${getHTMLDependencies()}
          <!-- Main -->
          <script src="vscode-resource:${htmlPath}"></script>
          #{endOfBody}
      </body>
  </html>
  `;
}

vscode-resource: 出于安全考虑,Webview 默认无法直接访问本地资源,它在一个孤立的上下文中运行。它只允许通过绝对路径访问特定的本地文件。

由上面的代码可见,针对一个命令/函数,如果涉及到 webview,只关注渲染代码(即 SPA 的 js 文件),不关心具体页面实现,所以可以将编写 UI 相关的逻辑,提炼到 node 主进程之外。

  React 和 Webpack

对于 vscode 插件来讲,UI 是独立的,所以我们可以像创建 react 项目一样来完成页面部分的代码。

const Template: React.FC = () => {
  const [loading, setLoading] = useState(false);
  ...
  return (
    <Spin spinning={loading} tip={loadingText}>
      <div className="template">
           ...
      </div>
    </Spin>
  );
};


ReactDOM.render(<Template />, document.getElementById('root'));

在打包方面,刚才提到了我们要根据不同命令加载不同的页面组件,即不同的 js,所以打包的 entry 是多入口的;为了不重复引入公共库,将 react、antd 等库 external,选择通过 cdn 的方式引入。

const config = {
    mode: env.production ? 'production' : 'development',
    entry: {
      template: createPageEntry('page-template'),
      layout: createPageEntry('page-layout'),
      view: createPageEntry('view-idl'),
      ...
    },
    output: {
      filename: '[name].js',
      path: path.resolve(__dirname, '../dist/webview'),
    },
    ...
    externals: {
        'react': 'root React',
        'react-dom': 'root ReactDOM',
        'antd': 'antd',
    },
  };
  进程通信

当我们实现 Webview 后,下一步是拉取数据,然后渲染到本地项目对应的路径中,可见这一步需要操作系统 api 的支持,我们需要使用 node 进程来做这件事。

那么问题来了,UI 是通过 html 字符串传给 vscode 进程的,他们之间是如何通信的呢。

开发 vscode 扩展最核心(恶心)的事情就是通信,单向的数据流导致不仅是 webview 和插件 node 进程通信复杂,即使在同一个 react 项目中的两个不同页面(webview)也是不能直接进行数据交互的。

流程如图:

a8e205006c0730dc30206ca8aab6bee3.png

vscode 在通信这里,只为我们提供了最简单粗糙的通信方法 —— acquirevscodeApi,这个对象里面有且仅有以下几个可以和插件通信的 API。

插件发送消息:

panel.webview.postMessage; // 支持发送任意被JSON化的数据

WebView 接收消息:

window.addEventListener('message', (event) => {
    const message = event.data;
    console.log(message);
});

WebView 给插件发消息:

export const vscode = acquirevscodeApi();
vscode.postMessage('xxx');

插件接收消息:

panel.webview.onDidReceiveMessage(
    (message) => {
        console.log('插件收到的消息:', message);
    },
    undefined,
    context.subscriptions
);

c37c708909b4558abfb8f176a6bcaedf.png

通信封装

基于以上的进程通信方式,如果所有通信逻辑都通过 message 事件监听,那怎么知道某一处该接收哪些消息,该如何发送一个具有唯一标识的消息?

vscode 本身没有提供类似的功能,不过可以自己封装。

流程如图:

bebc5ea4074d5812952f781e218966e8.jpeg

Webview端:

export abstract class App<> {
    //    private readonly _api: vscodeApi;


    // 单向通信
    protected sendCommand<TCommand extends IpcCommandType<any>>(
        command: TCommand,
        params: IpcMessageParams<TCommand>
    ): void {
        const id = nextIpcId();
        this.postMessage({ id: id, method: command.method, params: params });
    }


    // 双向通信
    protected async sendCommandWithCompletion<
        TCommand extends IpcCommandType<any>,
        TCompletion extends IpcNotificationType<any>
    >(
        command: TCommand,
        params: IpcMessageParams<TCommand>,
        completion: TCompletion
    ): Promise<IpcMessageParams<TCompletion>> {
        const id = nextIpcId();


        const promise = new Promise<IpcMessageParams<TCompletion>>(
            (resolve, reject) => {
                let timeout: ReturnType<typeof setTimeout> | undefined;


                const disposables = [
                    DOM.on(window, 'message', (e: MessageEvent<IpcMessage>) => {
                        onIpc(completion, e.data, (params) => {
                            if (e.data.completionId === id) {
                                disposables.forEach((d) => d.dispose());
                                queueMicrotask(() => resolve(params));
                            }
                        });
                    }),
                    {
                        dispose: function () {
                            if (timeout != null) {
                                clearTimeout(timeout);
                                timeout = undefined;
                            }
                        },
                    },
                ];


                timeout = setTimeout(() => {
                    timeout = undefined;
                    disposables.forEach((d) => d.dispose());
                    debugger;
                    reject(
                        new Error(
                            `Timed out waiting for completion of ${completion.method}`
                        )
                    );
                }, 600000);
            }
        );


        this.postMessage({
            id: id,
            method: command.method,
            params: params,
            completionId: id,
        });
        return promise;
    }


    private postMessage(e: IpcMessage) {
        this._api.postMessage(e);
    }
}

Node端:

parent.webview.onDidReceiveMessage(this.onMessageReceivedCore, this),


onMessageReceivedCore(e: IpcMessage) {
        if (e == null) return;


        switch (e.method) {
            case ExecuteCommandType.method:
                onIpc(ExecuteCommandType, e, params => {
                    if (params.args != null) {
                        void executeCommand(params.command as Commands, ...params.args);
                    } else {
                        void executeCommand(params.command as Commands);
                    }
                });
                break;
            default:
                this.provider.onMessageReceived?.(e);
                break;
        }
    }


// commands.ts
import { commands } from 'vscode';


export function executeCommand<U = any>(command: Commands): Thenable<U>;
export function executeCommand<T = unknown, U = any>(command: Commands, arg: T): Thenable<U>;
export function executeCommand<T extends [...unknown[]] = [], U = any>(command: Commands, ...args: T): Thenable<U>;
export function executeCommand<T extends [...unknown[]] = [], U = any>(command: Commands, ...args: T): Thenable<U> {
    return commands.executeCommand<U>(command, ...args);
}

c40b547902a98f13c4ce6a69f4521c71.png

需求实现

基于以上,视图层、逻辑层、通信层的框架就大致完成了,接下来就是基于需求本身实现视图(react)和逻辑(node)的实现了。

希望此文能帮助大家快速对 vscode 插件开发有一定了解。后续会再介绍基于 vscode 的 DX 插件和使用建议、以及提高 vscode 开发效率的配置分享~

95ed34d1102375d475d61b21e74e1935.png

参考资料

  • Introduction | Electron (electronjs.org):

    https://www.electronjs.org/docs/latest/?spm=ata.21736010.0.0.317e4797PUtlD0

  • Webview API | Visual Studio Code Extension API:

    https://code.visualstudio.com/api/extension-guides/webview?spm=ata.21736010.0.0.317e4797PUtlD0

团队介绍

我们是淘天集团 - 终端体验平台团队,立足于淘宝体验平台及集团移动中台定位,致力于无线端到端前沿技术探索,深入终端厂商到原生系统技术挖掘,打造集团先进且行业领先的终端基础设施及配套服务,涵盖多端性能体验、终端技术服务、原生技术研发、用户增长触达等关键领域的工作,为阿里巴巴数百款活跃App提供研发与性能支撑,即是集团终端技术生态的基石团队之一,也是淘天双11核心支撑团队之一!

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1875182.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

福昕阅读器再打开PDF文件时,总是单页显示,如何设置打开后就自动显示单页连续的模式呢

希望默认进入连续模式 设置方法 参考链接 如何设置使福昕阅读器每次启动时不是阅读模式 每次启动后都要退出阅读模式 麻烦_百度知道 (baidu.com)https://zhidao.baidu.com/question/346796551.html#:~:text%E5%9C%A8%E3%80%90%E5%B7%A5%E5%85%B7%E3%80%91%E9%87%8C%E6%9C%89%E…

使用ROS2的RCLCPP客户端库来实现话题通信

1.创建发布者目录文件 cd d2lros2/ mkdir -p chapt3/chapt3_ws/src cd chapt3/chapt3_ws/src 2.创建发布者节点 ros2 pkg create example_topic_rclcpp --build-type ament_cmake --dependencies rclcpp 3.新建发布者类 touch example_topic_rclcpp/src/topic_publisher_01.…

[CAN] Intel 格式与 Motorola 格式的区别

编码格式 数据传输规则一、Intel 格式编码二、Motorola 格式编码三、分析总结🙋 前言 CAN 总线信号的编码格式有两种定义:Intel 格式与 Motorola 格式。究竟两种编码格式有什么样的区别呢?设计者、dbc 文件编辑者或者测试人员又该如何判断两种格式,并进行有效正确的配置和解…

下载旧版本vscode及扩展,离线下载远程linux服务器插件

背景 工作的内网没有网络&#xff0c;无法使用网络来下载插件和vscode软件&#xff0c;且有远程linux服务器需求&#xff0c;linux服务器中lib相关库比较旧且无法更新&#xff0c;所以需要选择一个旧版本的vscode&#xff0c;相应插件也需要选择旧版本的 旧版本vscode下载 没…

SQL 29 计算用户的平均次日留存率题解

问题截图如下&#xff1a; SQL建表代码&#xff1a; drop table if exists user_profile; drop table if exists question_practice_detail; drop table if exists question_detail; CREATE TABLE user_profile ( id int NOT NULL, device_id int NOT NULL, gender varchar…

金融科技如何以细颗粒度服务提升用户体验与满意度

在金融科技迅速发展的当下&#xff0c;各种技术手段被广泛应用于提升用户体验与满意度。这些技术手段不仅提供了更为精准、个性化的服务&#xff0c;还通过优化操作流程、提升服务效率等方式&#xff0c;显著改善了用户的金融生活。以下将详细探讨金融科技如何运用这些技术手段…

短视频哪个软件好用?成都柏煜文化传媒有限公司

短视频哪个软件好用&#xff1f;一文带你了解各大平台特色 随着移动互联网的飞速发展&#xff0c;短视频已经成为现代人生活中不可或缺的一部分。市面上涌现出众多短视频平台&#xff0c;它们各具特色&#xff0c;满足了不同用户的需求。那么&#xff0c;短视频哪个软件好用呢…

Python学习笔记五

1.当循环执行完整后&#xff0c;就会执行else里面的代码 s0 i1 while i<100:sii1 else:print(s) 当循环不完整就会如下 s0 i1 while i<100:sii1if s6:break; else:print(s) 2. 实现密码匹配&#xff0c;可以输入三次&#xff0c;若输入三次错误会退出&#xff0c;或者输…

Linux高并发服务器开发(六)线程

文章目录 1. 前言2 线程相关操作3 线程的创建4 进程数据段共享和回收5 线程分离6 线程退出和取消7 线程属性&#xff08;了解&#xff09;8 资源竞争9 互斥锁9.1 同步与互斥9.2 互斥锁 10 死锁11 读写锁12 条件变量13 生产者消费者模型14 信号量15 哲学家就餐 1. 前言 进程是C…

vue3-openlayers 图标闪烁、icon闪烁、marker闪烁

本篇介绍一下使用vue3-openlayers 图标闪烁、icon闪烁、marker闪烁 1 需求 图标闪烁、icon闪烁、marker闪烁 2 分析 图标闪烁、icon闪烁、marker闪烁使用ol-animation-fade组件 3 实现 <template><ol-map:loadTilesWhileAnimating"true":loadTilesWh…

PyScript:在浏览器中释放Python的强大

PyScript&#xff1a;Python代码&#xff0c;直接在网页上运行。- 精选真开源&#xff0c;释放新价值。 概览 PyScript是一个创新的框架&#xff0c;它打破了传统编程环境的界限&#xff0c;允许开发者直接在浏览器中使用Python语言来创建丰富的网络应用。结合了HTML界面、Pyo…

美国总统对决影响比特币价格

刚刚&#xff0c;2024 年首场总统辩论之后&#xff0c;政治格局发生了翻天覆地的变化&#xff0c;数字货币市场也感受到了这种震动。这场辩论的时间安排史无前例&#xff0c;交锋激烈&#xff0c;在民主党内部引发了一系列猜测和战略。正如我们的 CNN 快报民意调查和摇摆州焦点…

STM32人体心电采集系统

资料下载地址&#xff1a;STM32人体心电采集系统 1、项目功能介绍 此项目主要实现了以STM32为核心的人体心电采集系统软硬件的设计。软件设计过程是在STM32上移植的uCGUI做图形界面&#xff0c;并如实显示采集到的心电波形信号&#xff0c;有SD卡存储和USB数据传输功能。 2、实…

1.SQL注入-数字型

SQL注入-数字型(post) 查询1的时候发现url后面的链接没有传入1的参数。验证为post请求方式&#xff0c;仅显示用户和邮箱 通过图中的显示的字段&#xff0c;我们可以猜测传入数据库里面的语句&#xff0c;例如&#xff1a; select 字段1,字段2 from 表名 where id1; 编辑一个…

RabbitMQ 的经典问题

文章目录 前言一、防止消息丢失1.1 ConfirmCallback/ReturnCallback1.2 持久化1.3 消费者确认消息 二、防止重复消费三、处理消息堆积四、有序消费消息五、实现延时队列六、小结推荐阅读 前言 当设计和运维消息队列系统时&#xff0c;如 RabbitMQ&#xff0c;有几个关键问题需…

“Hello, World!“ 历史由来

布莱恩W.克尼汉&#xff08;Brian W. Kernighan&#xff09;—— Unix 和 C 语言背后的巨人 布莱恩W.克尼汉在 1942 年出生在加拿大多伦多&#xff0c;他在普林斯顿大学取得了电气工程的博士学位&#xff0c;2000 年之后取得普林斯顿大学计算机科学的教授教职。 1973 年&#…

海南聚广众达电子商务咨询有限公司专业电商服务代名词

在数字化浪潮席卷全球的今天&#xff0c;电子商务行业日新月异&#xff0c;各大平台纷纷崭露头角。其中&#xff0c;抖音电商以其独特的短视频直播模式&#xff0c;迅速崛起成为电商领域的新星。而在这股浪潮中&#xff0c;海南聚广众达电子商务咨询有限公司凭借其专业的服务和…

华为实训案例

案例下载 案例内包含空拓扑图、配置完整的拓扑、以及步骤脚本文档&#xff0c;可按需下载。 拓扑图 任务清单 &#xff08;一&#xff09;基础配置 根据附录1拓扑图、附录2地址规划表、附录3设备编号表&#xff0c;配置设备接口及主机名信息。 将所有终端超时时间设置为永不…

我在高职教STM32——GPIO入门之按键输入(2)

大家好&#xff0c;我是老耿&#xff0c;高职青椒一枚&#xff0c;一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次&#xff0c;同行应该都懂的&#xff0c;老师在课堂上教学几乎是没什么成就感的。正因如此&#xff0c;才有了借助 CSDN 平台寻求认同感和成就…

【图论 树 深度优先搜索】2246. 相邻字符不同的最长路径

本文涉及知识点 图论 树 图论知识汇总 深度优先搜索汇总 LeetCode 2246. 相邻字符不同的最长路径 给你一棵 树&#xff08;即一个连通、无向、无环图&#xff09;&#xff0c;根节点是节点 0 &#xff0c;这棵树由编号从 0 到 n - 1 的 n 个节点组成。用下标从 0 开始、长度…