You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
再来看插件的代码,插件中需要引入一个叫 vscode 的模块
import * as vscode from 'vscode';
熟悉 TypeScript 的朋友都知道这实际上只是引入了一个 vscode.d.ts 类型声明文件而已,这个文件包含了所有插件可用的 API 及类型定义。
这些 API 在插件 import 时就被注入到了插件的运行环境中,它们定义在源码 src/vs/workbench/api/node/extHost.api.impl.ts 文件 createApiFactory 函数中,通过 defineAPI 函数统一被注入到插件运行环境。
functiondefineAPI(factory: IExtensionApiFactory,extensionPaths: TernarySearchTree<IExtensionDescription>,extensionRegistry: ExtensionDescriptionRegistry): void{// each extension is meant to get its own api implementationconstextApiImpl=newMap<string,typeofvscode>();letdefaultApiImpl: typeofvscode;// 已被全局劫持过的 requireconstnode_module=<any>require.__$__nodeRequire('module');constoriginal=node_module._load;// 重写 Module.prototype._load 方法node_module._load=functionload(request: string,parent: any,isMain: any){// 模块名不是 vscode 调用原方法返回模块if(request!=='vscode'){returnoriginal.apply(this,arguments);}// 这里会为每一个插件生成一份独立的 API (为了安全考虑?)constext=extensionPaths.findSubstr(URI.file(parent.filename).fsPath);if(ext){letapiImpl=extApiImpl.get(ext.id);if(!apiImpl){// factory 函数会返回所有 API apiImpl=factory(ext,extensionRegistry);extApiImpl.set(ext.id,apiImpl);}returnapiImpl;}/* 省略部分代码 */}}
从加载一个插件开始
以我们熟悉的 vscode-eslint 为例,查看源码会发现入口是 extension.ts 文件里的 activate 函数,它的函数签名像这样:
需要了解的一点是, package.json 里的 activationEvents 字段定义了插件的激活事件,考虑到性能问题,我们并不需要一启动 VSCode 就立即激活所有的插件。activation-events 定义了一组事件,当 activationEvents 字段指定的事件被触发时才会激活相应的插件。包含了特定语言的文件被打开,或者特定的【命令】被触发,以及某些视图被切换甚至是一些自定义命令被触发等等事件。
例如在 vscode-java 中,activationEvents 字段的值为
其中包含 languageId 为 java 的文件被打开,以及由该插件自定义的几个 JDT 语言服务命令被触发,和【工作空间】包含 pom.xml/buld.gradle 这些事件。在以上事件被触发时插件将会被激活。
这段逻辑被定义在
src/vs/workbench/api/node/extHostExtensionService.ts
中其中 ExtensionsActivator 定义在 src/vs/workbench/api/node/extHostExtensionActivator.ts 中
当调用 activator.activateByEvent 方法时(既某个事件被触发),activator 会获取所有符合该事件的插件并逐一执行 extHostExtensionService._activateExtension 方法(也就是 activator.actualActivateExtension) ,中间省去获取上下文,记录日志等一通操作后调用了 extHostExtensionService._callActivateOptional 静态方法
至此,插件被成功激活。
插件如何运行
再来看插件的代码,插件中需要引入一个叫 vscode 的模块
import * as vscode from 'vscode';
熟悉 TypeScript 的朋友都知道这实际上只是引入了一个 vscode.d.ts 类型声明文件而已,这个文件包含了所有插件可用的 API 及类型定义。
这些 API 在插件 import 时就被注入到了插件的运行环境中,它们定义在源码
src/vs/workbench/api/node/extHost.api.impl.ts
文件createApiFactory
函数中,通过 defineAPI 函数统一被注入到插件运行环境。实际上也很简单,这里的
require
已经被 Microsoft/vscode-loader 劫持了,所以在插件代码中所有通过 import (运行时会被编译为 require) 引入的模块都会经过这里,通过这种方式将 API 注入到了插件执行环境中。一般我们查看资源管理器或者进程会发现 VSCode 创建了很多个子进程,且所有插件都在一个独立的 Extension Host 进程在运行,这是考虑到插件需要在一个与主线程完全隔离的环境下运行,保证安全性。那么问题来了,我们调用 vscode.window.setStatusBarMessage('Hello World') 时是怎么在编辑器状态栏插入消息的?前文我们提到所有的 API 被定义在 extHost.api.impl.ts 文件的 createApiFactory 里,例如 vscode.window.setStatusBarMessage 的实现
实际调用的是
extHostStatusBar.setStatusBarMessage
函数,而 extHostStatusBar 则是 ExtHostStatusBar 的实例ExtHostStatusBar 包含了两个方法 createStatusBarEntry 和 setStatusBarMessage,createStatusBarEntry 返回了一个 ExtHostStatusBarEntry ,它被包装了一层代理,在 ExtHostStatusBar 被实例化化的同时也会产生一个 ExtHostStatusBarEntry 实例
所以当我们调用 setStatusBarMessage 时,先是调用了 this._statusMessage.setMessage 方法
而 this._statusMessage.setMessage 方法经过层层调用,最终调用了 ExtHostStatusBarEntry 实例的 update 方法,也就是前面的 StatusBarMessage 构造函数中的 this._item.update,而这里就到了重头戏,update 方法中包含了一个 延时为 0 的 setTimeout :
这里的 this.proxy 就是 ExtHostStatusBar 构造函数中的 this.proxy
这里的 IMainContext 其实就是继承了 IRPCProtocol 的一个别名而已,new ExtHostStatusBar 的参数是一个 rpcProtocol 实例,它被定义在 src/vs/workbench/services/extensions/node/rpcProtocol.ts 中,我们重点看一下 getProxy 的实现
_createProxy 返回的是一个代理对象,即它代理了主线程中真正实现这些 API 的对象,例如 'MainThreadStatusBar' 返回的是一个
MainThreadStatusBarShape
类型的代理。插件 API 定义中并没有实现这个接口,它只需要被主线程中对应的模块实现即可,前面我们说到 setStatusMessage 最终调用了 this._proxy.$setEntry。
_remoteCall 里会调用 RPCProcotol 的静态方法 serializeRequest 将 rpcId 方法名以及参数序列化成一个 Buffer 并发送给主线程。
关于主线程中接收到消息如何处理其实已经不用多说了,根据 rpcId 找到对应的 Services 以及方法,传入参数即可。
The text was updated successfully, but these errors were encountered: