之前一直都是使用智图来把图片转换成webp格式的,前段时间发现它居然不支持在线压缩转换了,必须要下载客户端,电脑要爆炸了,实在是不想继续添加软件了。近日公司有小伙伴搭建了一个项目,使用node的插件将图片转换为webp格式的,每次压缩还要把图片拷贝到这个项目下,然后运行,也是很麻烦的。。。便想到来开发一个vscode插件,方便快捷!
一、项目搭建
关于项目搭建,在之前的一篇文章中已经详细介绍过了,不会的小伙伴们可以先去学习下vscode插件开发之翻译插件。
二、准备工作
该插件将图片转换为webp格式的图片采用的是imagemin-webp插件。
首先安装下npm包:
npm install --save imagemin
npm install --save imagemin-webp
使用方式如下:
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
(async () => {
await imagemin(['images/*.{jpg,png}'], 'build/images', {
use: [
imageminWebp({quality: 50})
]
});
console.log('Images optimized');
})();
该插件图片压缩采用的是tinypng的API,也尝试了其它的几种node插件,压缩效果都没有这个好,一一试过后最终还是选择了这个。
首先我们需要去tinypng官网获取我们的API key。使用该API进行图片的压缩仅每个月的前500张是免费的,超出则需要付费使用,但是应该每个小伙伴每个月开发不需要使用如此多的图片。
获取到API key之后,打开vscode的配置文件,添加你的API key:
"tinypng.apiKey":"********",
首先我们需要安装它的npm包:
npm install --save tinify
安装好npm包之后,使用方式如下:
const source = tinify.fromFile("unoptimized.webp");
source.toFile("optimized.webp");
还有其它使用方法,具体可参考官网。
三、项目开发
主要的开发文件就是清单文件package.json以及入口文件extension.ts。
package.json
首先我们需要先配置我们的插件激活事件,本插件配置了五个激活事件,分别是压缩单个图片、压缩图片文件夹、将单个图片转为webp格式的、将整个文件夹下的图片转为webp格式的、获取当前已经使用了几次压缩次数,如下:
"activationEvents": [
"onCommand:extension.compressImage",
"onCommand:extension.compressImageFolder",
"onCommand:extension.convertToWebp",
"onCommand:extension.convertFolderToWebp",
"onCommand:extension.getCompressUsedCount"
],
接着来给我们配置的事件设置事件名称及分类,commands:
"contributes": {
"commands": [
{
"command": "extension.compressImage",
"title": "compress image",
"category": "compress"
},
{
"command": "extension.compressImageFolder",
"title": "compress images",
"category": "compress"
},
{
"command": "extension.convertToWebp",
"title": "convert image to webp",
"category": "compress"
},
{
"command": "extension.convertFolderToWebp",
"title": "convert images to webp",
"category": "compress"
},
{
"command": "extension.getCompressUsedCount",
"title": "get compress used count",
"category": "compress"
}
]
....
}
设置好事件名称及分类后,接着来配置我们的菜单,menus:
"contributes": {
"commands": [
...
],
"menus": {
"commandPalette": [
{
"command": "extension.getCompressUsedCount"
},
{
"command": "extension.compressImage",
"when": "False"
},
{
"command": "extension.convertToWebp",
"when": "False"
},
{
"command": "extension.compressImageFolder",
"when": "False"
},
{
"command": "extension.convertFolderToWebp",
"when": "False"
}
],
"editor/title/context": [
{
"when": "resourceLangId == image_file",
"command": "extension.compressImage",
"group": "1_modification"
},
{
"when": "resourceLangId == image_file",
"command": "extension.convertToWebp",
"group": "1_modification"
}
],
"explorer/context": [
{
"when": "resourceLangId == image_file",
"command": "extension.compressImage",
"group": "1_modification"
},
{
"when": "explorerResourceIsFolder",
"command": "extension.compressImageFolder",
"group": "1_modification"
},
{
"when": "resourceLangId == image_file",
"command": "extension.convertToWebp",
"group": "1_modification"
},
{
"when": "explorerResourceIsFolder",
"command": "extension.convertFolderToWebp",
"group": "1_modification"
}
]
}
}
commandPalette:全局命令面板,在命令栏中直接输入命令时,由于没有目标图片,所以这里我们让除了获取次数命令之外的其它命令都不可见。
editor/title/context:编辑器标题上下文菜单,当我们打开图片时,把鼠标放在编辑器图片的标题上,按右键触发的菜单。这里我们需要控制仅当当前的文件是图片时,才展示压缩图片的命令。
when:控制命令何时可见,resourceLangId是用来获取当前languages的id。在languages配置项里我们配置了一个id为image_file的配置,该配置限制文件扩展名为png、jpg、jpeg。所以这里的配置意思就是当前文件名的后缀为png、jpg、jpeg才显示该命令。when字段的具体介绍可见官网。command:命令group:分组排序,1_modification是在默认组的第二项。group字段的具体介绍可见官网。
explorer/context:资源管理器上下文菜单,当我们选中右侧文件目录的文件时,按右键触发的菜单。当文件是图片时,展示压缩图片的命令;当文件是文件夹时,展示压缩图片文件夹得命令。其中"when": "explorerResourceIsFolder"的意思是,当前资源是文件夹。
刚刚我们有提到languages配置项,该配置项是用来定义一个可以在VS Code API的其他部分重用的languageId,将文件扩展名、文件名模式、以特定行开头的文件、minetypes与该语言ID相关联。该插件定义了一个ID为image_file,关联了文件扩展名为png、jpg、jpeg的文件。
"contributes": {
"commands": [
...
],
"menus": [
...
],
"languages": [
{
"id": "image_file",
"extensions": [
".png",
".jpg",
".jpeg"
]
}
]
}
最后,来设置下使用者可选择的配置项,configuration。
"contributes": {
"commands": [
...
],
"menus": [
...
],
"languages": [
...
],
"configuration": {
"title": "TinyPNG Configuration",
"properties": {
"tinypng.apiKey": {
"type": "string",
"description": "Your TinyPNG API Key"
},
"tinypng.forceOverwrite": {
"type": "boolean",
"default": false,
"description": "Judge whether to overwrite the uncompressed picture or to create a new one"
},
"compress.webpQuality": {
"type": "number",
"default": 50,
"description": "Set picture quality factor between 0 and 100"
}
}
}
}
使用该插件前,需要先在vscode的配置文件中设置一些插件所必须的信息。该插件有三个配置项:
tinypng.apiKey:即我们原本在官网下获取到的API key。该配置项为必须项,不配置则无法进行图片压缩。每人每月前500张免费,超过则需到官网购买。tinypng.forceOverwrite:选择压缩后的图片是放置在新的目录下还是直接替换该图片。true:直接替换;false:放置在新目录下。不配置时默认为false。compress.webpQuality:转换为webp的质量配置,范围为0-100,配置越高,图片越清晰,同样体积越大。不配置时默认为50。
extension.ts
清单配置好了后,需要在我们的入口文件中注册我们所设置的命令并执行相应的操作。 分为以下三步:
- 获取配置项信息
- 验证身份
- 注册命令,声明命令运行时执行的函数
首先导入需要用到的第三方库
import * as vscode from 'vscode';
const tinify = require("tinify");
const path = require("path");
const fs = require("fs");
const imagemin = require("imagemin");
const imageminWebp = require("imagemin-webp");
接着来实现下验证身份的函数:
const validate = (
onSuccess = () => { },
onFailure = (e: any) => { }
) =>
tinify.validate(function (err: any) {
if (err) {
onFailure(err);
} else {
onSuccess();
}
});
压缩图片的函数:
const compressImage = (image: any, folderPath?: string) => {
// 获取配置信息,压缩后的图片是否覆盖未压缩的图片
const shouldOverwrite = vscode.workspace
.getConfiguration('tinypng')
.get('forceOverwrite');
// 压缩后的文件存放的地址
let destinationImagePath = image.fsPath;
const parsedPath = path.parse(image.fsPath);
if (!shouldOverwrite) {
const dirname = parsedPath.dir;
if (folderPath) {
// 如果是压缩文件夹的话,压缩后的图片放在压缩文件夹下的image-min文件夹下
destinationImagePath = path.join(folderPath, 'image-min');
} else {
// 如果是图片,压缩后的图片放在被压缩图片所在目录的image-min文件夹下
destinationImagePath = path.join(dirname, 'image-min');
}
// image-min文件夹不存在的话就新建
if (!fs.existsSync(destinationImagePath)) {
fs.mkdir(destinationImagePath, (err: any) => {
if (err) {
console.log('Failed to create a folder');
} else {
console.log('successed to create a folder');
}
});
}
destinationImagePath = path.join(destinationImagePath, parsedPath.base);
}
// 编辑器左下角显示压缩中的提示
const statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left
);
statusBarItem.text = `Compressing file ${image.fsPath}...`;
statusBarItem.show();
// 压缩图片
return tinify.fromFile(image.fsPath).toFile(destinationImagePath, (err: any) => {
statusBarItem.hide();
// 处理错误信息
if (err) {
// Verify your API key and account limit.
if (err instanceof tinify.AccountError) {
console.log("The error message is: " + err.message);
vscode.window.showErrorMessage(
'Authentication failed. Have you set the API Key?'
);
} else if (err instanceof tinify.ClientError) {
// Check your source image and request options.
console.log("The error message is: " + err.message);
vscode.window.showErrorMessage(
'Ooops, there is an error. Please check your source image and settings.'
);
} else if (err instanceof tinify.ServerError) {
// Temporary issue with the Tinify API.
console.log("The error message is: " + err.message);
vscode.window.showErrorMessage(
'TinyPNG API is currently not available.'
);
} else if (err instanceof tinify.ConnectionError) {
// A network connection error occurred.
console.log("The error message is: " + err.message);
vscode.window.showErrorMessage(
'Network issue occurred. Please check your internet connectivity.'
);
} else {
// Something else went wrong, unrelated to the Tinify API.
console.error(err.message);
vscode.window.showErrorMessage(err.message);
}
} else {
// compress successfully
vscode.window.showInformationMessage(
`Successfully compressed ${image.fsPath} to ${destinationImagePath}!`
);
}
});
};
转换成webp格式的函数:
const convertToWebp = async (image: any, folderPath?: string) => {
try {
const dirname = path.dirname(image.fsPath);
// 转换后的图片存放的地址
let destinationImagePath;
if (folderPath) {
// 如果是文件夹的话,转换后的图片放在文件夹下的webp文件夹
destinationImagePath = path.join(folderPath, 'webp');
} else {
// 如果是图片的话,转换后的
destinationImagePath = path.join(dirname, 'webp');
}
// 编辑器左下角显示压缩中的提示
const statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left
);
statusBarItem.text = `Compressing file ${image.fsPath}...`;
statusBarItem.show();
// 转换
await imagemin([image.fsPath], {
glob: false,
destination: destinationImagePath,
plugins: [imageminWebp({ quality: imageQuantity })]
});
statusBarItem.hide();
const parsedPath = path.parse(image.fsPath);
vscode.window.showInformationMessage(
`Successfully convert ${image.fsPath} to ${destinationImagePath + '\\' + parsedPath.name + '.webp'}!`
);
} catch (e) {
console.log('convert fail', e);
vscode.window.showErrorMessage('Ooops, there is an error. Please check your source image and settings.');
}
};
最后来注册下我们的命令
// the quality of the webp image
let imageQuantity: unknown;
export function activate(context: vscode.ExtensionContext) {
tinify.key = vscode.workspace.getConfiguration("tinypng").get("apiKey") || "";
imageQuantity = vscode.workspace.getConfiguration("compress").get("webpQuality") || 50;
// 验证身份
validate(
() => console.log('Validation successful!'),
(e) => {
console.error(e.message, "validate user fail");
vscode.window.showInformationMessage(
'TinyPNG: API validation failed. Be sure that you filled out tinypng.apiKey setting already.'
);
}
);
// 压缩图片
const compressImageDisposable = vscode.commands.registerCommand('extension.compressImage', (image) => {
compressImage(image);
});
context.subscriptions.push(compressImageDisposable);
// 压缩文件夹里的所有图片
const compressImageFolderDisposable = vscode.commands.registerCommand('extension.compressImageFolder', (folder) => {
vscode.workspace.
findFiles(new vscode.RelativePattern(
folder.fsPath,
`**/*.{png,jpg,jpeg}`
))
.then((files) => files.forEach((file) => {
compressImage(file, folder.fsPath);
}));
});
context.subscriptions.push(compressImageFolderDisposable);
// 将图片转换成webp格式的
const convertToWebpDisposable = vscode.commands.registerCommand('extension.convertToWebp', (image) => {
convertToWebp(image);
});
context.subscriptions.push(convertToWebpDisposable);
// 将文件夹里所有的图片转换为webp格式的
const convertFolderToWebpDisposable = vscode.commands.registerCommand('extension.convertFolderToWebp', (folder) => {
vscode.workspace.
findFiles(new vscode.RelativePattern(
folder.fsPath,
`**/*.{png,jpg,jpeg}`
))
.then((files) => files.forEach((file) => {
convertToWebp(file, folder.fsPath);
}));
});
context.subscriptions.push(convertFolderToWebpDisposable);
// 获取压缩图片目前已经使用了的次数
const getCompressUsedCountDisposable = vscode.commands.registerCommand('extension.getCompressUsedCount', () => {
// tinypng的API规定获取已使用的次数必须在验证身份的回调函数里或者命令执行的函数回调里
validate(
() => {
vscode.window.showInformationMessage(
`TinyPNG: You already used ${tinify.compressionCount} compression(s) this month.`
);
}
);
});
context.subscriptions.push(getCompressUsedCountDisposable);
}
到这里就开发完了,按F5运行起来看看吧~