s-blog

VSCode 源码二次开发:给 vscode 对象扩展自定义 API namespace

ssssmy · 2026-06-05 · 6 min · VsCode插件开发

VSCode 源码二次开发:给 vscode 对象扩展自定义 API namespace

做 fork 版 VSCode 二次开发时,常需要给插件开发者暴露一套自定义能力,让他们能像 vscode.window.xxx 那样调用 vscode.myExt.xxx。这需要在源码里打通「主线程(MainThread)↔ 插件宿主(ExtHost)」的 RPC 全链路。本文用一个最小示例 vscode.myExt 走一遍完整流程。

背景:插件系统为什么要分两端

VSCode 每个插件运行在**独立的扩展宿主进程(Extension Host)**里,与主进程隔离,二者通过基于 Node net 的 IPC + 代理(Proxy)模式通信:

  • 插件调用的 vscode.* API,多数只是 ExtHost 端的「代理」,真正干活的逻辑在主线程;
  • 调用被序列化成消息,跨进程发到 MainThread 执行,结果再回传。

所以扩展一个自定义 namespace,需要成对地写 MainThread 实现(主线程,真正逻辑)和 ExtHost 代理(插件宿主,转发调用),并在 Protocol 里定义两端接口、在 .d.ts 里定义对插件暴露的类型、最后在 api.impl 里把它挂到 vscode 对象上。

整体五步骨架:类型定义 → MainThread 实现 → Protocol 接口 → ExtHost 代理 → api.impl 暴露

1. MainThread —— 主线程实现

src/vs/workbench/api/browser 新增 mainThreadMyExt.ts

import { Disposable } from 'vs/base/common/lifecycle';
import { IMyExtService } from 'vs/platform/myExt/common/myExt';
import { ExtHostMyExtShape, MainContext, MainThreadMyExtShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';

@extHostNamedCustomer(MainContext.MainThreadMyExt)
export class MainThreadMyExt extends Disposable implements MainThreadMyExtShape {
  private readonly _proxy: ExtHostMyExtShape;

  constructor(
    extHostContext: IExtHostContext,
    @IMyExtService private readonly myExtService: IMyExtService
  ) {
    super();
    this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostMyExt);
    // 把主线程 service 的事件转发给 ExtHost
    this._register(this.myExtService.onMessage(e => {
      this._proxy.$onDidReceiveMessage(e);
    }));
  }

  async $showMessage(value: string): Promise<void> {
    return this.myExtService.showMessage(value);
  }
}

@extHostNamedCustomer 装饰器负责把这个类注册进依赖注入系统。

注册:在 src/vs/workbench/api/browser/extensionHost.contribution.ts import 一下让它生效:

import './mainThreadMyExt';

2. ExtHost —— 插件宿主端代理

src/vs/workbench/api/common 新增 extHostMyExt.ts

import { IMainContext, MainThreadMyExtShape, MainContext, ExtHostMyExtShape } from 'vs/workbench/api/common/extHost.protocol';
import { Emitter, Event } from 'vs/base/common/event';

export class ExtHostMyExt implements ExtHostMyExtShape {
  private _proxy: MainThreadMyExtShape;

  private readonly _onDidReceiveMessage = new Emitter<any>();
  readonly onDidReceiveMessage: Event<any> = this._onDidReceiveMessage.event;

  constructor(mainContext: IMainContext) {
    this._proxy = mainContext.getProxy(MainContext.MainThreadMyExt);
  }

  // 转发给主线程执行
  $showMessage(value: string): Promise<void> {
    return this._proxy.$showMessage(value);
  }

  // 接收主线程回传的事件
  $onDidReceiveMessage(e: any) {
    this._onDidReceiveMessage.fire(e);
    return Promise.resolve();
  }
}

3. Protocol —— 定义两端接口

src/vs/workbench/api/common/extHost.protocol.ts 增加成对接口,并在 MainContext / ExtHostContext 注册标识:

export interface MainThreadMyExtShape extends IDisposable {
  $showMessage(value: string): Promise<void>;
}
export interface ExtHostMyExtShape {
  $showMessage(value: string): Promise<void>;
  $onDidReceiveMessage(e: any): Promise<void>;
}
  • MainThreadMyExtShape = MainThread 类的类型约束;
  • ExtHostMyExtShape = ExtHost 类的类型约束。

4. 类型定义 —— 对插件暴露的 API

所有对插件可见的类型加到 src/vscode-dts/vscode.d.ts

export namespace myExt {
  export const onDidReceiveMessage: Event<any>;
  export function showMessage(value: string): Thenable<void>;
}

命名约定:类型 / 接口 / 枚举不以 “I” 开头;事件方法以 on 开头并接 Did / Will + 动词,如 onDidReceiveMessage

5. RpcProtocol —— 在 api.impl 挂到 vscode 对象

src/vs/workbench/api/common/extHost.api.impl.tscreateApiFactoryAndRegisterActors 里:

// 注册 ExtHost actor
const extHostMyExt = rpcProtocol.set(ExtHostContext.ExtHostMyExt, new ExtHostMyExt(rpcProtocol));

// ... 在返回给插件的工厂函数内,组装 namespace 实现:
const myExt: typeof vscode.myExt = {
  get onDidReceiveMessage() {
    return extHostMyExt.onDidReceiveMessage;
  },
  async showMessage(value: string): Promise<void> {
    return extHostMyExt.$showMessage(value);
  }
};

// ... 在最终 return 的 vscode 对象里挂上:
return <typeof vscode>{
  // ...
  myExt,
  // ...
};

至此插件里就能 vscode.myExt.showMessage('hi') 了。

官方风格的精简示例

社区里有同样思路的精简版,比如给 vscode 加一个 trash.getTrashRootPath

// vscode.d.ts
export namespace trash {
  export function getTrashRootPath(): Thenable<string>;
}

// 插件中调用
vscode.trash.getTrashRootPath().then(data => {
  console.log(data);
});

五步骨架完全一致,复杂度只在于「方法/事件多少」与「跨进程传输的数据结构」。

配套:私有 @types 的引用

如果团队是 fork 版且把自定义 API 类型发布到了私有 npm 源,插件项目要用上这些类型时:

  1. package.json@types/vscode 换成你们的私有类型包 @types/<your-vscode>
  2. 在项目 .npmrc 增加私有 registry 的 scope 映射,例如 @types:registry=<你的私有 npm 源>
  3. 删除 node_modules 与 lockfile,重新安装。

原文链接:https://www.ssssmy.com/notes/vscode-yuan-ma-er-ci-kai-fa-gei-vscode-dui-xiang-kuo-zhan-zi-ding-yi-api-namespace