一. 背景
入职公司三个月,熟悉新的业务、新的平台、新的技术栈以后。发现自己负责的算法A平台、业务B后台等管理系统的需求开发过程中,每个人一直在重复着相同的工作。
- 拷贝基础UI组件示例;
- 修改组件属性,每次都需打开浏览器,查看文档;
- 基础GET,POST请求模板;
- 拼接Filter + Table + EditModal管理页面;
- git add、git commit、git pull、git push等git工作流;
- 打开C部署系统,部署页面;
- 每周五下午周报填写人工提醒。
重复的工作浪费我们的开发时间,思考问题的时间,提升能力的时间。
所以基于VS Code的插件能力,结合团队FE统一UI组件,统一工作流的情况,开发包含多种功能的FE提效工具。与此同时,学习VS Code插件的从0到1开发,多掌握一门能力。
二. 功能介绍
github源码地址
也可在VS Code插件市场中搜索banma-helper自行体验。
2.1 tan****组件代码片段;关键字连想,自动补齐。
- 快捷键:bm-
- 说明:在代码中,输入bm-等关键字,自动插入tan****组件代码。
- 演示:
2.2 tan****组件,hover展示属性文档。
- 说明:在代码中,将鼠标移至tan****组件,即可查看属性文档。减少了在浏览器中查看文档的步骤。
- 演示:
2.3 axios $http请求代码片段。关键字连想,自动补齐。
- 快捷键:http
- 说明:在代码中,输入ajax或http等关键字,自动插入对应请求代码。
- 演示:
2.4 右键 - 打开远程仓库文件 或者 - 自动创建pr
- 快捷键:鼠标右击
- 说明:自动根据当前编辑文件所在的分支,创建pr
- 演示:
2.5 快捷键自动执行git工作流
- 快捷键:cmd + shift + g
- 说明:开发完需求后,执行快捷键,输入commit信息,即可push成功。并打开C部署系统(develop, qa, dev分支有效)
- 演示:
2.6 每天下午三点,提醒提肛运动。
- 也可使用快捷键尝鲜,command + shift + p,搜索 运动
- 相关配置:
默认关闭,如需请打开,请参考:
1. 左上角code -> 首选项 -> 设置 -> 搜索banma-helper -> 打开提肛运动配置(on)
2. 或者使用快捷键( command+, )打开设置 -> 搜索banma-helper -> 打开提肛运动配置(on) - 演示:
2.7 每周五下午三点周报填报提醒
三. 从0到1开发VS Code插件
1. 初始化项目
npm install -g yo generator-code
这个脚手架会生成一个可以立马开发的项目。运行生成器,然后填好各个字段。
完成后进入 VS Code,按下F5
,你会立即看到一个插件开发主机窗口,其中就运行着插件。
在命令面板(Ctrl+Shift+P
)中输入Hello World
命令。
2. 源文件重点解读
.
├── .vscode
│ ├── launch.json // 插件加载和调试的配置
│ └── tasks.json // 配置TypeScript编译任务
├── .gitignore // 忽略构建输出和node_modules文件
├── README.md // 一个友好的插件文档
├── src
│ └── extension.ts // ** 插件源代码
├── package.json // ** 插件配置清单
├── tsconfig.json // TypeScript配置
插件的关键部分——package.json
和extensions.ts
2.1 插件配置清单package.json
每个VS Code插件都必须包含一个package.json
,它就是插件的配置清单。package.json
混合了Node.js字段,如:scripts
、dependencies
,还加入了一些VS Code独有的字段,如:publisher
、activationEvents
、contributes
等。关于这些VS Code字段说明都在插件清单参考中可以找到。我们在本节介绍一些非常重要的字段:
2.2 插件入口文件extensions.ts
插件入口文件会导出两个函数,
activate
:激活事件被触发之时执行;deactivate
:插件关闭前执行清理工作。
import * as vscode from 'vscode';
// 一旦插件激活,vscode会立刻调用下述方法
export function activate(context: vscode.ExtensionContext) {
// 只会在插件激活时执行一次
console.log('Congratulations, your extension "my-first-extension" is now active!');
// 入口命令已经在package.json文件中定义好了,现在调用registerCommand方法
// registerCommand中的参数必须与package.json中的command保持一致
let disposable = vscode.commands.registerCommand('extension.sayHello', () => {
//
每次命令执行时都会调用这里的代码
// 给用户显示一个消息提示
vscode.window.showInformationMessage('Hello World!');
});
context.subscriptions.push(disposable);
}
接下来,就可以基于脚手架生成的项目,进行改造、功能开发,打造属于我们自己的VS Code插件。
3. 实战开发功能
3.1 axios $http请求代码片段。关键字连想,自动补齐。(侧重了解snippets)
用到的配置属性是contributes.snippets
。
为语言添加代码片段。
language
属性必须是语言标识符;path
必须是使用VS Code代码片段格式的代码片段文件的相对路径。
"snippets": [
{
"language": "javascript",
"path": "./snippets/javascript.json"
}
],
// snippets/javascript.json
{
"httpPost": {
"prefix": "httpPost",
"body": [
"const params = {}",
"const url = `${this.PATH_PREFIX.AI}${this.API.TOOL.MSGBOX.FETCH_MSG}`",
"this.\\$http.post(url, { ...params }).then(result => {",
"\tconst { code, msg, data } = result",
"\tif (code === 0) {",
"\t\tthis.\\$message.success('success')",
"\t} else {",
"\t\tthis.\\$message.error(msg)",
"\t}",
"}).catch(err => {",
"\tthis.\\$message.error(err)",
"})"
],
"description": "Code snippet for \"axios Http Post\""
}
}
简而言之,就是会把path路径下的文件,当做snippets片段处理,只需输入关键字,即可自动补全。
同理,tan****组件代码片段,关键字连想,自动补齐。也使用该方法实现,只需维护组件snippets片段文件即可。
3.2 快捷键自动执行git工作流(侧重了解keybindings、commands、vscode提供的api能力)
开发完需求后,执行快捷键,输入commit信息,即可push成功。并打开C部署系统(develop, qa, dev分支有效)。
因为涉及到快捷键、命令的操作。所以此时就会用到contributes.keybindings
,这个配置确定了用户输入按键组合时的触发规则。
"keybindings": [
{
"command": "banma-helper.git",
"key": "ctrl+shift+g",
"mac": "cmd+shift+g"
},
]
同时需要搭配contributes.commands
(调用命令)的使用。
"commands": [
{
"command": "banma-helper.git",
"title": "Git add、commit、push、打开C部署系统"
},
]
在入口extension.ts文件中,注册命令。
import { gitOp } from './lib/gitOp';
export function activate(context: vscode.ExtensionContext) {
...
...
const git = vscode.commands.registerCommand('banma-helper.git', () => {
gitOp();
});
context.subscriptions.push(git);
}
git工作流命令处理,主要了解vscode提供的api能力
- vscode.InputBoxOptions:输入框UI
- vscode.workspace.workspaceFolders:工作区文件
- vscode.window.setStatusBarMessage:设置底部进度条
- vscode.window.showInformationMessage:message提示
- env.openExternal(URI.parse(
http://***.***.***.com/fe/task#/deploy/${name}
)):打开外部网址
// /lib/gitOp.ts
import * as vscode from 'vscode';
import * as SimpleGit from 'simple-git/promise';
const { window, workspace, env, Uri: URI } = vscode;
export const gitOp = async () => {
const options: vscode.InputBoxOptions = {
prompt: "Label: ",
placeHolder: "请输入commit信息"
};
if (workspace.workspaceFolders) {
const git: SimpleGit.SimpleGit = SimpleGit(workspace.workspaceFolders[0].uri.path);
const value = await window.showInputBox(options);
if (!value) {
throw new Error('请输入commit信息');
};
try {
vscode.window.setStatusBarMessage('git工作流提交中,请耐心等待', 2000);
await git.add('.');
const { commit } = await git.commit(value.trim());
const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
if (!commit) {
// 如果没有更改内容,推测是develop, qa merge后的,需要直接push,所以没有 throw
if (!['develop', 'qa', 'dev'].includes(branch)) {
throw new Error('commit有问题');
}
}
await git.pull('origin', branch);
await git.push('origin', branch);
if (['develop', 'qa', 'dev'].includes(branch)) {
window.showInformationMessage('push成功,正在打开皮卡丘');
const name = workspace.workspaceFolders[0].uri.path.split('/').reverse()[0];
env.openExternal(URI.parse(`http://***.***.***.com/fe/task#/deploy/${name}`));
} else {
window.showInformationMessage('push成功');
}
} catch(e) {
window.showErrorMessage(e.message);
}
}
};
同理,快捷键生成A算法系统Filter + Table + EditModal文件,也使用该原理 + 插入文件操作。
3.3 右键功能(侧重了解menus,window.activeTextEditor)
contributes.menus
:为编辑器或者文件管理器设置命令的菜单项。- 插件创作者可以配置的菜单的地方有:
editor/context
(编辑器上下文菜单),scm/resource/context
(SCM资源)等等。 group
:属性定义了菜单的分组when
:显示菜单的时机
// package.json
"menus": {
"editor/context": [
{
"command": "banma-helper.reactpro",
"group": "1_Starling",
"when": "banma-helper:rightClick"
},
{
"command": "banma-helper.openorigin",
"group": "1_Starling"
},
{
"command": "banma-helper.openpr",
"group": "1_Starling"
}
]
},
"commands": [
{
"command": "banma-helper.openpr",
"title": "banma -- 创建pr"
},
]
入口文件注册命令:
import OpenOrigin from './lib/openOrigin';
vscode.commands.registerCommand('banma-helper.openpr', () => {
OpenOrigin.openpr(context);
});
git逻辑处理:
window.activeTextEditor
:当前活动的编辑器window.activeTextEditor.document.fileName
:当前光标所在文件路径- 获取当前分支:
const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
import * as vscode from 'vscode';
import * as SimpleGit from 'simple-git/promise';
const { window, Uri: URI, env, workspace } = vscode;
export default class ReactRro {
public static async openpr(context: vscode.ExtensionContext) {
if (window.activeTextEditor && workspace.workspaceFolders) {
try {
const path = window.activeTextEditor.document.fileName; // 本地路径
const workPath = path.match(/banma_[\s\S]*/g) || []; // 匹配banma_路径
const workPathArray = workPath[0].split('/'); // 路径切割
const workName = workPathArray[0]; // 项目名字
const gitPath = path.match(/[\s\S]*banma_[\w]*\//g) || [] // 项目根路径
const git: SimpleGit.SimpleGit = SimpleGit(gitPath[0]);
const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
const url = `http://****.****.com/code/repo-detail/bm/${workName}/pr/create?source=refs/heads/${branch}&sourceRepo=bm/${workName}&target=refs/heads/master&targetRepo=bm/${workName}`
env.openExternal(URI.parse(url));
} catch (e) {
window.showErrorMessage('打开失败');
}
} else {
window.showErrorMessage('pr创建失败');
}
}
};
即可完成,右键 -> 创建pr:自动根据当前分支,创建合并到master的pull request。
3.4 每天下午三点,提醒提肛运动(侧重了解deactivate,workspace.getConfiguration,window.createWebviewPanel)
- deactivate:生命周期 - 卸载
- workspace.getConfiguration:获取工作区配置,支持用户自定义。
- window.createWebviewPanel:创建并显示一个新的 webview 面板。
同样,注册commands
命令,
"commands": [
{
"command": "banma-helper.strong",
"title": "提gang运动"
}
]
提肛运动功能设计的出发点,默认会把该功能关闭,有需求的用户才会开启,每天下午进行提醒。这样就涉及到用户自行设置,获取插件额外配置。
在contributes.configuration
中配置的内容会暴露给用户,用户可以从“用户设置”和“工作区设置”中修改你暴露的选项。
contributes.configuration
是JSON格式的键值对,用户会在修改设置时获得对应的提示和更好的体验。
你可以用vscode.workspace.getConfiguration('myExtension')
读取配置值。
"configuration": {
"title": "banma-helper-strong",
"properties": {
"banma-helper.strong": {
"type": "string",
"description": "提肛运动配置",
"enum": [
"on",
"off"
],
"default": "off",
"scope": "window"
}
}
},
效果如下↓:
接下来实现,每天下午三点,自动提示提肛运动,并且跳转提肛运动webview页面的逻辑。
- 定时任务采用node-schedule
- window.createWebviewPanel:创建并显示一个新的 webview 面板,把我们的html片段填充到webview.html属性中。
// /lib/strong.ts
import * as vscode from 'vscode';
const schedule = require('node-schedule');
export default class Strong {
private static panel: vscode.WebviewPanel | undefined;
public static schedule() {
// 每天下午三点半
const timer = schedule.scheduleJob('50 38 10 * * *',()=>{
const matchConfig = vscode.workspace.getConfiguration().get('banma-helper.strong');
if (matchConfig === 'on') {
vscode.window.showInformationMessage('5s后进行提肛运动哦,请提前做好准备!');
setTimeout(() => {
this.send();
}, 5000);
}
});
return timer;
// timer.cancel();
}
public static send() {
if (this.panel) {
this.panel.webview.html = this.generatePage();
this.panel.onDidDispose(() => {
this.panel = undefined;
});
} else {
this.panel = vscode.window.createWebviewPanel("hyt", "提肛运动", vscode.ViewColumn.One, {
enableScripts: true,
retainContextWhenHidden: true,
});
this.panel.webview.html = this.generatePage();
this.panel.onDidDispose(() => {
this.panel = undefined;
});
}
}
public static generatePage(): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.desc {
color: #fff;
font-size: 20px;
}
button {
color: #fff;
font-size: 20px;
background-color: #252526;
border: none;
cursor: pointer;
}
.vi-conatiner {
padding-top: 100px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.img-base {
width: 300px;
height: 500px;
}
</style>
</head>
<body style="background-color: #15c27a;">
<div class="vi-conatiner">
<div class="desc">
有规律的上提、放松肛门,促进局部血液循环。
</div>
<button class="desc" onclick="start()">
点击开始哦
</button>
<div style="margin-top: 10px">
<img class="img-stop img-base" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7ea60bfe91c14a98bd098d19e5616a3a~tplv-k3u1fbpfcp-watermark.image" alt="">
</div>
</div>
</body>
<script>
const img = document.getElementsByClassName('img-stop')[0]
function start() {
img.src = "https://p1.meituan.net/paotui/kyvcpbt0f1p.gif"
}
</script>
</html>`;
}
};
最后,把定时任务逻辑加入到插件启动阶段,并在插件销毁时清空定时任务。
import Strong from './lib/strong';
const schedule = require('node-schedule');
const jobs = [];
export function activate(context: vscode.ExtensionContext) {}
const strongTimer = Strong.schedule();
jobs.push(strongTimer);
}
export function deactivate() {
// 清空任务
jobs.forEach(job => {
if (job instanceof schedule.Job) {
job.cancel();
}
});
}
基础功能均已实现,接下来就可以发布我们的插件,在vscode下载使用了。
4. 发布
使用vsce工具进行发布。
4.1 安装vsce
npm install -g vsce
vsce
只能用Personal Access Tokens发布插件,所以至少要创建一个 Token 以便发布插件
4.2 获取 Personal Access Token
首先,你得有一个Azure DevOps 组织。
点击New Token创建一个新的 Personal Access Token
点击Create,就会看到新创建的 Personal Access Token 了,复制好,接下来就会用到这个 token 来创建一个发行方了。
4.3 终端登录
创建发行方
vsce create-publisher (publisher name)
登录
vsce create-publisher (publisher name)
增量更新插件版本
用 SemVer 语义标识符:major
,minor
,patch
增量更新插件版本号。
vsce publish patch
之后就可以在vscode插件市场中下载使用了。
4.4 VS Code 版本兼容性
当你制作插件的时候,你需要描述插件对 VS Code 的版本兼容性——修改package.json
中的engines.vscode
:
{
"engines": {
"vscode": "^1.50.0"
}
}
四. 总结
本插件是笔者基于日常开发中的痛点、以及根据VS Code插件能力,做了一些功能实现。
可能实现过程中并不是最佳实践,亦或者某些功能适用性不强,只适合当前自己的工作项目实际情况(eg:打开pr,只适合当前工作项目情况)。
但想要表达的是:工作中某些场景,需要我们主动思考、解决某些问题,比如开发提效可以从一直在使用的VS Code出发考虑。
最后,附上github源码地址