本篇主要介绍了VSCode Extensions搜索及下载的原理,附带少量源码分析。
服务配置
要保证VSCode左侧的Extension能够正常运行,需要配置软件源的服务地址。这里提供一个例子:
// open-vsx.org提供的服务
{
extensionsGallery: {
serviceUrl: "https://open-vsx.org/vscode/gallery",
itemUrl: "https://open-vsx.org/vscode/item",
resourceUrlTemplate: "https://open-vsx.org/vscode/asset/{publisher}/{name}/{version}/Microsoft.VisualStudio.Code.WebResources/{path}",
controlUrl: "",
recommendationsUrl: ""
},
}
需要修改两处:
- server端的配置是读取自
product.json,所以要在这个文件里加上extensionsGallery配置。
// src/vs/platform/product/common/product.ts
product = require.__$__nodeRequire(joinPath(rootPath, 'product.json').fsPath);
- browser端的配置直接读取自源码,需要修改
src/vs/platform/product/common/product.ts文件。
// Built time configuration (do NOT modify)
product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration;
// Running out of sources
if (Object.keys(product).length === 0) {
Object.assign(product, {
version: '1.63.0-dev',
// ...
// 这里加上extensionsGallery配置
修改源码的方式第一感觉有些别扭,其实在构建生产版本时会把product.json的配置注入的源码中的。本地开发一般只编译就够了,所以需要自己修改下源码。细节可看:
// build/gulpfile.vscode.web.js
if (path.endsWith('vs/platform/product/common/product.js')) {
const productConfiguration = JSON.stringify({
...product,
// ...
});
// 替换注释为具体配置
return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/);
}
搜索插件
// src/vs/platform/extensionManagement/common/extensionGalleryService.ts
context = await this.requestService.request({
type: 'POST',
url: this.api('/extensionquery'),
data,
headers
}, token);
从源码中可以找到搜索插件的请求地址是:${extensionsGalleryUrl}/extensionquery。参数会比较复杂,以下是搜索关键词git的请求参数:
其中我们重点关注filters和flags这两个参数。
为了便于理解,这里先介绍一下源码中的两个枚举类型:FilterType表示筛选的类型,Flags可以理解为特殊含义的十六进制数字。
// src/vs/platform/extensionManagement/common/extensionGalleryService.ts
enum FilterType {
Tag = 1,
ExtensionId = 4,
Category = 5,
ExtensionName = 7,
Target = 8,
Featured = 9,
SearchText = 10,
ExcludeWithFlags = 12
}
enum Flags {
None = 0x0,
IncludeVersions = 0x1,
IncludeFiles = 0x2,
IncludeCategoryAndTags = 0x4,
IncludeSharedAccounts = 0x8,
IncludeVersionProperties = 0x10,
ExcludeNonValidated = 0x20,
IncludeInstallationTargets = 0x40,
IncludeAssetUri = 0x80,
IncludeStatistics = 0x100,
IncludeLatestVersionOnly = 0x200,
Unpublished = 0x1000
}
所以上述参数filters中的三个数据可以理解为:
{ "filterType": 8, "value": "Microsoft.VisualStudio.Code" }是源码中写死的,表示搜索目标是VSCode插件(而不是其它){ "filterType": 10, "value": "git" }表示我们是通过文本搜索的,关键词git{ "filterType": 12, "value": "4096" }对应的是FilterType.ExcludeWithFlags,可以理解未“根据条件排除”。而这个"条件"就是4096,即十六进制的0x1000,对应Flags.Unpublished,所以总结起来就是“排除一些非法和未发布的插件”
而参数flags的作用是自定义接口返回的数据,它的值是由一组Flags数据通过位运算得到的值,源码也很简单:
// src/vs/platform/extensionManagement/common/extensionGalleryService.ts
withFlags(...flags: Flags[]): Query {
// 把一组数字reduce成一个值
return new Query({ ...this.state, flags: flags.reduce<number>((r, f) => r | f, 0) });
}
// 这些Flags.xxx就是在定义我们需要的返回数据字段
withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
最终得到的数据结构:
插件下载
插件下载的源码链路特别长,阅读起来比较麻烦,主要步骤大致可分为:
- 下载VSIX文件至
/data/CachedExtensionVSIXs目录 - 解压至
/data/extensions目录
有兴趣可以直接读abstractExtensionManagementService.ts文件的下载入口函数AbstractExtensionManagementService#installFromGallery
- 下载地址
根据上述请求得到的数据,可以拼接去插件包下载地址:
// src/vs/platform/extensionManagement/common/extensionGalleryService.ts
function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset {
return {
uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`,
fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`
};
}
// 从而得到下载地址:
// {
// uri: 'https://open-vsx.org/vscode/asset/donjayamanne/githistory/0.6.19/Microsoft.VisualStudio.Services.VSIXPackage?redirect=true&install=true',
// fallbackUri: 'https://open-vsx.org/vscode/asset/donjayamanne/githistory/0.6.19/Microsoft.VisualStudio.Services.VSIXPackage?install=true'
// }