写这篇文章是因为最近一段时间的工作涉及到 Cloud Studio 插件这一块的内容,旧的插件系统在面向用户开放后暴露了安全性、扩展性等诸多问题。调研了几个不同架构下 IDE 的插件系统实现( Theia, VS Code 等),也大致阅读了一遍 VS Code 插件系统相关的源码,在这里做一个简单的分享,个人水平有限,如有错误之处还请观众老爷们指点一下。
以我们熟悉的 vscode-eslint 为例,查看源码会发现入口是 extension.ts 文件里的 activate 函数,它的函数签名像这样:
activate(context: ExtensionContext): void
需要了解的一点是, package.json 里的 activationEvents 字段定义了插件的激活事件,考虑到性能问题,我们并不需要一启动 VS Code 就立即激活所有的插件。activation-events 定义了一组事件,当 activationEvents 字段指定的事件被触发时才会激活相应的插件。包含了特定语言的文件被打开,或者特定的【命令】被触发,以及某些视图被切换甚至是一些自定义命令被触发等等事件。
例如在 vscode-java 中,activationEvents 字段的值为
"activationEvents": [
"onLanguage:java",
"onCommand:java.show.references",
"onCommand:java.show.implementations",
"onCommand:java.open.output",
"onCommand:java.open.serverLog",
"onCommand:java.execute.workspaceCommand",
"onCommand:java.projectConfiguration.update",
"workspaceContains:pom.xml",
"workspaceContains:build.gradle"
]
其中包含 languageId 为 java 的文件被打开,以及由该插件自定义的几个 JDT 语言服务命令被触发,和【工作空间】包含 pom.xml/buld.gradle 这些事件。在以上事件被触发时插件将会被激活。
这段逻辑被定义在 src/vs/workbench/api/node/extHostExtensionService.ts 中
// 由 ExtensionHostProcessManager 调用并传入相应事件作为参数
public $activateByEvent(activationEvent: string): Thenable<void> {
return (
this._barrier.wait()
.then(_ => this._activateByEvent(activationEvent, false))
);
}
/* 省略部分代码 */
// 实例化 activator
this._activator = new ExtensionsActivator(this._registry, {
/* 省略部分代码 */
actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise<ActivatedExtension> => {
return this._activateExtension(extensionDescription, reason);
}
});
// 调用 ExtensionsActivator 的实例 activator 的方法激活插件
private _activateByEvent(activationEvent: string, startup: boolean): Thenable<void> {
const reason = new ExtensionActivatedByEvent(startup, activationEvent);
return this._activator.activateByEvent(activationEvent, reason);
}
其中 ExtensionsActivator 定义在 src/vs/workbench/api/node/extHostExtensionActivator.ts 中
export class ExtensionsActivator {
constructor(
registry: ExtensionDescriptionRegistry,
// 既上文中实例化 activator 传的第二个参数
host: IExtensionsActivatorHost,
) {
this._registry = registry;
this._host = host;
}
}
当调用 activator.activateByEvent 方法时(既某个事件被触发),activator 会获取所有符合该事件的插件并逐一执行 extHostExtensionService._activateExtension 方法(也就是 activator.actualActivateExtension) ,中间省去获取上下文,记录日志等一通操作后调用了 extHostExtensionService._callActivateOptional 静态方法
/* 省略部分代码 */
// extension.ts 里的 activate 函数
if (typeof extensionModule.activate === 'function') {
try {
activationTimesBuilder.activateCallStart();
logService.trace(`ExtensionService#_callActivateOptional ${extensionId}`);
// 调用并传入相关参数
const activateResult: Thenable<IExtensionAPI> = extensionModule.activate.apply(global, [context]);
activationTimesBuilder.activateCallStop();
activationTimesBuilder.activateResolveStart();
return Promise.resolve(activateResult).then((value) => {
activationTimesBuilder.activateResolveStop();
return value;
});
} catch (err) {
return Promise.reject(err);
}
}
至此,插件被成功激活。
再来看插件的代码,插件中需要引入一个叫 vscode 的模块
import * as vscode from 'vscode';