findxc / blog

88 stars 5 forks source link

在 git commit 时自动压缩图片 #65

Open findxc opened 2 years ago

findxc commented 2 years ago

最近在研究图片压缩这块,因为如果项目中图片比较多的话,压缩后,页面下载的资源体积减小,加载速度还是能提高一丢丢的。

使用什么工具来压缩

一种是直接调用 TinyPNG 提供的 API ,会比较省事,缺点是由于有网络请求,会稍微慢一些,再就是一个 key 一个月只能免费压缩 500 张。(我最后选用的就是这个方案,毕竟图片不多,然后调用 API 比较省心)

另外一种是使用 imagemin 来在本地进行压缩,本地压缩会比较快,也没有压缩数量的限制,缺点是需要安装一些底层依赖,比如对于 macOS 来说,安装 imagemin-mozjpeg 插件之前需要先 brew install autoconf automake nasm 然后再安装插件才能成功。这样每个前端电脑上都需要安装底层依赖后才能压缩图片,稍微有点麻烦。

还有一种思路就是,在公司内网基于 imagemin 搭建一个压缩图片的服务,然后类似于 TinyPNG 那样通过 API 调用的方式来压缩图片 hhh ,因为在内网网络请求会相对快点,然后也没有压缩数量限制,然后前端本地也不需要安装底层依赖。如果确实公司里面这种压缩图片需求很旺盛的话其实搭建一下还是不错的。

这里贴一下 imagemin 使用的代码,就用 imagemin-mozjpeg 和 imagemin-pngquant 这两个插件就好了。注意 imagemin-mozjpeg 不要用默认参数,默认参数出来质量有点差,我试了下 { quality: 95 } 和 TinyPNG 差不多。实际使用时可以根据自己的需求来设。

import imagemin from 'imagemin'
import imageminMozjpeg from 'imagemin-mozjpeg'
import imageminPngquant from 'imagemin-pngquant'

imagemin(['images/*.{jpeg,jpg,png}'], {
  destination: 'build/images',
  plugins: [imageminMozjpeg({ quality: 95 }), imageminPngquant()],
}).then(() => {
  console.log('Images optimized')
})

在 post-commit 时压缩图片

git 本身是有一些 hook 的,比如我们一般会在 pre-commit 的时候用 eslint 检查代码和用 prettier 去格式化代码,然后在 commit-msg 的时候用 commitlint 去检查 commit 信息是否符合规范。

这里我们可以在 post-commit 时,也就是 commit 完成后去做图片的压缩。

为什么我会选择 post-commit 呢,因为我想把图片压缩放在一个单独的步骤中,这样图片压缩如果失败了,网络原因或者压缩脚本执行本身 bug 等,不影响代码的提交。这个时机见仁见智,也有很多就直接放 pre-commit 的,都可以的。

然后假设我们现在已经有一个压缩图片的脚本了,比如 tiny-image.sh ,那我们用 husky 这个库来配置 post-commit 时执行这个脚本就行了。

压缩图片的脚本咋写

为什么我选择了用 shell 脚本呢,因为 TinyPNG 的 API 使用提供的示例就是用 curl 去执行,我就顺势想到直接写个 shell 脚本来压缩图片就行了。

脚本完整内容如下:

# 在 .env.local 文件中需要有一行 tinyKey=xxx
tinyKey=$(cat .env.local | grep tinyKey | sed 's/tinyKey=\(.*\)$/\1/')

commitMsg="fix: auto tiny image"

# 因为在 post-commit 中执行 git commit 后又会触发 post-commit ,所以这里判断一下
lastCommit=$(git log -1 --pretty="%s")
if [ "$lastCommit" = "$commitMsg" ]; then
  exit 0
fi

list=$(git diff HEAD^ HEAD --name-only --diff-filter=AM | grep -E '\.(png|jpg|jpeg)$')

# $? 指上个命令的退出状态,或函数的返回值
# grep 返回 1 表示没有匹配的结果,返回 0 表示有匹配的结果,
if [ $? -eq 1 ]; then
  echo "🤪 no image to tiny"
  exit 0
else
  echo "🤪 start tiny image..."
fi

set -eo pipefail

for originF in $list; do
  tinyF=$originF.tiny

  origin_size=$(ls -l $originF | awk '{print $5}')

  if [ $origin_size -lt 10240 ]; then
    continue
  fi

  curl -sS --user api:$tinyKey --data-binary @$originF https://api.tinify.com/shrink | sed 's/^.*"url":"\(.*\)"}}$/\1/' | xargs curl -sS -o $tinyF

  tiny_size=$(ls -l $tinyF | awk '{print $5}')

  osize=$(ls -lh $originF | awk '{print $5}')
  wsize=$(ls -lh $tinyF | awk '{print $5}')

  percent=$(awk 'BEGIN{printf "%.1f%%\n",(('$origin_size'-'$tiny_size')/'$origin_size')*100}')
  echo "✅ replaced $originF, $osize --> $wsize, reduce $percent"

  mv $tinyF $originF
  git add $originF
done

git commit --no-verify -m "$commitMsg"

如果公司很多项目都需要做压缩图片的处理的话,可以把这个脚本做成一个 npm 包,这样使用时装一下包就行了,然后 tinyKey 可以从 package.json 或者环境变量中去取值。都是内网的话走 package.json 比较省事,要不还是走环境变量吧,安全,避免 tinyKey 泄漏。

如果是想批量把项目中已有的图片进行压缩,把上面脚本中 list 的赋值改一下就行了,比如说 list=$(find ./public ./src -size +10k -name '*.png' -o -size +10k -name '*.jpg' -o -size +10k -name '*.jpeg') ,这样 list 是指 public 和 src 下所有大于 10k 的图片。