前言
最近在开发一个vscode插件,看了一下官方的文档,实现一个插件功能相对比较简单而且很快,但是总感觉代码不够优雅,特别是在需要开发很多命令的时候。
看了不少开源大神们的插件代码,学到了不少知识以及开阔了眼界,这里就简单介绍一下微软开源的vsocde插件中注册命令的代码。
vscode-react-native
具体代码实现
// rn-extensions.ts
async function registerVscodeCommands() {
// 这边导入所有的命令
const commands = await import("./commands");
// 依次进行注册
Object.values(commands).forEach(it => {
EXTENSION_CONTEXT.subscriptions.push(it.formInstance().register(entryPointHandler));
});
}
// ./commands/index.ts
// 导出所有的命令
export * from "./debugScenario";
export * from "./elementInspector";
export * from "./launchAndroidEmulator";
export * from "./launchIosSimulator";
export * from "./networkInspector";
export * from "./publishToExpHost";
export * from "./reloadApp";
export * from "./restartPackager";
export * from "./runAndroid";
export * from "./runExponent";
export * from "./runIos";
export * from "./runMacOs";
export * from "./runWindows";
export * from "./selectAndInsertDebugConfiguration";
export * from "./showDevMenu";
export * from "./startLogCatMonitor";
export * from "./startPackager";
export * from "./stopLogCatMonitor";
export * from "./stopPackager";
export * from "./testDevEnvironment";
// command.ts
// 所有的命令都继承了Command类,这个类定义了命令的执行以及命令执行的生命周期
// 拿RunAndroidDevice举例子
abstract class RunAndroid extends ReactNativeCommand {
error = ErrorHelper.getInternalError(InternalErrorCode.FailedToRunOnAndroid);
// 在执行命令前的操作
async onBeforeExecute(): Promise<void> {
await super.onBeforeExecute();
assert(this.project);
const nodeModulesRoot = this.project.getOrUpdateNodeModulesRoot();
const versions = await ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(
nodeModulesRoot,
);
this.project.setReactNativeVersions(versions);
TargetPlatformHelper.checkTargetPlatformSupport(PlatformType.Android);
}
}
export class RunAndroidDevice extends RunAndroid {
// 执行命令的名称
codeName = "runAndroidDevice";
// 简介
label = "Run Android on Device";
// 实现具体功能
async baseFn(): Promise<void> {
assert(this.project);
await runAndroid(TargetType.Device, this.project);
}
}
// command.ts
// 注册命令
public register = (() => {
let isCalled = false;
return (entryPointHandler: EntryPointHandler) => {
this.entryPointHandler = entryPointHandler;
assert(!isCalled, "Command can only be registered once");
isCalled = true;
return vscode.commands.registerCommand(
`reactNative.${this.codeName}`,
this.createHandler(),
);
};
})();
// 命令具体执行的绑定以及增加类似生命周期的实现
protected createHandler(fn = this.baseFn.bind(this)): (...args: ArgT) => Promise<void> {
return async (...args: ArgT): Promise<void> => {
assert(this.entryPointHandler, "this.entryPointHandler is not defined");
const resultFn = async (generator: TelemetryGenerator) => {
try {
await this.onBeforeExecute(...args);
await fn.bind(this)(...args);
} catch (error) {
switch (error.errorCode) {
case InternalErrorCode.CommandCanceled:
generator.addError(error);
return;
default:
throw error;
}
}
};
OutputChannelLogger.getMainChannel().debug(`Run command: ${this.codeName}`);
await this.entryPointHandler.runFunctionWExtProps(
`commandPalette.${this.codeName}`,
{
platform: {
value: this.platform,
isPii: false,
},
},
this.error,
resultFn.bind(this),
);
};
}
实际开发
一般像我在开发vscode插件的时候,几乎用不到这么复杂的架构,但实际为了代码的整洁性,还是会加一点简单的架构,也是方便后期的维护和扩展。
自己早期在开发类似工具方面代码的时候,都比较随意,把代码写的跟流水账似的,在一个方法里,写了很多具体的功能实现代码,即便是抽离,也是很难被其他代码复用,最近我也在不断思考,是否有更加合理的方式来解决这样的问题,所以也看了很多不同的开源仓库的代码,包含java,typescript,c++,希望能够找到并且总结出符合现代化开发的思路。