从“图片压缩”,看程序员的自我成长
在线图片压缩
负责项目的朋友甩给我一个在线图片压缩网站(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 之类的执行程序。
相信各位遇到过各种需求,需要解决各种难题,但真正“吃透”一个问题少之又少,而那些象牙塔的同学却在越走越高,我们的差距就是如此被拉大的,所以:好好学习,天天向上,与各位共勉。
转自https://www.toutiao.com/i6823001585723900420/?timestamp=1593354080&app=news_article&group_id=6823001585723900420&use_new_style=1&req_id=202006282221200100160322061541F1D2
喜欢这篇文章?欢迎打赏~~