vscode插件开发的优雅姿势

99 阅读2分钟

前言

最近在开发一个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++,希望能够找到并且总结出符合现代化开发的思路。