【VSCode Web Server】插件下载流程

919 阅读2分钟

本篇主要介绍了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的请求参数:

image.png

其中我们重点关注filtersflags这两个参数。

为了便于理解,这里先介绍一下源码中的两个枚举类型: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中的三个数据可以理解为:

  1. { "filterType": 8, "value": "Microsoft.VisualStudio.Code" }是源码中写死的,表示搜索目标是VSCode插件(而不是其它)
  2. { "filterType": 10, "value": "git" }表示我们是通过文本搜索的,关键词git
  3. { "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)

最终得到的数据结构: image.png

插件下载

插件下载的源码链路特别长,阅读起来比较麻烦,主要步骤大致可分为:

  1. 下载VSIX文件至/data/CachedExtensionVSIXs目录
  2. 解压至/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'
// }