前言
最近做私活,因为图片资源切的不够精细,导致客户反应加载速度慢。
所以,本篇以一次 图片压缩 需求为起因,讲下一名普通开发者,如何锻炼自己,让自己在普通的环境中努力成长。
在线图片压缩
负责项目的朋友甩给我一个在线图片压缩网站(https://tinypng.com)
意图很明显,但难道让我把图片一张张上传上去压缩下载吗?
我依然不是前端小白,即使肉眼一扫基本知道哪些图片需要处理,但这种重复劳动虽然管用但很“不健康”,对于自我成长,首先要有强烈的意愿去杜绝这种最低效的劳动。
我尝试去找网站相关的开发者栏目,但觉得麻烦,又要申请 key,又要 curl 调链接:
所以换我们前端比较熟悉的 gulp 入手。
gulp.js 脚本
项目是基于 vue-cli 的骨架,不想加入 webpack 配置增加复杂度,所以就回归到简单上手的 gulp 。
因为我们是有经验的开发(百度一下),所以很快就找到能用的模块:gulp-image
const gulp = require('gulp');
const image = require('gulp-image');
gulp.task('image', function () {
gulp.src('./fixtures/*')
.pipe(image())
.pipe(gulp.dest('./dest'));
});
当然如果嫌文档不太友好,也可以试下 gulp-imagemin。
具体如何使用就暂且省略,因为这些不是本文重点。
很好,我们不再 人工处理 这些问题,有了些“经验”的提升,但依然停留在使用 API 层面,任何一个人都能代替我们。
那么第二步,请在工作中 抽出时间来思考这些工具是怎么解决问题的,而不是止于寻找工具,调用它们完事。
自定义脚本
试问自己这些问题:
如果没有这些工具怎么办?我们肯定要自己写脚本。
但我们又不了解 png、jpg 之类格式的压缩算法,那怎么办?
这里就要提到,大多数“核心”的模块基本都被人实现过一遍了,而我们常用的工具都是对这些模块的再次封装,就好比小米手机对各个厂商的零部件整合一样。
但为什么有些工具那么“出众”呢?我认为就是他们创造了类似 miui 之类的玩意,让我们使用者用起来更加舒服,方便。
确定使用哪些基础库
好,现在开始分析上面 gulp 模块内部调用哪些“核心”库:
gulp-image 相关依赖:
// gulp-image
"dependencies": {
"gifsicle": "^5.0.0",
"jpeg-recompress-bin": "^4.0.0",
"mozjpeg": "^6.0.1",
"pngquant-bin": "^5.0.2",
...
}
gulp-imagemin 相关依赖:
// gulp-imagemin
"dependencies": {
"imagemin": "^7.0.0",
...
},
"devDependencies": {
"imagemin-pngquant": "^8.0.0",
...
},
"optionalDependencies": {
"imagemin-gifsicle": "^7.0.0",
"imagemin-mozjpeg": "^8.0.0",
"imagemin-optipng": "^7.0.0",
"imagemin-svgo": "^7.0.0"
}
我看了 imagemin-pngquant 和 imagemin-mozjpeg 相关依赖,发现和 gulp-image 用的一样,所以基于这些模块来作为我们自定义脚本的基础。
开始动手
这里我选择了 imagemin 相关的基础模块,还引入 node.js 相关文件操作,
//定义模块
const imagemin = require('imagemin')
const imageminJpegtran = require('imagemin-jpegtran')
const imageminPngquant = require('imagemin-pngquant')
const path = require('path')
const fs = require('fs')
定义一些基础信息
const imageRoot = path.resolve(__dirname, 'src/assets/images')
const newImageDirtory = path.resolve(__dirname, 'tiny')
const parseFilesPromise = []
定义主要方法
// 创建目录
function createDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
}
// 读取目录
function readDirtory(dir, newDir) {
createDir(newDir)//创建目录
const dirData = fs.readdirSync(dir)
for (let innerDir of dirData) {
const totalDir = path.resolve(dir, innerDir)
const childDir = path.resolve(newDir, innerDir)
const stat = fs.statSync(totalDir)
if (stat.isDirectory()) {
// 递归子文件夹
readDirtory(totalDir, childDir)
} else {
// 解析文件
if (['.png', '.jpeg'].indexOf(path.extname(totalDir)) !== -1) {
parseFiles(totalDir,childDir)
}
}
}
}
// 准备解析文件
function parseFiles(originFile,destFile){
parseFilesPromise.push(
imagemin([originFile], {
destination: path.dirname(destFile),
glob: false,
plugins: [
imageminJpegtran(),
imageminPngquant({
quality: [0.6, 0.8]
})
]
})
)
}
最后执行:
readDirtory(imageRoot, newImageDirtory)
Promise.all(parseFiles)
.then(data => {})
.catch(err => {
console.log(err)
})
似乎没什么问题,但在实际中遇到了某些图片太大,使得模块解析报错,导致最后 promise.all break 掉。
优化
存放于 promise 队列中的 promise 对象额外封装一个 promise,用来解析中间出现的 error :
parseFilesPromise.push(
new Promise((resolve, reject) => {
return imagemin([totalDir], {
destination: path.dirname(childDir),
glob: false,
plugins: [
imageminJpegtran(),
imageminPngquant({
quality: [0.6, 0.8]
})
]
})
.then(data => {
resolve(data)
})
.catch(err => {
reject(totalDir)
})
})
)
在 promise.all 中,通过数组的 map 处理异常熔断,打印出错误资源文件路径:
Promise.all(
parseFiles.map(p => p.catch(error => console.log('解析错误:', error)))
)
最后,手动处理下有问题的图片资源。
总结
目前我停留在“自己写脚本”这一步,用了 node.js 中一两个 api,也搭上了几个图片压缩的库。相比在线工具和直接 gulp,现在脚本的“机动性”好了不少,不再局限别人工具的束缚。
但真的就这样完了吗?
这里又要再提下一步:重构代码,达到像别人一样能给他们开箱即用的效果,这样自己才能算解决了“图片压缩”这个需求,甚至有必要,去研究下压缩的算法,或者试图写个 exe 之类的执行程序。
相信各位遇到过各种需求,需要解决各种难题,但真正“吃透”一个问题少之又少,而那些象牙塔的同学却在越走越高,我们的差距就是如此被拉大的,所以:好好学习,天天向上,与各位共勉。
本文使用 mdnice 排版