布景

鉴于当时前端技术人员比较严重,防止前端开发人员糟蹋在毫无意义的增修正查上面,思考着能否依据vscode指令生成一套增修正查模版,后端人员能够经过简单指令就能够生成代码模版。首要想到的是vscode代码片段功用,vscode代码片段能够依据指令生成代码片段,但有一个缺陷是不能依据代码片段中的引进文件去创立文件,这关于有些后端人员来说是灾难性的损伤。因此想到能够借助vscode插件来生成代码片段并创立代码片段中引进的文件和办法,大大削减后端人员上手开发的难度,经过修正对应的api就能够轻松完结前端增修正查页面的开发。
tips:项目后台模版是依据umi脚手架生成的后台管理项目,生成的代码片段是依据antd-pro,所以项目中有必要引进antd-pro依赖。

完结如下功用

  1. 依据指令生成代码片段;
  2. 依据代码片段中引进的文件去创立方针文件并给文件中写入相应办法;

前期准备

  1. 先装置vscode官方供给的插件脚手架
    npm install -g yo generator-code
  1. 运转 yo code来生成项目结构,挑选生成类型;
    yo code

    _-----_ ╭──────────────────────────╮

    | | │ Welcome to the Visual │

    |--(o)--| │ Studio Code Extension │

    `--------- │ generator! │

    ( _U`_ ) ╰──────────────────────────╯

    /___A___\ /

    | ~ |

    __'.___.'__

     ` |  Y `

    ? What type of extension do you want to create? (Use arrow keys)

    ❯ New Extension (TypeScript)

    New Extension (JavaScript)

    New Color Theme

    New Language Support

    New Code Snippets

    New Keymap

    New Extension Pack

    New Language Pack (Localization)

    New Web Extension (TypeScript)

    New Notebook Renderer (TypeScript)

最终生成的项目结构如下

    ├── .vscode

    ├── out

    ├── src

    ├── test

    ├── extension.ts

    ├── .eslintrc.json

    ├── .gitignore

    ├── .vscodeignore

    ├── CHANGELOG.md

    ├── package-lock.json

    ├── package.json

    ├── README.md

    ├── tsconfig.json

    ├── vsc-extension-quickstart.md
  1. 运转vscode插件:按下F5即可进入调试阶段,会打开一个新的vscode窗口,Ctrl+Shift+P 打开指令行,输入Hello World ,右下角弹出提示框 Hello World from jikaibo !

代码剖析

经过阅览官方vscode开发文档,了解到extension.ts是脚手架的入口文件。

    // extension.ts
    import * as vscode from 'vscode';
    export function activate(context: vscode.ExtensionContext) {
        console.log('Congratulations, your extension "react-antd" is now active!');
        // registerCommand 注册指令api
        let disposable = vscode.commands.registerCommand('react-antd.helloWorld', () => {
            vscode.window.showInformationMessage('Hello World from jikaibo!');=
        });
        // 一切注册类的 API 履行后都需求将返回成果放到 `context.subscriptions`中。
        context.subscriptions.push(disposable);
    }
    export function deactivate() {}

其间 vscode.commands.registerCommand 用于注册指令,react-antd.helloWorld 为指令id,在package.json文件中现已装备了该指令如下:

    // package.json
    {
        "activationEvents": [ // 激活事件
            "onCommand:react-antd.helloWorld"
        ],
        "main": "./dist/extension.js",
        "contributes": {
            "commands": [
                {
                "command": "react-antd.helloWorld",
                "title": "Hello World"
                }
            ]
        },
        "keybindings": [ // 按键
            {
            "command": "react-antd.helloWorld", // 指定快捷键履行的操作;
            "key": "ctrl+f10", // windows下快捷键
            "mac": "cmd+f10", // mac下快捷键
            "when": "editorTextFocus" // 快捷键何时收效
            }
        ],
        "menus": { // 菜单
            "editor/context": [
                {
                    "when": "editorFocus", // 菜单呈现机遇
                    "command": "react-antd.helloWorld", // 界说菜单被点击后要履行什么操作;
                    "group": "navigation" // 界说菜单分组
                }
            ]
        }
    }

注意:activationEvents中的 "onCommand:react-antd.helloWorld" 中的 react-antd为插件id应该与extensions.js中的注册指令匹配,onCommand为监听类型,值能够为onUri、onLanguage 等。contributes 特点用于装备显现指令方法,还能够是按键或许菜单形式。 第二个参数为回调函数,当指令触发时,弹出提示框。
以上便是脚手架生成的项目供给的功用,趁便也是熟悉下基本的api,接下来依据要求完结咱们自己的功用。

功用开发

  1. 依据代码提示功用注入代码文本,具体中心代码如下:
    // extension.ts
    import * as vscode from 'vscode';
    import * as fs from 'fs';
    import autoCompletionItemProvider from './autoCompletionItemProvider';
    import createWriteFile from './createWriteFile';
    export function activate(context: vscode.ExtensionContext) {
        console.log('祝贺,您的扩展“vscode-plugin-antd-pro”已被激活!');
        const commandId ="vscode-extension.quick-antd-pro"; // 界说指令
        const commandHandler = (
            editor: vscode.TextEditor,
            edit: vscode.TextEditorEdit,
            position: vscode.Position,
            str: string,
        ) => {
            // 获取文件途径
            // const filePath = path.normalize(editor.document.uri.path)
            // const code = path.basename(path.dirname(filePath))
            const lineText = editor.document.lineAt(position.line).text;
            const startSpaces = lineText.length - lineText.trimStart().length;
            // 删去当时行内容
            edit.delete(
                new vscode.Range(
                position.with(undefined, startSpaces),
                position.with(undefined, lineText.length)
            );
            // 插入代码提示片段
            edit.insert(position.with(undefined, startSpaces), str);
            // 创立写入文件内容
            createWriteFile();
            return Promise.resolve([]);
        };
        context.subscriptions.push(
            // 注册编辑器指令,仅在编辑器被激活时调用才收效,访问到当时活动编辑器`textEditor`
            vscode.commands.registerTextEditorCommand(commandId, commandHandler)
        );
        // 获取当时工作区文件信息
        let {textDocuments,name} = vscode.workspace;
        if(textDocuments.length === 0) {
            return;
        }
        const {fileName} = textDocuments[textDocuments.length -1];
        const fileNameArr = fileName.split('/');
        // 当时文件目录
        const currnetFolder = fileNameArr[fileNameArr.length - 2];
        const disposable = vscode.languages.registerCompletionItemProvider(
            ["javascript", "javascriptreact", "typescript", "typescriptreact", "html"],
            new autoCompletionItemProvider(currnetFolder), // 当时的文件目录传入结构函数中
            '.' // 触发字符
        );
        // context.subscriptions.push(disposable);
    }
    export function deactivate() {}

registerCompletionItemProvider 承受三个参数:

  • 第一个参数为在何种文件格局下触发此函数 类型为string 或许 array

  • 第二个参数为对象合集,包含两个函数

    • provideCompletionItems 键入一个字符时触发,在此阶段供给代码文本。承受vscode传入的当时文档(document)和当时光标方位(position),经过这两个参数获取当时行输入的文本。
    • resolveCompletionItem 选中此代码时的触发动作;
    • 第三个参数为剩下参数,为该函数触发的字符,英文默认触发,数字 . / 等字符需求手动触发。

registerTextEditorCommand 为注册文本编辑器指令,仅在编辑器被激活时调用才收效,此外,这个指令能够访问到当时活动编辑器 textEditor,其承受两个参数

  • 第一个参数为当时指令ID,类型为string;
  • 第二个参数为回调函数
  1. autoCompletionItemProvider 代码如下:
    import * as vscode from 'vscode';
    import createIndexFragment from './fragments/indexFragment';
    class AutoCompletionItemProvider implements vscode.CompletionItemProvider {
        private position?: vscode.Position;
        private str = '';
        constructor(props:any) {
            // 赋值代码片段
            this.str = createIndexFragment(props);
        }
        // 供给代码提示的候选项
        public provideCompletionItems(
            document: vscode.TextDocument,
            position: vscode.Position
        ) {
            this.position = position;
            // 用于提示正在键入的文件内容
            const snippetCompletion = new vscode.CompletionItem(
                'react-antd-pro',
                 vscode.CompletionItemKind.Operator
            );
            // 文档注释
            snippetCompletion.documentation = new vscode.MarkdownString('quick antd-pro');
            snippetCompletion.detail = 'selected it'; // 附加信息
            snippetCompletion.filterText = ''; // 刷选项过滤字符串
            return [snippetCompletion];
        }
        // 光标选中当时自动补全item时触发动作
        public resolveCompletionItem(item: vscode.CompletionItem) {
            const label = item.label;
            if (this.position && typeof label === "string") {
                // 选中代码,发起vscode-extension.quick-antd-pro指令
                item.command = {
                    command: "vscode-extension.quick-antd-pro",
                    title: "refactor",
                    // 经过arguments传递参数
                    arguments: [this.position.translate(0, label.length + 1), this.str], // 这儿能够传递参数给该指令
            };
        }
            return item;
        }
   }

大致流程为用户键入字符时,触发 provideCompletionItems 函数,代码提示悬浮框插入代码提示,用户挑选此提示,再触发 resolveCompletionItem 函数,指令操作删去文本,插入自界说文本内容。

  1. createWriteFile 代码如下:
    import * as vscode from 'vscode';
    import * as fs from 'fs';
    import modalFragment from './fragments/componentFragmentModal';
    import apiFragment from './fragments/apiFragment';
    import createServicesFragment from './fragments/servicesFragment';
    const createWriteFile = () => {
        let {textDocuments,name} = vscode.workspace;
        if(textDocuments.length === 0) {
            return;
        }
        const {fileName} = textDocuments[textDocuments.length -1];
        const fileNameArr = fileName.split('/');
        // 创立文件index.less文件
        const createFileName = `${fileNameArr.filter(item => item !== 'index.tsx').join('/')}/index.less`;
        const currnetFolder = fileNameArr[fileNameArr.length - 2];
        if (fs.existsSync(createFileName)) {
            vscode.window.showErrorMessage(`文件${createFileName}已存在`);
        }
        fs.writeFile(createFileName, '', () => {
            vscode.window.showTextDocument(vscode.Uri.file(createFileName), {
                viewColumn: vscode.ViewColumn.Two, // 显现在第二个编辑器窗口
         });
    });
    // 异步创立文件夹;同步用 mkdirSync api
    const currentDirName = `${fileNameArr.filter(item => item !== 'index.tsx').join('/')}/components`;
    fs.mkdir(currentDirName,() => {
        fs.writeFile(`${currentDirName}/RuleModal.tsx`, modalFragment, (err) => {
            if(err) {
                vscode.window.showErrorMessage('文件写入失利');
                return;
            }
             vscode.window.showTextDocument(vscode.Uri.file(`${currentDirName}/RuleModal.tsx`), {
                viewColumn: vscode.ViewColumn.Two, // 显现在三个编辑器窗口
            });
        });
    });
    const basePath = fileName.slice(0, fileName.indexOf('/src'));
    // 判别文件途径是否存在 src下是否存在api、serveces
    if (fs.existsSync(`${basePath}/src/api`) && fs.existsSync(`${basePath}/src/services`) ) {
            // vscode.window.showErrorMessage(`文件已存在`);
            // return;
            // 写入api下面的接口文件
            fs.writeFile(`${basePath}/src/api/${currnetFolder}.ts`, apiFragment, (err) => {
                if(err) {
                    vscode.window.showErrorMessage('文件写入失利');
                    return;
                }
                     vscode.window.showTextDocument(vscode.Uri.file(`${basePath}/src/api/${currnetFolder}.ts`), {
                     viewColumn: vscode.ViewColumn.Three, // 显现在四个编辑器窗口
                });
            });
            // 写入serveces下的服务文件
            fs.writeFile(`${basePath}/src/services/${currnetFolder}.ts`, createServicesFragment(currnetFolder), (err) => {
                if(err) {
                    vscode.window.showErrorMessage('文件写入失利');
                    return;
                }
                vscode.window.showTextDocument(vscode.Uri.file(`${basePath}/src/api/${currnetFolder}.ts`), {
                    viewColumn: vscode.ViewColumn.Four, // 显现在第五个编辑器窗口
                });
            });
        }
    };
    export default createWriteFile;

该办法首要运用的fs来创立文件并写入文件内容,上文代码片段创立完结,会在该模块下同步创立相应的index.less文件。并在src/api文件下创立与模块同名的api文件,此刻后端开发人员只需修正里边对应的api即可。在src/services下创立与模块同名的services服务文件,其间

  • fs.existsSync 判别文件是否存在。承受一个参数,类型为string,表明文件途径。返回为true 或许false
  • fs.writeFile 创立一个文件并写入内容。其承受三个参数。第一个参数为文件途径,第二个参数为文件内容,类型为string。第三个参数为回调函数,在里边能够做异常判别。
  • fs.mkdir 为异步创立文件夹与之对应的是同步创立文件夹 fs.mkdirSync ,其承受两个参数。第一个参数为文件夹称号,第二个参数为创立之后的回调函数。 至此该插件的中心逻辑现已完结了。全体的大致流程为用户键入字符时,触发 provideCompletionItems 函数,代码提示悬浮框插入代码提示,用户挑选此提示,再触发 resolveCompletionItem 函数,该函数发起一个指令,指令操作删去文本,插入自界说文本内容,创立首页需求引进的文件及写入文件内容。

发布

接下来便是发布了,发布之前需求注意下:package.json中的name取名有必要在使用商场里边唯一,不能有重名,不然会发布失利,生成的包名即为name称号。

  • 先装置本地打包东西vsce

        // 全局装置vsce东西
        npm i vsce -g
    

    然后在项目根文件下履行

        vsce package
    

    在打包时会呈现提示信息,例如修正README.md, 增加LICENSE。依据提示操作即可。之后会生成vsix文件,之后就能够经过vscode的扩展右上角挑选 install from vsix 来装置,一些涉及到公司的机密插件能够经过这种方法来装置,而不需求发不到使用商场。

    从零开发一个vscode插件

  • 发布到使用商场

    • 注册账号获取Token
      Visual Studio Code 运用 Azure DevOps 作为其 Marketplace 服务,所以需求登录 Azure,如果没有安排的话会创立提示创立安排。点击下图按钮创立一个新Token

      从零开发一个vscode插件

      从零开发一个vscode插件
      依据提示填写相关信息,点击create就可完结token的创立。

    • 创立发布者:经过网页版端 marketplace.visualstudio.com/manage

      从零开发一个vscode插件
      填写一些基本信息,这儿需求注意的是 name 的称号有必要与package.json中的 publisher 保持一致,其间verified domain的时分有必要在本地dns服务器装备中增加其供给的相关信息,点击保存即可成功创立发布者。之后履行指令:

          vsce login <publisher name>
      

      publisher name 为package.json中的name,然后依据要求输入Personal Access Token,即为刚创立的token,复制过来即可。成功之后会提示 The Personal Access Token verification succeeded for the publisher 'xxxx'

    • 发布使用:

      • 方法一:履行指令

            vsce publish
        

        运转成功之后提示 Published jikaibo.antd-pro-pluginv1.0.0. ,阐明发布完毕。此刻能够在使用商场里边查看现已发布的插件Extensions for Visual Studio Code.

      • 方法二:经过 Extensions for Visual Studio Code.

        从零开发一个vscode插件

        点击New extension 挑选从Visual Studio Code 上传本地打包的vsix文件来进行发布。

运用办法

  1. 在pages下面新建一个事务文件夹,称号如demo,然后在此文件下新建一个index.tsx文件;
  2. 在index.tsx文件里边输入 react-antd-pro.指令,依据弹出的履行提示挑选生成代码模版;
  3. 成功之后会在index.tsx文件中创立相关事务代码,并在src/api下创立同名(demo)api文件,在src/services下面创立同名(demo)request服务文件

总结

到此一个vscode插件从开发到发布上线现已完结了,全体过程的体验还算ok。遇到的问题还是注册Azure用户账号的时分,重复验证不是机器人,比较emo,还有创立发布者的时分需求验证域名信息,需求依据生成的地址装备本地dns服务,算是比较坑的存在吧。本插件首要完结的功用依据代码提示生成代码片段,并创立相关文件、写入文件内容。其有用的vscode api仅仅冰山一角,有机会能够深化到 vscode 插件开发当中去,Enenen, 可是不建议造轮子(究竟有很多大牛现已帮咱们完结了功用完善的插件了)。

从零开发一个vscode插件

相关链接

  • Vscode 插件官方文档
  • Vscode 使用商铺
  • Vscode 官方插件

参考文章

  • /post/711909…
  • Vscode 插件开发攻略