VsCode插件开发

199 阅读6分钟

1、初衷

在开发RN项目时,经常会新建页面,新建的页面内容如果是手敲的话,比较慢,如果是从其他文件中复制过来还要删除多余的代码;所以开发一个一健生成hooks的RN文件会提升效率

2、插件功能

在编辑目录中新增一个“新建hooks文件”的功能,该功能可自动生成设置好的内容

image.png

3、开始开发

3.1、安装官方脚手架

npm install -g yo generator-code

3.2、安装成功后使用脚手架

yo code

image.png

3.3、安装成功后的目录结构

image.png

其中,最核心的两个文件是package.jsonextension.jspackage.json是整个插件工程的配置文件,extension.js则是工程的入口文件。下面将对这两个文件进行详细的介绍。

4、运行调试

image.png

5、package.json详解

{
    "name": "createhooks", // 插件名称
    "displayName": "createHooks", // 显示在插件市场的名称
    "description": "快速创建一个hooks的RN文件", // 插件描述
    "publisher":"zhuangzi", //发布者
    "version": "0.0.1", //版本号
    "engines": { // 表示插件支持的最低vscode版本
        "vscode": "^1.66.0"
    },
    "categories": [ // 插件应用市场分类,可选值: [Programming Languages, Snippets,Linters,Themes, Debuggers, Formatters, Keymaps, SCM Providers, Other, Extension Packs, Language Packs]
        "Other"
    ],
    "icon": "images/icon.png",// 插件图标,至少128x128像素  images文件需要在根目录下创建
    "activationEvents": [ // 扩展的激活事件数组,可以被哪些事件激活扩展,后文有详细介绍
        "*"
    ],
    "main": "./out/extension.js", // 插件的入口文件
    "contributes": { // 贡献点,整个插件最重要最多的配置项 后文有详细介绍
        "commands": [ // 注册命令 其中‘createHooks.createFile’为标识命令字段
                {
                    "command": "createHooks.createFile",
                    "title": "新建hooks文件"
                }
        ],
        "menus": { 
              "explorer/context": [ //右键菜单
                    {
                      "command": "createHooks.createFile",
                      "group": "1_modification",
                      "when": "explorerResourceIsFolder"
                    }
              ]
        }
    },
    "scripts": {
        "vscode:prepublish": "yarn run compile",
        "compile": "tsc -p ./",
        "watch": "tsc -watch -p ./",
        "pretest": "yarn run compile && yarn run lint",
        "lint": "eslint src --ext ts",
        "test": "node ./out/test/runTest.js"
    },
    "devDependencies": {
        "@types/vscode": "^1.66.0",
        "@types/glob": "^7.2.0",
        "@types/mocha": "^9.1.0",
        "@types/node": "14.x",
        "@typescript-eslint/eslint-plugin": "^5.16.0",
        "@typescript-eslint/parser": "^5.16.0",
        "eslint": "^8.11.0",
        "glob": "^7.2.0",
        "mocha": "^9.2.2",
        "typescript": "^4.5.5",
        "@vscode/test-electron": "^2.1.3"
    }
}

5.1、activationEvents

配置项配置插件的激活数组,即在什么情况下插件会被激活,目前支持以下8种配置:

  • onLanguage: 在打开对应语言文件时
  • onCommand: 在执行对应命令时
  • onDebug: 在 debug 会话开始前
  • onDebugInitialConfigurations: 在初始化 debug 设置前
  • onDebugResolve: 在 debug 设置处理完之前
  • workspaceContains: 在打开一个文件夹后,如果文件夹内包含设置的文件名模式时
  • onFileSystem: 打开的文件或文件夹,是来自于设置的类型或协议时
  • onView: 侧边栏中设置的 id 项目展开时
  • onUri: 在基于 vscode 或 vscode-insiders 协议的 url 打开时
  • onWebviewPanel: 在打开设置的 webview 时
  • *: 在打开 vscode 的时候,如果不是必须一般不建议这么设置

5.2、contributes

整个插件的贡献点,也就是说这个插件有哪些功能。contributes字段可以设置的key也基本显示了vscode插件可以做什么。

  • configuration:通过这个配置项我们可以设置一个属性,这个属性可以在vscodesettings.json中设置,然后在插件工程中可以读取用户设置的这个值,进行相应的逻辑。
  • commands:命令,通过cmd+shift+p进行输入来实现的。
  • menus:通过这个选项我们可以设置右键的菜单
  • keybindings:可以设置快捷键
  • languages:设置语言特点,包括语言的后缀等
  • grammars:可以在这个配置项里设置描述语言的语法文件的路径,vscode可以根据这个语法文件来自动实现语法高亮功能
  • snippets:设置语法片段相关的路径 就是输入一个前缀,会得到一个或多个提示,然后回车带出很多代码。

查看更多配置 code.visualstudio.com/api/referen…

举例

"contributes": {
    // 插件配置项
    "configuration": {
      "type": "object",
      // 配置项标题,会显示在vscode的设置页
      "title": "create-hooks-file",
      "properties": {
        // 这里我随便写了2个设置,配置你的昵称
        "vscodePluginDemo.yourName": {
          "type": "string",
          "default": "guest",
          "description": "zhuangzi" //你的名称
        },
        // 是否在启动时显示提示
        "vscodePluginDemo.showTip": {
          "type": "boolean",
          "default": true,
          "description": "是否在每次启动时显示欢迎提示!"
        }
      }
    },
    // 命令
    "commands": [ 
            {
                "command": "createHooks.createFile",
                "title": "新建hooks文件"
            }
    ],
    "menus": { 
            // 编辑器右键菜单
          "editor/context": [{
              "when": "editorFocus", // 表示只有编辑器具有焦点时才会在菜单中出现
              "command": "extension.sayHello",
              "group": "navigation@6" // navigation是一个永远置顶的分组,后面的@6是人工进行组内排序
                  //   `navigation`:导航组在所有情况下都排在第一位
                 // `1_modification`:该组紧随其后,包含修改代码的命令
               // `9_cutcopypaste`:带有基本编辑命令的倒数第二个默认组
                // `z_commands`:带有打开命令面板的条目的最后一个默认组
            },
            {
              "when": "editorFocus",
              "command": "extension.demo.getCurrentFilePath",
              "group": "navigation@5"
            },
            {
              "when": "editorFocus && resourceLangId == javascript", // 只有编辑器具有焦点,并且打开的是JS文件才会出现
              "command": "extension.demo.testMenuShow",
              "group": "z_commands"
            },
            {
              "command": "extension.demo.openWebview",
              "group": "navigation"
            }
          ],
          
           // 编辑器右上角图标,不配置图片就显示文字
          "editor/title": [{
            "when": "editorFocus && resourceLangId == javascript",
            "command": "extension.demo.testMenuShow",
            "group": "navigation"
          }],
          
          // 编辑器标题右键菜单
          "editor/title/context": [{
            "when": "resourceLangId == javascript",
            "command": "extension.demo.testMenuShow",
            "group": "navigation"
          }],

          // 资源管理器右键菜单
          "explorer/context": [{
              "command": "extension.demo.getCurrentFilePath",
              "group": "navigation"
            },
            {
              "command": "extension.demo.openWebview",
              "group": "navigation"
            }
      ]
    },
    "keybindings": [ // 按键绑定
        {
            "command": "extension.helloWorld",
            "key": "ctrl+f10",
            "mac": "cmd+f10",
            "when": "editorTextFocus"
        }
    ],
    // 代码片段
    "snippets": [{
        "language": "javascript",
        "path": "./snippets/javascript.json"
      },
      {
        "language": "html",
        "path": "./snippets/html.json"
      }
    ],

5.3、代码片段

想要在vscode插件中实现snippets的功能,首先要在package.jsoncontributes配置项中配置代码提示文件的文件路径:

"snippets": [
    {
        "language": "typescriptreact", // javascript typescript javascriptreact
        "path": "./snippets.json"
        }
    ]

这里language设置了snippets作用于何种语言,path设置了服务于snippets的文件的路径。
再看一下在snippets.json文件中:

{
    "View组件": {
        "prefix": "View",
        "body": [
            "<View>",
            "${1}",
            "</VIew>"
        ],
        "description": "View组件"
    }
}
  • "View组件": snippet的名称
  • "prefix": 前缀,即输入什么可以出现snippets的提示
  • "body": 按回车后出现的一大段代码,是一个数组,数组里面是字符串,每个字符串代表一行代码,${1}表示第一个光标的位置,同样,${2}表示第二个光标的位置
  • "description": 对于这个snippet的描述,当我们选中这个snipets提示时,描述会出现在后面。 现在,我们运行插件,并保证插件被激活,在规定的语言下,输入View:

5796542-ad8bbbdabe352a54.webp

6、extension

import * as vscode from 'vscode';
import genFile from './getFiles';
export function activate(context: vscode.ExtensionContext) {
    // 当createHooks.createFile命令触发时 弹出输入文件的名的输入框并检查文件名是否已存在
    let createdirindexCommand = vscode.commands.registerCommand('createHooks.createFile', async(uri: vscode.Uri) => {
            const fileName = await vscode.window.showInputBox({prompt: '文件名称(不需要输入后缀名)', placeHolder: '输入文件名称'});
            const dirPath = uri.fsPath;
            genFile(dirPath, fileName);
    });

    context.subscriptions.push(createdirindexCommand);
}

export function deactivate() {}

genFile.ts

import { readdir, writeFile } from 'fs';
import * as vscode from 'vscode';
import { join } from 'path';
import { promisify } from 'util';

const indexFileExt = '.tsx';

const genFile = async(dir: string,fileName:string | undefined) =>{
  if(!fileName || fileName===''){
    vscode.window.showErrorMessage('文件名不能为空');
    return;
  }
  // 得到目录下所有文件名集合
  const result = await promisify(readdir)(dir); 
  if(result.indexOf(fileName+'.tsx') !== -1){
    vscode.window.showErrorMessage('文件名已存在');
    return;
  }
  const content=`
import React, { useState } from 'react';
import { View, StyleSheet, Image, TouchableOpacity } from 'react-native';
import { DeprecatedNavigator, TYText, Utils } from 'tuya-panel-kit';
import String from '@i18n';
import Res from '@res';
import { useIoTUIValue } from '@models';

const { convertX: cx } = Utils.RatioUtils;
interface MainProps {
  navigator: DeprecatedNavigator;
}

const Pages: React.FC<MainProps> = () => {
  const fontColor = useIoTUIValue('fontColor');
  const background = useIoTUIValue('background');

  return <View style={styles.main} />;
};

const styles = StyleSheet.create({
  main: {
    flex: 1,
    alignItems: 'center',
  },
});
export default Pages;
  `;

  // 写入文件夹下
  await promisify(writeFile)(join(dir, fileName + indexFileExt), content);
};
export default genFile;

7、打包

安装vsce(Visual Studio Code Extension)
npm install -g vsce

打包(首次打包需要修改readme.md文件内容,否则打包不成功)
vsce package

打包后在项目目录下会生成一个vsix包,可通过添加扩展插件导入使用

image.png

image.png

8、发布

  • 1.在网站https://dev.azure.com/vscode获取一个access token,这个token用来创建一个publisher

  • 2.创建publisher
    vscr create-publisher (publisher name)

  • 3.登入一个publisher
    vscde login (publisher name)

  • 4.发布
    vsce publish

9、开发过程中遇到的问题

1.首次打包的时候需要修改下README.md文件内容,否则打包会失败

2.设置了插件icon打包的时候路径报错了————需要将存放图片的文件夹放在项目的根目录下,然后用绝对路径引入

3.设置了snippets代码片段不生效————configuration中的配置导致的,建议去掉configuration中的配置,一般也不会用到这个