目前网络上可以找到很多VSCode插件开发教程,其中不乏一些优秀的文章。本篇将从实战的角度出发,带领大家开发一个好用、强大的插件。
Visual Studio Code 是一个运行在桌面上,并且可用于Windows,Mac OS X和Linux平台的的轻量级且功能强大的源代码编辑器。它配备了内置的JavaScript的,TypeScript和Node.js的支持,并具有其他语言(C ++,C#,Python和PHP)的扩展以及一个丰富的生态系统。
比sublime开源,比atom更快,比webstorm更轻。
下面来让我们一起开发一个强大的px转rpx插件。
npm install -g yo
npm install -g generator-code
执行下面代码
yo code
通过yo code生成插件开发项目,这里官方推荐使用TypeScript,当然我们更熟悉javascript,可根据自身实际情况选择。
执行后会提示几个问题,第一个问题我们选择 New Extension (TypeScript)
,创建使用TypeScript开发的扩展插件。(可以使用 yo code 来创建插件、主题、语言支持、代码片段、语言支持、键盘映射、插件包,本文我们只讨论插件,其他功能感兴趣的同学可以自行研究。)
填写完成后,会自动创建文件夹并帮助初始化完成文件,我们先来看下目录结构。
|-- src
|-- test //插件单测文件
|-- extension.js //插件入口文件
|-- CHANGELOG.md //修改日志,发布后会展示
|-- package-lock.json
|-- package.json
|-- README.md //插件说明 README,发布后会展示
|-- tsconfig.json
|-- tslint.json
|-- vsc-extension-quickstart.md //插件开发说明
通过VSCode打开刚生成的插件项目,在这些文件中,有两个重点:
extension.js 是插件的入口文件
package.json包含插件的配置信息(插件命令、快捷键、菜单均在此配置)
准备完成后,为了先验证下插件项目正常OK,在VSCode中F5运行(或Debug->start)如果你可以看到VSCode又启动了一个窗口运行插件项目,shift+ctrl+p 输入Hello World如果在右下角能看到Hello World的提示信息就OK 了。
如果在编辑插件时内容做了变更,在运行的窗口只需通过Ctrl+r 即可刷新,无需关闭重新运行
在开始正式编写插件之前,我们先来明确一下需求。因为笔者主要从事小程序开发,经常需要使用rpx来实现自适应布局,而px到rpx的转换却是一项繁琐的工作。那么如何才能在编写代码时快速的进行px到rpx的转换以提高开发效率呢?VSCode插件自然是最直接的一种方式。
下面我们就来看一下插件要实现的功能:
在css文件中输入px时自动给出px到rpx的转换提示,并且可以使用右键菜单的方式转换选定代码中的px。
下面就让我们开始动手实现我们的插件。
新建process.ts
export class CssRpxProcess {
constructor(private config: any) {}
private rePxReg: RegExp = /(\\d+(\\.\\d+)?)(px)?/;
private rePxAllReg: RegExp = /(\\d+(\\.\\d+)?)px/g;
/**
* transform px value to rpx value
*
* @param{string}pxStr px value
* @return{Object}transformed object
*/
private pxToRpx(pxStr: string) {
const px = parseFloat(pxStr);
let rpxValue: number | string = +(px * (750 / this.config.baseWidth)).toFixed(this.config.fixedDigits);
if (this.config.autoRemovePrefixZero) {
if (rpxValue.toString().startsWith('0.'))
rpxValue = rpxValue.toString().substring(1);
}
return {px: pxStr, pxValue: px, rpxValue, rpx: rpxValue + 'rpx'};
}
/**
* transform px to rpx
*
* @param{string}code origin text
* @return{string}transformed text
*/
convert(text: string) {
let match = text.match(this.rePxReg);
if (!match) return null;
return this.pxToRpx(match[1]);
}
/**
* transform all px to rpx
*
* @param{string}code origin text
* @return{string}transformed text
*/
convertAll(code: string): string {
if (!code) return code;
return code.replace(this.rePxAllReg, (word: string) => {
const res = this.pxToRpx(word);
if (res) return res.rpx;
return word;
});
}
}
新建provider.ts
import * as vscode from 'vscode';
import{ CssRpxProcess }from "https://zhuanlan.zhihu.com/p/process";
import * as nls from 'vscode-nls';
//国际化支持
const localize=nls.config({ messageFormat: nls.MessageFormat.both})();
//实现代码提示
export class CssRpxProvider implements vscode.CompletionItemProvider{
constructor(private process: CssRpxProcess){ }
provideCompletionItems (
docume、nt: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): Thenable<vscode.CompletionItem[]>{
return new Promise((resolve, reject)=>{
let wordAtPosition=document.getWordRangeAtPosition(position);
var currentWord='';
if (wordAtPosition && wordAtPosition.start.character < position.character){
var word=document.getText(wordAtPosition);
currentWord=word.substr(0, position.character - wordAtPosition.start.character);
}
const res=this.process.convert(currentWord);
if (!res){
return resolve([]);
}
//代码提示信息
const item=new vscode.CompletionItem(`${res.pxValue}px -> ${res.rpx}`, vscode.CompletionItemKind.Snippet);
// 要插入的文本
item.insertText=res.rpx;
item.detail='Value';
//国际化提示信息
let message=localize('px2rpx.description',
'Translate $0px to $1, you can set design base width in preferences settings.');
message=message.replace('$0', res.pxValue + '')
message=message.replace('$1', res.rpx + '')
item.documentation=`${message}`;
return resolve([item]);
});
}
}
修改extension.ts
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
'use strict';
import * as vscode from 'vscode';
import { CssRpxProcess } from 'https://zhuanlan.zhihu.com/p/process';
import { CssRpxProvider } from 'https://zhuanlan.zhihu.com/p/provider';
let config = null;
// 插件被激活时调用activate
export function activate(context: vscode.ExtensionContext) {
config = vscode.workspace.getConfiguration("px-to-rpx");
const process = new CssRpxProcess(config);
let provider = new CssRpxProvider(process);
const LANS = ['html', 'vue', "swan", "wxml", "axml", 'css', "wxss", "acss", 'less', 'scss', 'sass', 'stylus', 'wxss', 'acss'];
for (let lan of LANS) {
//为对应类型文件添加代码提示
let providerDisposable = vscode.languages.registerCompletionItemProvider(lan, provider);
context.subscriptions.push(providerDisposable);
}
//注册px2rpx命令
vscode.commands.registerTextEditorCommand('extension.px2rpx', (textEditor, edit) => {
const doc = textEditor.document;
const start = new vscode.Position(0, 0);
const end = new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length);
//获取全部文本区域
const selection = new vscode.Range(start, end);
let text = doc.getText(selection);
//替换文件内容
textEditor.edit(builder => {
builder.replace(selection, process.convertAll(text));
});
});
//注册px2rpxInSelection命令
let disposable = vscode.commands.registerTextEditorCommand('extension.px2rpxInSelection', (textEditor, edit) => {
const doc = textEditor.document;
let selection: vscode.Selection | vscode.Range = textEditor.selection;
//获取选中区域
if (selection.isEmpty) {
const start = new vscode.Position(0, 0);
const end = new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length);
selection = new vscode.Range(start, end);
}
let text = doc.getText(selection);
//替换文件内容
textEditor.edit(builder => {
builder.replace(selection, process.convertAll(text));
});
});
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate(){}
添加gulpfile.js
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const gulp=require('gulp');
const path=require('path');
const ts=require('gulp-typescript');
const typescript=require('typescript');
const sourcemaps=require('gulp-sourcemaps');
const del=require('del');
const runSequence=require('run-sequence');
const es=require('event-stream');
const vsce=require('vsce');
const nls=require('vscode-nls-dev');
const tsProject=ts.createProject('https://zhuanlan.zhihu.com/p/tsconfig.json',{ typescript });
const inlineMap=true;
const inlineSource=false;
const outDest='out';
// If all VS Code langaues are support you can use nls.coreLanguages
const languages=[{ folderName: 'zh-cn', id: 'zh-cn' }];
const cleanTask=function(){
return del(['out/**', 'package.nls.*.json', 'i18n-sample*.vsix']);
}
const internalCompileTask=function(){
return doCompile(false);
};
const internalNlsCompileTask=function(){
return doCompile(true);
};
const addI18nTask=function(){
return gulp.src(['package.nls.json'])
.pipe(nls.createAdditionalLanguageFiles(languages, 'i18n'))
.pipe(gulp.dest('.'));
};
const buildTask=gulp.series(cleanTask, internalNlsCompileTask, addI18nTask);
const doCompile=function (buildNls){
var r=tsProject.src()
.pipe(sourcemaps.init())
.pipe(tsProject()).js
.pipe(buildNls ? nls.rewriteLocalizeCalls() : es.through())
.pipe(buildNls ? nls.createAdditionalLanguageFiles(languages, 'i18n', 'out') : es.through());
if (inlineMap && inlineSource){
r=r.pipe(sourcemaps.write());
}else{
r=r.pipe(sourcemaps.write("https://zhuanlan.zhihu.com/out",{
// no inlined source
includeContent: inlineSource,
// Return relative source map root directories per file.
sourceRoot: "https://zhuanlan.zhihu.com/src"
}));
}
return r.pipe(gulp.dest(outDest));
}
const vscePublishTask=function(){
return vsce.publish();
};
const vscePackageTask=function(){
return vsce.createVSIX();
};
gulp.task('default', buildTask);
gulp.task('clean', cleanTask);
gulp.task('compile', gulp.series(cleanTask, internalCompileTask));
gulp.task('build', buildTask);
gulp.task('publish', gulp.series(buildTask, vscePublishTask));
gulp.task('package', gulp.series(buildTask, vscePackageTask));
gulp.task('watch-build', function (){
buildTask();
gulp.watch('*.ts', gulp.series(buildTask));
gulp.watch('https://zhuanlan.zhihu.com/p/i18n/**/*.json', gulp.series(buildTask));
gulp.watch('https://zhuanlan.zhihu.com/p/package.json', gulp.series(buildTask));
});
修改package.json
{
"name": "px-to-rpx",
"displayName": "px to rpx",
"description": "This is an extension for Visual Studio Code that allows you to convert px to rpx.",
"version": "0.0.2",
"publisher": "zhengjiaqi",
// 仓库地址
"repository": "https://github.com/zhengjiaqi/vscode-px-to-rpx",
"engines":{
"vscode": "^1.35.0"
},
// 插件类别
"categories":[
"Formatters",
"Snippets",
"Other"
],
// 搜索关键字
"keywords":[
"px2rpx",
"rpx",
"px",
"px to rpx"
],
// 插件图标
"icon": "imgs/pxToRpxIcon.png",
// 插件激活事件
"activationEvents":[
"onLanguage:html",
"onLanguage:vue",
"onLanguage:swan",
"onLanguage:wxml",
"onLanguage:axml",
"onLanguage:css",
"onLanguage:wxss",
"onLanguage:acss",
"onLanguage:less",
"onLanguage:scss",
"onLanguage:sass",
"onLanguage:stylus",
"onLanguage:tpl"
],
// 插件入口
"main": "https://zhuanlan.zhihu.com/p/out/extension.js",
"contributes":{
// 注册命令
"commands":[
{
"command": "extension.px2rpx",
"title": "%extension.px2rpx.title%"
},
{
"command": "extension.px2rpxInSelection",
"title": "%extension.px2rpxInSelection.title%"
}
],
// 注册快捷键
"keybindings":[
{
"command": "extension.px2rpx",
"key": "cmd+Alt+p"
},
{
"command": "extension.px2rpxInSelection",
"key": "Alt+shift+p"
}
],
// 注册菜单
"menus":{
"editor/context":[
{
"when": "editorFocus",
"command": "extension.px2rpx",
"group": "6_px"
},
{
"when": "editorHasSelection",
"command": "extension.px2rpxInSelection",
"group": "6_px"
}
]
},
// 注册插件设置项
"configuration":{
"type": "object",
"title": "px-to-rpx configuration",
"properties":{
"px-to-rpx.baseWidth":{
"type": "number",
"default": 1242,
"description": "%extension.baseWidth.title%"
},
"px-to-rpx.fixedDigits":{
"type": "number",
"default": 3,
"description": "%extension.fixedDigits.title%"
},
"px-to-rpx.autoRemovePrefixZero":{
"type": "boolean",
"default": false,
"description": "%extension.autoRemovePrefixZero.title%"
}
}
}
},
"scripts":{
"compile": "tsc -p https://zhuanlan.zhihu.com/p/",
"watch": "tsc -watch -p https://zhuanlan.zhihu.com/p/",
"postinstall": "node https://zhuanlan.zhihu.com/p/node_modules/vscode/bin/install",
"test": "npm run compile && node https://zhuanlan.zhihu.com/p/node_modules/vscode/bin/test",
"package": "gulp package",
"build": "gulp build",
"publish": "gulp publish",
"dev": "gulp watch-build"
},
"devDependencies":{
"vscode": "^1.1.28",
"@types/node": "^10.12.21",
"@types/mocha": "^2.2.42",
"del": "^4.1.1",
"event-stream": "^4.0.1",
"gulp": "^4.0.2",
"gulp-filter": "^5.1.0",
"gulp-sourcemaps": "^2.6.5",
"gulp-typescript": "^5.0.1",
"run-sequence": "^2.2.1",
"tslint": "^5.16.0",
"typescript": "^3.5.1",
"vsce": "^1.63.0",
"vscode-nls-dev": "^3.2.6"
},
"dependencies":{
"vscode-nls": "^4.1.1"
}
}
然后执行
npm run dev
默认情况下,工程已经帮我们配置好了调试相关参数(有兴趣的可以查看.vscode/launch.json
文件的写法),只需要到调试面板选中要调试的项目(仅仅是第一次需要,后续会自动记住上次调试的项目),然后按下F5
就会弹出一个新的vscode窗口:
进入到插件运行环境下就可以测试px转rpx功能了。
插件编写好之后,我们可以把开发好的插件打包成.vsix
文件,在本地安装测试,或者提供给身边的小伙伴试用一下。
执行
npm run package
此命令会调用vsce模块的createVSIX方法,在项目根目录创建一个.vsix插件包
在VSCode扩展中选择从VSIX安装即可安装本地插件包。重启VSCode,即可使用新的插件功能。
Visual Studio Code的应用市场基于微软自己的Azure DevOps,插件的身份验证、托管和管理都是在这里。要发布到应用市场首先得有应用市场的publisher账号
点击继续,默认会创建一个以邮箱前缀为名的组织。
3.默认进入组织的主页后,点击右上角的Security
:
点击创建新的个人访问令牌,这里特别要注意Organization
要选择all accessible organizations
,Scopes
要选择Full access
,否则后面发布会失败。
创建令牌成功后你需要本地记下来,因为网站是不会帮你保存的。
4. 获得个人访问令牌后,使用vsce
以下命令创建新的发布者:
vsce create-publisher your-publisher-name
创建成功后会默认登录这个账号,接下来你可以直接发布了,当然,如果你是在其它地方创建的,可以试用vsce login your-publisher-name
来登录。
5. 执行发布命令
npm run pubilsh
此命令调用vsce publish
进行发布。
发布成功后,可以访问https://marketplace.visualstudio.com/items?itemName=zhengjiaqi.px-to-rpx 查看
在VSCode中也可以搜索进行安装
https://github.com/zhengjiaqi/vscode-px-to-rpxhttp://blog.haoji.me/vscode-plugin-overview.html
https://github.com/microsoft/vscode-extension-samples/tree/master/i18n-sample