基于tinify Api开发一个图片压缩的VS Code插件

222 阅读3分钟

VS Code编辑器右键选择图片或者整个文件夹,选择compress image或者compress images,图片压缩完替换掉原来的图

第一步:初始化项目

按照官网的步骤初始化项目,主要关注extension.ts

image.png

第二步:功能开发

extension.ts 是插件的入口文件,由于项目比较小,所以功能实现的方法都在这个文件中

import * as vscode from "vscode";

const tinify = require("tinify");
const path = require("path");
const fs = require("fs");

export function activate(context: vscode.ExtensionContext) {
  tinify.key = "这个地方的key需要自己去tinify的官网登录生成";

  // 验证身份
  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(
    "tinify.compressImage",
    (image) => {
      compressImage(image);
    }
  );
  context.subscriptions.push(compressImageDisposable);

  // 压缩文件夹里的所有图片
  const compressImageFolderDisposable = vscode.commands.registerCommand(
    "tinify.compressImageFolder",
    (folder) => {
      vscode.workspace
        .findFiles(
          new vscode.RelativePattern(folder.fsPath, `**/*.{png,jpg,jpeg,webp}`)
        )
        .then((files) =>
          files.forEach((file) => {
            compressImage(file, folder.fsPath);
          })
        );
    }
  );
  context.subscriptions.push(compressImageFolderDisposable);

  // 获取压缩图片目前已经使用了的次数
  const getCompressUsedCountDisposable = vscode.commands.registerCommand(
    "tinify.getCompressUsedCount",
    () => {
      // tinypng的API规定获取已使用的次数必须在验证身份的回调函数里或者命令执行的函数回调里
      validate(() => {
        vscode.window.showInformationMessage(
          `TinyPNG: You already used ${tinify.compressionCount} compression(s) this month.`
        );
      });
    }
  );
  context.subscriptions.push(getCompressUsedCountDisposable);
}

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}!`
        );
      }
    });
};

export function deactivate() {}

第三步:插件配置

package.json 包含插件的配置信息(插件命令、快捷键、菜单均在此配置)

  • menus

    • editor/context 编辑器上下文菜单(这里的命令配置主要是在资源区点右键会出现的命令)

      • when 控制菜单何时出现
      • command 定义菜单被点击后要执行什么操作
      • group 定义菜单分组
    • editor/title/context 编辑器上下文菜单(这里的命令配置主要是在标题栏点右键会出现,内容同上)

{
  "name": "tinify",
  "displayName": "tinify",
  "description": "",
  "repository": {},
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.81.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "tinify.compressImage",
        "title": "compress image",
        "category": "compress"
      },
      {
        "command": "tinify.compressImageFolder",
        "title": "compress images",
        "category": "compress"
      },
      {
        "command": "tinify.getCompressUsedCount",
        "title": "get compress used count",
        "category": "compress"
      }
    ],
    "menus": {
      "commandPalette": [
        {
          "command": "tinify.getCompressUsedCount"
        },
        {
          "command": "tinify.compressImage",
          "when": "False"
        },
        {
          "command": "tinify.compressImageFolder",
          "when": "False"
        }
      ],
      "editor/title/context": [
        {
          "when": "resourceLangId == image_file",
          "command": "tinify.compressImage",
          "group": "1_modification"
        }
      ],
      "explorer/context": [
        {
          "when": "resourceLangId == image_file",
          "command": "tinify.compressImage",
          "group": "1_modification"
        },
        {
          "when": "explorerResourceIsFolder",
          "command": "tinify.compressImageFolder",
          "group": "1_modification"
        }
      ]
    },
    "languages": [
      {
        "id": "image_file",
        "extensions": [
          ".png",
          ".jpg",
          ".jpeg",
          ".webp"
        ]
      }
    ],
    "configuration": {
      "title": "TinyPNG Configuration",
      "properties": {
        "tinypng.forceOverwrite": {
          "type": "boolean",
          "default": true,
          "description": "Judge whether to overwrite the uncompressed picture or to create a new one"
        }
      }
    }
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "npm run compile && npm run lint",
    "lint": "eslint src",
    "test": "vscode-test"
  },
  "devDependencies": {
    "@types/mocha": "^10.0.8",
    "@types/node": "20.x",
    "@types/vscode": "^1.81.0",
    "@typescript-eslint/eslint-plugin": "^8.7.0",
    "@typescript-eslint/parser": "^8.7.0",
    "@vscode/test-cli": "^0.0.10",
    "@vscode/test-electron": "^2.4.1",
    "eslint": "^9.11.1",
    "typescript": "^5.6.2"
  },
  "dependencies": {
    "tinify": "^1.7.1"
  }
}

第四步:本地调试及使用

  1. 安装vsce打包工具
npm install -g vsce
  1. 执行打包命令
vsce package

image.png 3. 在vscode中安装此插件 点右键选择项目中生成的vsix文件安装后就可以正常使用此插件了。

image.png

(因为这个插件主要是用于开发中的图片压缩,所以并没有发布到vscode插件商店,需要发布的话可以搜索一下发布教程)