VSCode插件开发

3,006 阅读7分钟

简介

关于vscode

VSCode是微软出的一款轻量级代码编辑器,免费而且功能强大,以功能强大、提示友好、不错的性能和颜值俘获了大量开发者的青睐,对JavaScript和NodeJS的支持非常好,自带很多功能,例如代码格式化,代码智能提示补全等。再强大的IDE也不会面面俱到,这样只会让IDE太臃肿,vscode的很多强大功能都是基于插件实现的,IDE只提供一个最基本的框子和最基本功能,由插件来丰富和扩展它的功能。

什么是vscode插件

因为vscode本身都是用浏览器实现的,所以其插件不用说肯定也是基于HTML+JS等前端技术实现,从形式上看就是一个类似于npm包的vsix文件,只不过按照一些特殊规范来实现一些特殊功能。

如何开发一个vscode插件

1.安装官方脚手架

npm install -g yo generator-code

选择配置

生成文件目录为

其中extension.js是项目的主入口

package.json里面配置项目的命令

2.package.json解释以及执行HelloWorld

我们先看一个例子

{
 "name": "wx-command",
 "displayName": "wx-command",
 "description": "微信小程序文件助手",
 "version": "0.0.2",
 "engines": {
   "vscode": "^1.36.0"
 },
 "categories": [
   "Other"
 ],
 "activationEvents": [
   "onCommand:extension.wxPage",
   "onCommand:extension.wxComponents"
 ],
 "main": "./out/extension.js",
 "contributes": {
   "commands": [
     {
       "command": "extension.wxPage",
       "title": "wx page"
     },
     {
       "command": "extension.wxComponents",
       "title": "wx components"
     }
   ],
   "menus": {
     "explorer/context": [
       {
         "command": "extension.wxPage"
       },
       {
         "command": "extension.wxComponents"
       }
     ]
   },
   "snippets": [
     {
       "language": "vue",
       "path": "./snippets/ts.json"
     }
   ]
 },
 "scripts": {
   "vscode:prepublish": "npm run compile",
   "compile": "tsc -p ./",
   "watch": "tsc -watch -p ./",
   "pretest": "npm run compile",
   "test": "node ./out/test/runTest.js"
 },
 "devDependencies": {
   "@types/glob": "^7.1.1",
   "@types/mocha": "^5.2.6",
   "@types/node": "^10.12.21",
   "@types/vscode": "^1.36.0",
   "glob": "^7.1.4",
   "mocha": "^6.1.4",
   "tslint": "^5.19.0",
   "typescript": "^3.3.1",
   "vscode-test": "^1.0.2"
 }
}

看起来和我们平时的项目没有什么区别,但是多了一个contributes的配置,这个也是我们所有命令的配置入口

结合实现一个helloword来加深下认识

首先在package.json里的contributes进行配置

{
   // 扩展的激活事件
   "activationEvents": [
   	"onCommand:extension.sayHello"
   ],
   // 入口文件
   "main": "./src/extension",
   // 贡献点,vscode插件大部分功能配置都在这里
   "contributes": {
   	"commands": [
   		{
   			"command": "extension.sayHello",
   			"title": "Hello vscode"
   		}
   	]
   }
}

在activationEvents配置我们要激活的事件,(可以直接配置为*激活所有命令)然后contributes-->commands里面自定义命令,sayHello,命令 然后在extensions里实现具体功能

const vscode = require('vscode');

/**
* 插件被激活时触发,所有代码总入口
* @param {*} context 插件上下文
*/
exports.activate = function(context) {
   console.log('恭喜,您的扩展“vscode-plugin-demo”已被激活!');
   // 注册命令
   context.subscriptions.push(vscode.commands.registerCommand('extension.sayHello', function () {
   	vscode.window.showInformationMessage('Hello vscode 弹窗!');
   }));
};

/**
* 插件被释放时触发
*/
exports.deactivate = function() {
   console.log('您的扩展“vscode-plugin-demo”已被释放!')
};

其中activate和deactivate 是插件的两个生命周期名,分别在插件插入和释放时候执行

vscode.commands.registerCommand是注册命令的API,执行后会返回一个Disposable对象,所有注册类的API执行后都需要将返回结果放到context.subscriptions中去。

vscode.window.showInformationMessage是插件的弹窗方法

最终实现效果 调试后,command+shift+p打开vscode命令执行 输入自定义命令Hello vscode-可见执行弹窗

3. 模版文件的自动生成

预期效果,模仿微信开发者工具右键菜单生成文件,并自动填充模版

先看package.json里面的配置

"activationEvents": [
    "onCommand:extension.wxPage",
    "onCommand:extension.wxComponents"
  ],
"contributes": {
    "commands": [
      {
        "command": "extension.wxPage",
        "title": "wx page"
      },
      {
        "command": "extension.wxComponents",
        "title": "wx components"
      }
    ],
    "menus": {
      "explorer/context": [
        {
          "command": "extension.wxPage"
        },
        {
          "command": "extension.wxComponents"
        }
      ]
    },

和刚才一样在contributes里的commands里面进行我们的命令配置

然后在menus配置菜单执行

几个api

  • explorer/context 是key值,定义这个菜单出现在哪里;

    • 资源管理器上下文菜单 - explorer/context
    • 编辑器上下文菜单 - editor/context
    • 编辑标题菜单栏 - editor/title
    • 编辑器标题上下文菜单 - editor/title/context
  • when控制菜单合适出现;

  • command定义菜单被点击后要执行什么操作;

大致思路就是根据不同的命令,writeFileSync生成文件并写入提前设置的模版

然后看下具体实现

const vscode = require("vscode");
const fs = require("fs");
const path = require("path");
const files_1 = require("./files");
const { page, components } = files_1.files("index");
const fileTypes = ["js", "wxss", "wxml", "json"];
function wxCommandHander(type, e) {
    const stat = fs.statSync(e.fsPath);
    const dir = path.normalize(e.fsPath);
    if (stat.isDirectory()) {
        try {
            fileTypes.map((i) => __awaiter(this, void 0, void 0, function* () {
                const data = new Uint8Array(Buffer.from(type === "page" ? page[i] : components[i]));
                fs.writeFileSync(`${dir}/index.${i}`, data);
            }));
            vscode.window.showInformationMessage(`create ${type} success!`);
        }
        catch (error) {
            vscode.window.showErrorMessage("create page files failed");
        }
    }
    else {
        vscode.window.showErrorMessage("please choose a folder");
    }
}
function activate(context) {
    console.log('Congratulations, your extension "wx" is now active!');
    let disposable = vscode.commands.registerCommand("extension.wxPage", (e) => __awaiter(this, void 0, void 0, function* () {
        wxCommandHander("page", e);
    }));
    context.subscriptions.push(disposable);
    disposable = vscode.commands.registerCommand("extension.wxComponents", (e) => {
        wxCommandHander("components", e);
    });
    context.subscriptions.push(disposable);
}
exports.activate = activate;
function deactivate() { }
exports.deactivate = deactivate;

file.js里面就是配置的填充模版

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 *
 * @param name file name
 */ exports.files = function (name) {
    const page = {
        js: `
/**
 * ${name}.js
*/
Page({

  /**
   * 页面的初始数据
   */
  data: {

  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})`,
        wxss: `/**${name}.wxss**/`,
        json: `
/** 
 * ${name}.json
 * */
{
"usingComponents": {}
}`,
        wxml: `<!--${name}.wxml-->
<text>${name}</text>`
    };
    return {
        page,
        components
    };
};
//# sourceMappingURL=files.js.map

执行效果,调试后,在一个文件夹目录下右键执行

4.自动补全

const vscode = require('vscode');
const util = require('./util');
const snippetsList = require('./utilList.js')
/**
 * 自动提示实现,这里模拟一个很简单的操作
 * 当输入 this.dependencies.xxx时自动把package.json中的依赖带出来
 * 当然这个例子没啥实际意义,仅仅是为了演示如何实现功能
 * @param {*} document 
 * @param {*} position 
 * @param {*} token 
 * @param {*} context 
 */
function provideCompletionItems(document, position, token, context) {
    const line = document.lineAt(position);
    const projectPath = util.getProjectPath(document);
    const localDependencies = snippetsList.util
    // 只截取到光标位置为止,防止一些特殊情况
    const lineText = line.text.substring(0, position.character);
    // 简单匹配,只要当前光标前的字符串为`this.dependencies.`都自动带出所有的依赖
    if (/util\.$/g.test(lineText)) {
        let atest = ['test']
        return localDependencies.map(dep => {
            return new vscode.CompletionItem(dep.name, vscode.CompletionItemKind.Field);
        })
    }
}

/**
 * 光标选中当前自动补全item时触发动作,一般情况下无需处理
 * @param {*} item 
 * @param {*} token 
 */
function resolveCompletionItem(item, token) {
    return null;
}

module.exports = function (context) {
    // 注册代码建议提示,只有当按下“.”时才触发
    context.subscriptions.push(vscode.languages.registerCompletionItemProvider('javascript', {
        provideCompletionItems,
        resolveCompletionItem
    }, '.'));
};

其中vscode.languages.registerCompletionItemProvider为新的api,接受三个参数

  • 执行的文件类型
  • 提供提示的函数
  • 触发词

5.代码提示

代码提示其实是利用代码更改vscode的自定义代码片段 使用方式 新建一个vue.json文件

{
  "console.打印": {
    "prefix": "colo",
    "body": [
      "console.log($0)"
    ],
    "description": "console打印"
  },
  "console.打印2": {
    "prefix": "ajax",
    "body": [
      "$.ajax({",
      "    url: '$1',",
      "    method: '${2:POST}',",
      "    datatype: 'json',",
      "    success: data => {",
      "        $3;",
      "    },",
      "    error: err => {",
      "        $4;",
      "    }",
      "})"
    ],
    "description": "ajax模块"
  },
  "大区": {
    "prefix": "adress-s",
    "body": [
      "<el-form-item label='所属大区' :prop='$1'>",
      "    <el-select v-model='$2' clearable placeholder='请选择所属大区'>",
      "        <el-option label='东北大区 :value='$3' />",
      "        <el-option label='华北大区 :value='$4' />",
      "    </el-select>",
      "</el-form-item>"
    ],
    "description": "大区"
  }
}

其中prefix为触发词 body为生成体

"contributes": {
     "snippets": [
            {
                "language": "vue",
                "path": "./snippets/vue.json"
            }
        ],
    },

插件开发完成后的工作

插件开发完成后需要进行打包以及共享

开发完成后,我们有三种方式进行使用

方法一:直接把文件夹发给别人,让别人找到vscode的插件存放目录并放进去,然后重启vscode,一般不推荐;

方法二:打包成vsix插件,然后发送给别人安装,如果你的插件涉及机密不方便发布到应用市场,可以尝试采用这种方式;

方法三:注册开发者账号,发布到官网应用市场,这个发布和npm一样是不需要审核的。

介绍下第二种,本地打包成vsix包,别人使用的时候直接发给他们。然后接收的人在插件里面安装

安装方法

打包时候需要安装打包插件

npm i vsce -g 

打包命令

vsce package

打包的时候可能提醒我们,没有定义开发者,这时候就需要用到我们之前说的packagejson 里面的publisher配置,这个是需要注册的。下面说下注册流程

注册逻辑

要发布到应用市场首先得有应用市场的publisher账号;

而要有发布账号首先得有Azure DevOps组织;

而创建组织之前,首先得创建Azure账号;

创建Azure账号首先得有Microsoft账号;

是不是有点晕,梳理一下:

一个Microsoft账号可以创建多个Azure组织;

一个组织可以创建多个publisher账号;

同时一个组织可以创建多个PAT(Personal Access Token,个人访问令牌);

第一个网址 login.live.com/,注册Microsof…

第二个网址 aka.ms/SignupAzure… ,如注册Azure,注册这个提示输入刚才我们注册的Microsoft账号,按步骤注册

注册成功后生成个人钥匙

注意以下,一定要选择full access,名字时间自己定义

生成token后,注意保存,这个你一旦退出,网站也不会保存,

现在我们有了账号,也有了token 在我们的项目目录下

vsce create-publisher 你的名字(随便)

最后写入你的token

可以通过vsce ls-publishers 查看已经添加的发布者

然后在项目的packagejson里面添加publisher写发布者

再执行

vsce package

在项目目录下就可以生成后缀名为vsix的插件了。