VSCode For Web 深入浅出 -- 插件加载机制

最近我在浏览 VSCode for web 的 repo,在最近更新的一些 commit 中发现了一个新的 VSCode 插件特性支持,名为 webOpener,它的作用是什么呢?又是如何影响插件加载的呢?在这一篇中我们结合 VSCode For Web 的插件加载机制来详细分析一下。

VSCode for web 的插件加载机制

我们知道,由于 VSCode for web 运行在浏览器上,因此,它的插件加载机制与 VSCode for desktop 有所不同。

在 VSCode for desktop 中,插件是以 vsix 包的形式存在的,因此,VSCode for desktop 可以直接通过 vsix 包的形式加载插件。而在 VSCode for web 中,由于浏览器的安全机制,不能直接加载 vsix 包。

因此,VSCode for web 采用了一种特殊的插件加载机制。发布 VSCode for web 插件时,发布系统会直接将项目编译,并发布到 CDN 节点上。当用户加载插件时,通过向该目标 url 发送请求,拉取远端(也可以是本地)的 extension.js 文件。并利用 web worker 加载机制,为每个插件分配独立线程加载与执行。

在生产环境中,对每个进入 VSCode 插件商店的插件,VSCode for web 会将支持 web 环境的插件的 package.jsonextension.js 等文件打包成一个 zip 包,然后根据 publisher 分配合适的二级域名,通过 CDN 分发。

以我在使用的One Dark Pro主题为例:

[图片上传失败...(image-a930df-1683544239315)]

而在调试模式中,我们可以通过 Install extension from location...命令,指定编译后插件的 url,从而加载插件。

[图片上传失败...(image-9d188b-1683544239315)]

我们使用本地服务器,指定一个已编译好的 VSCode web extension,并填入本地服务器地址 (https://localhost:5000),并刷新页面,那么从 Chrome 的 Network 中可以看到 VSCode 向目标位置请求了package.jsonextension.js,并看到插件已经被成功加载了。

[图片上传失败...(image-48e1e0-1683544239315)]

通过这样的方式,VSCode for web 在每次页面打开后,完成了对用户自定义的插件管理与加载。并由于web worker的特性,每个插件的执行环境都是独立且相互隔离的。

通过特殊 url 路由的方式的插件加载机制

VSCode for web 最突出的特点是它是运行在浏览器上的,因此,我们可以利用 url,来实现一些奇妙的新特性。例如,通过特殊的 url 路由,免安装地加载插件。

目前,vscode.dev 可以使用这样的方式加载插件:

https://vscode.dev/+publisher.name

例如,在浏览器中输入 https://vscode.dev/+ms-vscode.onedrive-browser 将加载 OneDrive 浏览器扩展。

当然,我们也可以使用同样的方式加载本地编译的插件。由于 vscode.dev 强制要求 secure context ,因此,我们需要在本地启动一个 https 的服务器,并对 url 进行 base64 编码,才能正常访问。

访问https://vscode.dev/+aHR0cHM6Ly9sb2NhbGhvc3Q6MzAwMA==即可。(后面那一段为"https://localhost:3000") 的 base64 编码)

webOpener 特性介绍

有开发过 VSCode for desktop 的插件的同学应该知道,vscode 插件的所有能力都是在 package.json 中声明的,这也是为什么 VSCode 除了需要加载入口的 extension.js 外,还一定要加载插件的 package.json 的原因。

在插件 package.jsoncontributes 字段中,我们可以声明插件的各种能力,例如,命令、菜单、快捷键、主题、语言、调试器等等。

对于 vscode for web 版本的插件来说,我们还可以声明 webOpener 能力,其所有属性都是可选的。声明如下:

{
  "name": "onedrive-browser",
  "contributes": {
    "webOpener": {
      "scheme": "onedrive",
      "import": "webOpener.js",
      "runCommands": [{ "command": "hello-world", "args": ["$url"] }]
    }
    ...
  }
}

webOpener.scheme

默认情况下,vscode.dev/+publisher.name 路由将直接打开默认的 VSCode 示例工作区。但是,如果提供了 scheme path,则 VSCode 将根据路由参数打开一个以该协议打开 url 中后续 path 指向的文件夹,格式如下:

# 当 scheme 设置为 onedrive
https://vscode.dev/+publisher.name/remoteAuthority/path/segments/...

例如,当插件 webOpener 的 scheme 设置为 onedrive 时,访问 https://vscode.dev/+ms-vscode.onedrive-browser/myPersonalDrive/cool/folder ,此时访问 url 将重定向为 onedrive:///myPersonalDrive/cool/folder

若此协议不在 VSCode 的内置协议中,我们可以在插件中通过 vscode.workspace.registerFileSystemProvider 这个 API 注册自定义的 FileSystemProvider,从而实现对自定义协议的 FileSystem 支持。

本质上,它打开的方式与 VSCode for web 的 vscode.open 命令也是一致的。

webOpener.runCommands

当 VSCode 的主 workbench 加载完毕后,会触发 webOpeneronDidCreateWorkbench 的钩子,并执行此处声明的命令集。

这将传入一个命令数组,例如:[{ "command": "test-extension.hello-world", "args": ["$url"] }],此时将可以执行自定义插件 test-extension 的相关命令。

其中,$url 指代当前页面 url。如果插件的初始化依赖来自 url 的 query/path 等等信息,这将很有用。

webOpener.import

这里定义了 webOpener 加载的入口点。它是一个相对于插件 package.json 的 ES Module 路径,例如:webOpener.js

它与 extension.js 一样,默认导出一个 doRoute 函数,该函数将获取 route 与 workbench 等信息(workbench 这个实例中提供了当前 vscode for web 的命令、日志、环境、window、workspace 等多种能力支持)。由于 webOpener.js 运行在主线程中,因此它能做到的事情要比处于 web worker 下的 vscode for web 插件更多。

举一个例子,这是一个简单的 webOpener 贡献 onedrive-browser:

export default function doRoute(route) {
  // If we're not already opening a OneDrive, show the picker immediately
  // when the user hits `vscode.dev/+ms-vscode.onedrive-browser`.
  if (route.workspace.folderUri?.scheme !== 'onedrive') {
    route.onDidCreateWorkbench.runCommands.push({
      command: 'onedrive-browser.openOneDrive',
      args: [],
    });
  }
}

它将在 workbench 加载完毕后,判断当前的 workspace 是否为 onedrive,如果不是,则执行 onedrive-browser.openOneDrive 命令,从而打开 onedrive 文件夹。

webOpener 与插件的通信机制

在了解了 webOpener 的基本特性之后,我们来看看该如何利用这些特性,与我们的 web 插件进行通信,从而扩展插件能力。

我们可以看出,由于 webOpener 加载在主线程,且 doRoute 方法的执行时机在主线程 workbench 加载完毕之后,在请求远端插件并执行之前。因此,我们可以有两种方式来传递信息,与处于 web worker 下,与宿主隔离的插件进行通信。

第一种即为在 runCommands 中介绍的,通过执行 command 并传递 url 的方式传递初始化信息。该方式也是 webOpener 与插件通信的常用方式之一,用于为初始化插件时提供部分依赖参数。

第二种则是通过 doRoute 方法,捕获此时的请求信息,并根据请求信息的不同对插件能力进行不同的变更,但本质上还是通过 command 的方式给插件发送 args 来实现的。

我在当前最新版本的 vscode-dev 代码库中(1.79.0),并未发现直接通过 webOpener 暴露类似 postMessage 的与插件通信的方法,因此到目前为止,我们只能通过给插件的 command 方式触发 trigger 与传入参数这一种方式来实现与插件的通信。这导致了在 web 下插件的能力其实相当受限。

总结

本篇文章解析了在 VSCode for web 中的插件加载机制,以及如何通过 webOpener 特性来扩展插件的能力。

我们可以看出,在现阶段的 VSCode for web 中,插件的加载机制也仅仅只是做到了可用状态。由于 web worker 天然的与主线程隔离的特性,desktop 的很多好用的功能性插件(即除了 theme/key-binding 这种不需要执行逻辑的插件之外)在 web 端的支持还是会遇到很多问题,并不能无缝迁移。这点也是我在尝试开发 VSCode for web 插件时最大的痛点。

不过,随着 VSCode for web 项目仍在进行高频的开发与完善,希望未来的 VSCode for web 能在插件开发与使用上尽可能对齐甚至兼容 desktop 的体验。

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348