團隊開啓了一個新項目,希望能在原來項目的工程化基礎上再進一步,於是想到了圖片自動壓縮。
這裏的圖片自動壓縮並不是在webpack構建階段壓縮,而是在git commit
的時候進行。
pre-commit
pre-commit 是git hook 衆多鉤子中的一個,在每次 git commit
前執行,可以是shell等任何可執行的腳本文件,通過返回0 or 1 來表示commit是否通過。在bash中,非零返回值代表失敗.
pre-commit.js
市面上有許多通過node.js來封裝pre-commit的npm包,我使用了功能最精簡的pre-commit.js
, 按README在package.json中簡單配置後,可以看到 .git/hooks/pre-commit腳本如下所示:
#!/bin/bash
./node_modules/pre-commit/hook
RESULT=$?
[ $RESULT -ne 0 ] && exit 1
exit 0
pre-commit.js重寫了原來的示例鉤子,在git commit
時執行自己的/hook
腳本.
hook bash
# 之前的代碼主要確認node環境
# git commit --dry-run 的情況(測試性提交,執行動作不產生結果),不對提交作任何處理
if [[ $* == *--dry-run* ]]; then
if [[ -z "$BINARY" ]]; then # 但還是要檢查下NODE是否存在,不存在返回非零值代表失敗
exit 1
fi
else
# 用NODE 執行 'pre-commit/index.js' 模塊
"$BINARY" "$("$BINARY" -e "console.log(require.resolve('pre-commit'))")"
fi
最終pre-commit/index.js
將讀取package.json中配置好的路徑來執行指定的腳本。
pre-commit/index.js
// 執行自定義鉤子腳本的核心部分
Hook.prototype.run = function runner() {
var hooked = this;
(function again(scripts) {
// 腳本數組爲空,則返回0,執行本次 commit .
if (!scripts.length) return hooked.exit(0);
// 否則彈出一個腳本來執行
var script = scripts.shift();
// 使用的是異步執行子進程方法child_process.spawn.
spawn(hooked.npm, ['run', script, '--silent'], {
env: process.env,
cwd: hooked.root,
stdio: [0, 1, 2]
}).once('close', function closed(code) {
// 監聽了執行結束的close事件,遞歸繼續執行下一個腳本
if (code) return hooked.log(hooked.format(Hook.log.failure, script, code));
again(scripts);
});
})(hooked.config.run.slice(0));
};
從上述代碼中可以看出,即使我們定義的鉤子腳本有異步處理邏輯也是可以的,因爲整個腳本都是新開了一個異步的子進程來執行的,通過監聽close事件來確認異步邏輯執行完畢,最終exit(0)通知成功,執行commit。
TinyPNG
TinyPNG是一個專門進行有損壓縮PNG圖像的網站,經過它特有的算法,能夠降低圖像70% - 80%的大小。TinyPNG提供在線API,每月500張免費壓縮額度,可以通過npm包tinify.js調用。
const execSync = require('child_process').execSync;
const path = require('path');
const tinify = require('tinify');
tinify.key = 'tDvJPN2Fd4yjtHJDSwQQtQQZWTldVls7'; // tinypng提供的用戶key
console.log('pre-commit hook start! \n');
let diff = getDiffFiles();
compressPics(diff);
function getDiffFiles(type) {
// pre-commit鉤子本身不傳遞參數
//https://git-scm.com/docs/githooks/1.7.4#_pre_commit
// 所以通過git diff 命令拿到本次提交涉及的變動文件
let root = process.cwd();
let files = execSync('git diff --cached --name-status HEAD').toString().split('\n');
let result = [];
// add, delete, modified, renamed, copied
type = type || 'admrc';
let types = type.split('').map(t => {
return t.toLowerCase();
});
files.forEach(file => {
if (!file) {
return;
}
let temp = file.split(/[\n\t]/);
let status = temp[0].toLowerCase();
let filePath = root + '/' + temp[1];
let extName = path.extname(filePath).slice(1);
if (types.length && ~types.indexOf(status)) {
result.push({
status: status, // admrc中的一個
path: filePath, // 絕對路徑
subpath: temp[1], // 相對路徑
extName: extName, // 擴展名
})
}
});
return result;
}
function compressPics(files) {
let pngs = files.filter(file => file.extName === 'png' && ['a', 'm'].includes(file.status));
console.log(pngs);
pngs.forEach(file => {
let source = tinify.fromFile(file.subpath);
source.toFile(file.subpath)
.then(res => {
++flag;
console.log(file.subpath + ' has been compressed !');
execSync('git add .', {encoding: 'utf8'});
})
.catch(err => {
console.log(err);
process.exit(1);
})
})
}
該方法使用了帶key的在線接口,而且大大延長commit時長,還受到每月500張限制。
imagemin-pngquant.js
鑑於tinipng的缺點,我換了一個npm包,使壓縮過程能夠在本地完成。
const imagemin = require('imagemin');
const imageminPngquant = require('imagemin-pngquant');
let parentFolder = {};
pngs.forEach(x => { // 根據不同父級目錄分類
let pf = x.subpath.slice(0, x.subpath.lastIndexOf('/'));
parentFolder[pf] ? parentFolder[pf].push(x.subpath) : parentFolder[pf] = [x.subpath];
});
for (let pf in parentFolder) {
imagemin(parentFolder[pf], { // 原圖片目錄
destination: pf, // 生成圖片的目錄
plugins: [
imageminPngquant({
speed: 1,
quality: [0.40, 0.50],
})
]
})
.then(res => {
execSync('git add . ');
})
.catch(err => {
console.log(err);
process.exit(1);
})
}
實際檢驗在上述配置下,imagemin-pngquant
具有80%的壓縮率,高於tinypng
,唯一的例外是一張色彩空間爲RGB
的圖片,這張圖片在同批測試中僅得到了20%的壓縮率,在tinyPNG
中雖然獲得了50%的壓縮率,但也屬於同批圖片中效率最低的一張。
因此在UI交付圖片時,我們應該關注下色彩空間是否爲sRGB,如果不是,應該打回重新切。正常情況下,用於Web程序的圖片,顏色通道應爲sRGB,因爲絕大部分瀏覽器是使用sRGB色彩空間來渲染圖片的。
Why Should We Use sRGB in Broswer
Preparing Images Color for the Web
除了壓縮效率壓過tinyPNG
一頭,在時間上iamgemin-pngquant
幾乎是秒轉換,同批圖片通過tinyPNG
的api轉換需要花費10 - 20s。
webp
將png
格式的圖片轉換爲webp
格式,可以節約更多的空間,在lighthouse
性能檢測中拿到更高的得分,但IOS
系統目前還是無法直接兼容它,需要做轉換,很難在工作流中做自動化的處理。
The End
不知道你的團隊是如何處理圖片壓縮這一塊的工作的?希望能一起分享