vscode插件开发实践

初次尝试了一下vscode插件开发,写了一个前端工具箱作为日常方便使用,记录一下

image

在vscode插件里面搜索:前端工具箱。或打开该网址安装即可 https://marketplace.visualstudio.com/items?itemName=poetries.fe-tools

搭建开发环境

我们先准备开发环境。我使用的操作系统:mac,首先确保安装了VS Code、Node.js 和 Git:

code -v
node -v
npm -v
git --version

https://code.visualstudio.com/api/get-started/your-first-extension

npm install -g yo generator-code

使用yo code指令初始化VSCode插件项目,然后需要回答一些简单的配置问题

yo code

# What type of extension do you want to create? 
# 创建那一种类型的扩展?

# What's the name of your extension?
# 扩展的名称?

# What's the identifier of your extension?
# 扩展的标示?

# What's the description of your extension?
# 扩展的描述

# Initialize a git repository? 
# 初始化git仓库

# Which package manager to use? 
# 使用那一种包管理器

运行插件

使用 VS Code 打开项目,在编辑器内部,按F5,编译并打开一个“扩展开发宿主机”窗口运行调试插件。为了叙述方便,把新打开的窗口称为运行窗口,旧窗口称为编辑窗口。在新窗口的命令面板(Ctrl+Shift+P) 运行 Hello World 命令。看到右下角的 Hello World 通知信息了吗?恭喜你已经运行了一个自己编写的插件!

image

调试插件

使用 VS Code 调试扩展插件很容易。这里演示一下如何设置断点。在编辑窗口打开 extension.js 文件, 点击编辑器行号左侧的边栏设置断点。在运行窗口 命令面板输入 Hello World 命令运行插件,命中断点。

image

调试webview

按F5打开调试模式,在webview页面,按command+shift+p选择open webview

image

image

项目解析

接下来我们来深入研究一下 helloworld 插件。 helloworld 的功能很简单,就是用户可以在命令面板执行 Hello World 命令,输出 Hello World 信息

从实现的角度来看,helloworld 插件做了三件事:

  • 注册激活事件 onCommand:extension.helloWorld:插件在extension.helloWorld 触发时被激活。
  • 注册贡献点 contributes.commands:extension.helloWorld:在命令面板中使能 hello world 命令,并将其绑定到 extension.helloworld
  • 调用 VS Code API commands.registerCommand 给注册的命令 extension.helloWorld绑定处理函数。

基本概念

简单解释一下上面提到的三个概念:激活事件、贡献点和接口:

  • 激活事件,译自 Activation Events,在配置清单 package.json 中静态声明,其实就是 JSON 数组 activationEvents 的值。当激活事件发生时,声明的扩展将被激活。以下是目前所有可用激活事件:onLanguageonCommandonDebug:onDebugInitialConfigurationsonDebugResolveworkspaceContainsonFileSystemonViewonUrionWebviewPanel、*;
  • 贡献点,译自 Contribution Points,在配置清单 package.json 中静态声明,贡献点其实就是VS Code 的可以扩展的功能点。以下是目前所有可用的贡献点:configurationconfigurationDefaultscommandsmenuskeybindingslanguagesdebuggersbreakpointsgrammarsthemessnippetsjsonValidationviewsviewsContainersproblemMatchersproblemPatterns taskDefinitionscolorstypescriptServerPluginsresourceLabelFormatterscontributes.configur
  • VS Code 接口:可以在扩展代码中调用的一组 JavaScript API。链接中列举了所有可用的API,熟悉基本的,其他的用到的时候按需查找就行了。

一般来说,插件都会使用到这三个概念:激活事件贡献点VS Code API。接下来我们分析一下 HelloWorld 示例的源代码,看看它是如何使用这些概念的。

JavaScript 插件目录结构

image

VS Code 插件的目录结构很简单,根据命名大概就能知道作用。不同项目类型目录结构可能会有很大不同。对于 JavaScript 类型的项目来说,最重要的就是package.jsonextension.js

清单文件:package.json

每个 VS Code 插件都必须有一个用来描述插件的清单文件 package.json。VS Code 的清单文件是声明式的 JSON 格式,用于声明插件名(name)、插件展示名( displayName )、描述信息(description)、版本( version )、引擎( engines )、类别( categories )、依赖( devDependencies )、脚本 (scripts)、贡献点( contributes )、入口文件( main )、激活事件( activationEvents )等。

下面是 helloworld 插件的清单文件内容,为了便于理解,我加了一些注释。

{
    "name": "helloworld", //插件名
    "displayName": "helloworld", // 插件市场显示的插件名,支持中文
    "description": "demo", // 插件市场显示的描述信息
    "version": "0.0.1", // 版本号
    // 最低支持的 VS Code 版本
    "engines": {
        "vscode": "^1.38.0"
    },
    // 插件市场分类
    "categories": [
        "Other"
    ],
    // 激活事件
    "activationEvents": [
        "onCommand:extension.helloWorld"
    ],
    "main": "./extension.js", // 指定入口文件
    // 贡献点
    "contributes": {
        "commands": [{
            "command": "extension.helloWorld",
            "title": "Hello World"
        }]
    },
    // 脚本
    "scripts": {
        "test": "node ./test/runTest.js"
    },
    // 依赖,包含版本信息
    "devDependencies": {
        "@types/glob": "^7.1.1",
        "@types/mocha": "^5.2.6",
        "@types/node": "^10.12.21",
        "@types/vscode": "^1.38.0",
        "eslint": "^5.13.0",
        "glob": "^7.1.4",
        "mocha": "^6.1.4",
        "typescript": "^3.3.1",
        "vscode-test": "^1.2.0"
    }
}

插件入口文件:extension.js

插件的入口文件是在清单文件中指定的,如果项目比较大,源文件比较多,也可以统一放在 src 目录里。

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
/* 导入 "vscode" 模块,这个模块包含 VS Code 的扩展接口。 */ 
const vscode = require('vscode');

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
// 第一次执行命令时,插件会被激活;插件被激活时,这个函数会被调用
// 必须在入口中实现这个函数。

/**
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    /* 输出诊断日志到控制台 */
    console.log('Congratulations, your extension "helloworld" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with  registerCommand
    // The commandId parameter must match the command field in package.json
    /* 
     * 这个命令 extension.helloWorld 已经在 package.json 文件中定义了。
     * 现在我们使用 registerCommand 接口给这个命令绑定实现。
     */
    let disposable = vscode.commands.registerCommand('extension.helloWorld', function () {
        // The code you place here will be executed every time your command is executed
        /* 每一次执行命令,这儿的代码都会执行 */

        // Display a message box to the user
        vscode.window.showInformationMessage('Hello World!');
    });

    context.subscriptions.push(disposable);
}
exports.activate = activate;

// this method is called when your extension is deactivated
function deactivate() {}

module.exports = {
    activate,
    deactivate
}

一个插件必须在其主模块实现并导出 activate()deactivate() 函数。

  • activate() 函数 初始化插件。当任何指定的激活事件发生时,VS Code 会调用并且只调用它一次。
  • deactivate() 函数 清理插件。如果清理过程是异步的, deactivate() 函数必须返回一个 Promise 对象。如果清理运行同步,则 deactivate() 函数返回 undefined

Webview

插件可以分为多种,比如主题样式类型的插件,图标插件,语言支持类型的插件。Webview类型的插件只是Vscode插件的一个大类。大致的实现大家可以参考文档,文档的示例使用的是html字符串,但这不适合复杂的Webview的开发。在GameNews这个插件中,模版部分,我使用了vue以及pug。

// 静态资源的目录。绝对路径,并且使用了vscode-resource协议
// vscode-resource:/Users/Desktop/game-news/views
const webviewDir = path.join(context.extensionPath, 'views');

// 创建一个Webview的面板
const panel = vscode.window.createWebviewPanel(
    viewType,
    title,
    vscode.ViewColumn.One,
    {
        enableScripts: true, // 允许运行js脚本,默认是关闭的
        retainContextWhenHidden: true, // webview不可见时,脚本就会被挂起
        // 指定允许加载的本地资源的根目录
        localResourceRoots: [vscode.Uri.file(webviewDir)]
    }
);

// 模版文件
const tpl = path.join(webviewDir, 'index.pug');

// 通过pug渲染模版文件,到webview上
panel.webview.html = pug.renderFile(tpl, options);

本地资源的使用

Webview中,我们会需要使用本地的css,js文件。虽然可以使用行间js或者行间样式,但是总归不太好。使用本地文件,就会涉及的静态文件路径的问题,在VScode中,我们需要使用绝对路径。并且是vscode-resource协议的路径。

const webviewDir = path.join(context.extensionPath, 'views');
// 静态资源的绝对目录
let URI = vscode.Uri.file(path.join(webviewDir, 'js', 'vue.js'))
// 使用vscode-resource协议头
// 然后这个URL就可以使用在我们的webview的模版中了
URI = URI.with({ scheme: 'vscode-resource' });

Webview与插件通信

Webview相当于一个网页,而网页是无法调用一些本地功能的。但是插件本身是运行在node环境的,而已我们可以通过插件实现一些在网页中无法完成的功能。Webview如果通知插件呢?这涉及到了Webview于网页的通信机制。

下面是GameNews插件的例子,我通过事件将游戏新闻的url,发送给插件。插件会调用系统的命令,使用本地的浏览器打开url。

// webview

// webview中,一个内置的全局api
const vscode = acquireVsCodeApi()

vscode.postMessage({
    command: 'preview',
    text: url
})
// 插件
panel.webview.onDidReceiveMessage(message => {
    switch (message.command) {
        case 'preview':
            // 打开浏览器
            open(message.text);
            return;
    }
}, undefined, context.subscriptions);

更多参考文档:https://code.visualstudio.com/api/extension-guides/webview

打包、发布和升级

如何让别人也能使用自己开发的插件呢?这和移动应用开发一样,有两种方式:

  • 把它发布到 VS Code 插件市场,这样其他人就可以找到、下载和使用你的插件。
  • 或者,可以将插件打包为可安装的vsix格式,并与其他用户共享。

vsce,简写自 Visual Studio Code Extensions, 是用于打包、发布和管理 VS Code 插件的命令行工具

先安装 Node.js ,然后运行 npm install -g vsce 安装 vsce。在插件的根目录下运行 vsce package 打包插件,运行 vsce publish 发布插件。

npm install -g vsce
vsce create-publisher poetry # 这一步先创建一个发布账号,需要用到token,看下面步骤获取token
vsce package #打包插件 .vsix 格式
vsce publish #发布到 MarketPlace

发布插件到 VS Code 插件市场,需要注册开发者账号。有关如何发布插件的内容很简单,可以参考官方文档这一部分的内容:https://code.visualstudio.com/api/working-with-extensions/publishing-extension

  • 在Visual Studio Team Services 创建一个账号

  • 根据账号的名字访问主页,例如我的名字是bingou-ms,主页链接就是

  • 创建Personal Access Token

image

image

需要将Accounts设置为All accessible accounts

image

那么如何升级已经发布到插件市场的插件呢?修改/增加版本号,然后再执行 vsce publish 即可。

安装vsix文件

可以直接安装vsce package的vsix文件,方便在本地进行调试。

code --install-extension vsix文件名

参考文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容