VSCode 插件开发指北(一) 结构与规范

AI summary
date
Aug 5, 2024
slug
vscode-1
status
Published
tags
技术
summary
本文介绍了 VSCode 插件开发的基本结构与规范,包括 VSCode 的架构、插件的能力、配置文件的结构(如 package.json)及其重要字段,以及插件逻辑的实现。插件运行在独立进程中,开发者需注册基本信息和功能贡献,并实现激活和销毁逻辑。
type
Post

VScode 基本结构

在正式进入 VScode 插件开发之前,我们先简单了解一下 VScode 本身。
 
notion image
 

1.1 基于 Electron 的 VScode(架构)

VScode 是基于 electron 构建的项目。其宏观上的运行结构如下图:
 
  • Chromium: Blink(渲染能力) + Web API
  • Node.js: 操作系统级别能力(文件读写,进程管理,网络管理)
  • Native API: 特定平台的操作系统交互能力(对话框,窗口事件...)
 

1.2 进程结构(运行)

Electron 程序是多进程的。
  • 主进程 Main Process: 应用启动第一个进程是主进程,负责原生 GUI, 窗口管理,控制整个应用的生命周期,访问底层操作系统 API
  • 渲染进程 Renderer Process: 被主进程管理,每个窗口通常对应一个渲染进程,负责前端渲染,页面脚本执行。通过 IPC 与主进程通信。
  • GPU 进程 (可选): GPU 加速任务
 
在 VScode 这个应用的场景中:
其结构示意图如下:
notion image
  • 主进程:整个应用的生命周期,处理菜单等全局UI,快捷键
  • 渲染进程:VSCode 主窗口的渲染,处理用户交互文本编辑,文件树视图
  • 拓展主机进程 Extension Host:隔离运行插件(可以有多个该进程,用于不同的拓展插件)
  • LSP 进程: 关于 LSP 又是一个庞大的内容,它抽象了对语言的支持,比如代码补全,快速跳转,语法错误诊断...
  • ....
 
VScode 运行时,活动监控截图:
notion image
VScode 插件运行在单独的进程中
这样做明显的好处是提高了安全性(因为插件的来源是非信任的,假设插件崩了,VScode 本身应该保持稳定)。
插件激活时,新的插件进程启动,执行插件逻辑。
热身结束,现在,我们正式开始学习插件开发:
从宏观上看,学习插件开发的两个关键是:
  • 边界: 所谓边界,就是我们的插件能增强 VScode 一些什么?(显然取决于 VScode 提供的 api 的边界)
  • 规范: 所谓规范,就是我们如何告诉 VScode 我们要干嘛?( 不妨想想如何你是设计者,你会如何做?常见的就是特定格式的配置文件解析 + 回调函数注册 )
我们围绕上面两个两个问题展开。
 

边界

2.1 VScode 本身是什么?

下面这个图比较清晰的说明了 VScode 的产品定位:
  • Editor:轻量,文件管理,语言高亮,快捷键
  • IDE:项目,语言能力(跳转,提示),Debug,构建集成,代码片段 ...
notion image
值得一提的是,VScode 将其编辑器能力封装成单独的库 Monaco
 

2.2 VScode 插件能做些什么

在探讨如何编写 VScode 插件之前,先来考虑插件可能有哪些能力,即他能拓展 VScode 什么?
notion image
  • 语言支持:代码补全提示,格式化,跳转,代码检查...
  • 主题配色:语法高亮,主题配置
  • 编辑器增强:扩充编辑器的功能,举例:Auto Import(自动导入引用),Image preview(预览导入的图片)...
  • VScode 通用功能:快捷键,命令,消息通知...
  • UI 扩充: 侧边栏 Activity Bar , 底部 Panel, Editor 视图 ...(见上图)
  • ...
总的来看,插件主要可以:
  1. 增强代码编辑体验(围绕文本编辑)
  1. 扩充 UI(嵌入一些自定义的 UI,可以在 SideBar , Panel, Editor 三个位置)
 

规范

VScode 本身是一个庞大的程序。如何拓展一个已有的程序?根据我们的经验也可以想到:
  1. 编辑配置文件(基本信息,插件激活前就要被使用)
  1. 注册回调函数(逻辑,插件激活时实际运行脚本)
notion image

3.1 配置文件(基本信息)

插件的配置文件就是项目根目录下的 package.json 文件
很多配置都是见名知意,请看这一份配置文件:
  • 基本信息(插件名,作者,版本,描述,图标,分类,标签)
    • name, version, publisher, description, author, icon ...
  • 引擎和依赖(引擎负责标识插件适用的 VScode 版本,依赖则是该项目的依赖文件)
    • engines, dependencies, devDependencies ...
  • 激活事件
    • 该字段告诉 VScode 该插件什么时候激活
      为什么需要设置激活事件?我们的插件应该“按需”加载,节省资源,所谓的需就是通过这个字段配置
举个🌰,这段配置表示当打开 json , md ,ts 类型文件时,插件激活(详细语法在下方文档)
  • 入口文件 main(指定了插件脚本的入口文件,见下文)
  • 贡献点 contributes (即拓展了什么功能)(划重点)
    • 如下图,是 pitaya 插件在商店中展示的截图,` 功能贡献` 中展示了该插件的激活事件贡献点
      这俩信息说明了插件在什么情况下会被激活(打开特定类型的文件?还是点了插件图标,又或者是通过什么快捷键,命令...),以及该插件都拓展了什么(拓展UI?增强语言?)
      notion image
这里掏出几种 contributes 配置带大家感受一下:
新增一个 Command:
 
notion image
 
新增一个 MenuItem
 
notion image
 
总而言之, contributes 字段是整个配置文件的核心,是告诉 VScode 我这个插件都想干啥。
当然,这里只是告诉了 VScode 我们想做什么?而具体怎么做的,是通过 main 字段指定的脚本实现的
关于更加完整详细,请看官方文档:
package.json 配置详情
https://code.visualstudio.com/api/references/extension-manifest
激活事件配置详情:
https://code.visualstudio.com/api/references/activation-events
Contributes 字段配置详情:
https://code.visualstudio.com/api/references/contribution-points

3.2 回调函数(插件逻辑)

前文提到,可以在 package.json 中的 `main` 字段可声明入口文件路径,如:
小学二年级学过,回调函数的关键有俩,1. 运行时机 2. 调用者传入的参数。

3.2.1 运行时机

VScode 会根据 main 字段读取该文件夹下的两个函数:
  • 入口函数 reactive :在插件激活时调用,用来声明插件本身的逻辑
  • 出口函数 deactivate:在插件销毁时调用,用来清理一些内存资源
🌰 举个例子:

3.2.2 VScode API

在插件中,你需要使用的 API 主要来自两个地方
  • 导入 vscode import * as vscode from 'vscode': vscode 环境交互通用功能
我们统称为 VScode API
 
某种程度上,学习 VScode 插件开发的本质就是熟悉 VScode API 提供了哪些能力。
作为 VScode API 的用户,不妨先想想自己可能有哪些需求需要被 VScode API 满足:
  1. 命令的注册(配置文件告诉 vscode 我要新增一个怎样的命令,脚本文件告诉 vscode 当命令触发时我要干嘛)
  1. UI 的注册(配置文件告诉 vscode 我想在某个位置注册一个视图,脚本文件负责告诉 vscode 这个视图具体显示什么)
  1. 文件操作能力(读写用户 Workspace 中的文件)
  1. Workspace 配置的读写(即修改和读取用户的 VScode 配置)
  1. Editor 内容的读写(比如检测当前光标的文本,单词,格式化文本的能力)
  1. ....
是的,vscode API 都会满足你。下面举一些简单的🌰
  1. 注册一个命令:
context.subscriptions
值得一提的是,上方的 context.subscriptions 是一个用来释放资源的栈,vscode 会在插件销毁的时候调用栈中的函数,因此将释放资源的函数 push 进去吧。 比如在上面的代码中, vscode.commands.registerCommand 这个函数就会返回释放资源的函数。
  1. 注册 webview 视图
大致看个眼熟就行,下文“视图”章节会补充细节
  1. 操作 workspace 配置
案例:注册两个 command 来读取和更新 vscode 配置
关于 VS Code API 详情请看 VCR:
https://code.visualstudio.com/api/references/vscode-api#api-namespaces-and-classes
 
一些个人感想
无论是学习框架,语言,一套解决方案...
本质上,我们都是他们的用户,作为用户,可以先想想自己有哪些需求要满足,带着这些需求再审视学习的内容,一些内容就会 “对号入座”,减少学习难度。
 

3.3 一个值得思考的问题

为什么在package.json 中需要 contributes 这个字段?
再描述清楚一点问题,为什么需要先在 contributes 注册拓展功能,再在口入文件中实现?为什么不直接使用入口文件,省去注册。
 
下面是我的一些想法:
  1. 关注点分离
在 package.json 中声明我要干啥。
在 入口文件 中说明怎么干。
用户也可以通过阅读 package.json 大致了解这个插件拓展了哪些内容。
从这个角度来看,方便思考,方便概览。
  1. 自动集成
插件有一些视图是在插件激活前就需要有的,比如侧边的按钮,或者命令菜单中的选项。因此,他们一定不可能写在入口文件中(因为这个脚本只有插件激活时才初次运行)。
因此,有一些功能是 vscode 根据配置文件自动集成的,而不是通过我们的入口文件手动去写。
比如下文会提到的 viewContainer , vscode 会自动在目标位置渲染在 package.json 中注册的按钮。再比如命令的视图,当你按下 cmd+shift+p ,会在命令菜单中看到我们在 package.json 中注册的命令 title, icon.
从这个角度来看,这是必要的,同时也降低了开发人员的心智负担。
  1. 安全性
配置文件是静态的,vscode 分析这个静态能容来了解开发者申请了哪些功能,然后给到不同权限的沙箱。
而且,用户可以通过禁用 contributes 中的某些项,来禁用某部分拓展功能。
 
 
 
 

总结

我们了解了 VScode 的基本结构,VScode 插件运行在单独的进程中。
以及了解了编写插件时的基本规范,要编写一个 vscode 插件,主要做两件事
  • 注册 package.json (基本信息,contributes, ...)。
  • 暴露出入口函数 activate 实现插件逻辑, 出口函数 deactivate 在插件销毁时清理资源。