探索前端最佳图片压缩方案

5,582 阅读4分钟

背景

平时从蓝湖下载下来的图片,个头都非常大,身边同事基本上都会手动压缩一下再用,或者压缩完成后上传到CDN中。前段时间做了一个自动上传CDN的插件,把手动上传这一步给省了,现在想着能不能把手动压缩这一步也省了,图片拿过来直接用,自动压缩自动上传岂不美哉?

何时压缩?

和同事激烈讨论一番后,觉得有三个时间点适合做这项工作:

  1. 通过VSCODE插件在下载图片后压缩
  2. 在webpack/vite打包时压缩
  3. pre commit阶段压缩

第1点有悖我们做这项工作的初衷,自动就是配置好后一点事情都不用做,就算点个按钮那都不能算作自动。
在打包阶段压缩,确实实现了自动化,但是每次打包都会全部压缩一遍,会有效率问题和重复压缩的问题。
最终决定,还是在commit之前进行压缩操作,这样图片提交到代码库时就是压缩过的,之后也不会出现重复压缩的情况。

如何实现?

这里就要用到两款插件huskylint-staged。做过提交时对代码进行lint的同学对这两款插件一定不会陌生,它们可以在代码提交时对代码进行一些校验,通过才能提交,以此来避免错误代码提交到仓库中。

我们可以借助这两个插件的能力,在pre commit阶段,通过lint-staged获取到暂存区的图片资源路径,然后对图片进行压缩并替换掉源文件,再将替换后的文件添加到暂存区。lint-staged执行完成后会将我们压缩后的图片一并提交到代码库。

至于用什么压缩,那还得是tinypng,它也提供了一款npm包,可以在node中调用它的压缩图片接口。

前期准备

在完成这项工作之前,我们需要一个示例项目,配置好huskylint-staged

安装、配置husky

大家准备好一个项目,然后先安装husky

npm install husky -D

初始化husky

npm pkg set scripts.prepare="husky install"
npm run prepare

添加pre-commit:

npx husky add .husky/pre-commit "npx lint-staged"
git add .husky/pre-commit

安装lint-staged

npm install --save-dev lint-staged

package.json中添加一条:

{
  "lint-staged": {
    "*.{png,jpg,jpeg}": "tiny-files --key pVP3cH1vNnLscYbkC8wNQC7KcBk8MT2b",
  }
}

这项配置的意思是,在lint-staged被执行时,会筛选暂存区中的文件,当暂存区存在png、jpg、jpeg文件时,运行tiny-files --key pVP3cH1vNnLscYbkC8wNQC7KcBk8MT2b指令,并将相关文件当做参数传入。

安装tiny-files

npm i tiny-files -D

这个tiny-files是什么?我们接下来说。

关于tiny-files

tiny-files是一个压缩图片工具,它封装了tinypng的api,能够实现批量图片压缩并替换源文件。

GitHub地址:leglegend/tiny-files: 在commit之前压缩图片 (github.com)

key

tinypng的api需要key才能调通,申请key非常简单,只需要在TinyPNG官网填入邮箱即可。该key需作为参数传入tiny-files中,以使其正常工作。

原理

tiny-files的功能实现原理非常简单,先从参数中拆分出key,将key值传入tinify(tinypng的npm包)如果没有key则抛出异常:

const tinify = require('tinify')

// 获取参数
const args = process.argv.slice(2)
const argsKeyIndex = args.findIndex((arg) => ['-k', '--key'].includes(arg))
const argsKeyValue = argsKeyIndex !== -1 ? args[argsKeyIndex + 1] : undefined

if (!argsKeyValue && (!package.tinyFiles || !package.tinyFiles.key))
  throw Error('tiny-files缺少key')

tinify.key = argsKeyValue || package.tinyFiles.key

通过参数读取文件路径列表,并筛选出图片文件,没有图片则退出:

// 过滤图片
const files = args.filter((file) => /\.(png|jpg|jpeg)$/.test(file))
if (files.length === 0) {
  process.exit(0)
}

将图片压缩,并将压缩完成后的图片加入暂存区:

const fs = require('fs')
const tinify = require('tinify')
const { spawnSync } = require('child_process')

const tasks = []
files.forEach((path) => {
  tasks.push(tinify.fromFile(path).toFile(path))
})

Promise.all(tasks).then(() => {
  const { status } = spawnSync('git', ['add', ...files])
  process.exit(status)
})

至此,我们需要的功能就全部完成了。

体验一下

将上方的配置完全配置后,在项目中添加一个图片,在提交时,图片会自动压缩。

写在最后

上面只是个人对压缩图片如何融入工作流程中的一点见解,如果大家有更好的方案,欢迎指正指出!
如果觉得还实用,麻烦给这个仓库点个星:leglegend/tiny-files: 在commit之前压缩图片 (github.com),谢谢支持!