手把手教你用node撸一个图片压缩工具
上篇文章中我们提到了用node撸一个简易的爬虫,本次基于上一篇文章中的项目get_picture给大家分享下我是如何用node撸一个图片压缩工具的。原文链接leeing.site/2018/10/27/…
历史:
《手把手教你用node撸一个简易的headless爬虫cli工具》
tinypng
依然是先介绍一下工具,本次我们主要用到了 tinypng
这个工具。tinypng是一个主流的图片压缩工具,他可以实现高保真的压缩我们的图片,一般我们可以进入他的官网tinypng.com/压缩图片,手动点击上传,但是每次只能压缩20张,这对于追求方便的我们来说肯定是不能满足的。我们需要一次性将所有图片都压缩!
这怎么办呢?tinypng官网十分的人性化,提供了各种服务端直接调用的接口,我们点开他的文档看一看,找到node.js,通过npm i --save tinify
安装在我们的项目中,其次可以看到他提供了各种各样的功能,包括压缩图片
、resize图片
、上传cdn
等。我们主要用到了他的压缩图片
、验证key
、查看已用数
。
目录结构
|-- Documents |-- .gitignore |-- README.md |-- package.json |-- bin | |-- gp |-- output | |-- .gitkeeper |-- src |-- app.js |-- clean.js |-- imgMin.js |-- index.js |-- config | |-- default.js |-- helper |-- questions.js |-- regMap.js |-- srcToImg.js |-- tinify.js
基于上一个项目,我们新增了两个文件
- /src/imgMin.js。即我们的主文件。
- /src/helper/tinify.js。主要用于操作tinypng的相关API
主文件
在主文件中,我们主要用到了node
的fs模块
。
首先我们会判断输入的key是否有效,其次我们会判断该key剩余可用数是不是小于0,如果没问题的话,我们就开始查找检索路径下的所有文件。
检索路径
首先我们会通过fs.stat
判断该路径是否是文件夹,如果是,则通过fs.readdir
获取当前文件列表,遍历后然后将其传给获取图片方法。注意这边有个坑点,因为我们的操作几乎都是异步操作,所以我一开始也很理所当然的用了forEach来遍历,伪代码如下
files.forEach(async (file) => { await getImg(file); });
后来发现,这种写法会导致await并不能如我们预期的阻断来执行,而是变成了一个同步的过程(一开始的预期是一张图片压缩输出完才执行第二张,虽然这样会导致很慢。所以后面还是换成了同步压缩),这是因为forEach
可以理解为传入一个function,然后在内部执行循环,在循环中执行function并传回index和item,如果传入的是async函数的话,则其实是并行执行了多个匿名async函数自调,因此await无法按照我们预期的来执行。所以该处我们采用for-of
循环,伪代码如下
for(let file of files){ await getImg(file); }
获取图片
在获取图片中,我们依然会通过fs.stat
来判断,如果当前文件依然是个文件夹,我们则递归调用findImg
检索其下的文件,如果是图片,先判断当前累计图片总数有没有超过剩余数的最大值(如果使用异步压缩,则不需要进行这一步,因为每一次图片处理都是等待上一张图片处理完成后再进行处理;如果是同步压缩,则必须要这一步,否则如果压缩过程中超数量了,会导致整批压缩失败),如果没有超过,则通过调用tinify.js
中的imgMin
方法开始进行压缩。
压缩图片
在这一步中,我们先通过fs.readFile
读取文件内容sourceData,再通过tinypng的APItinify.fromBuffer(sourceData).toBuffer((err, resultData) => {})
方法获取图片压缩后的数据resuleData,最后通过fs.writeFile
对原图片进行覆盖。需要注意一点,async/await中,只有遇到await才会等待执行,并且await后面需要跟一个promise对象,因此,我们把readFile
、tinify.fromBuffer(sourceData).toBuffer((err, resultData) => {})
、fs.writeFile
用promise进行封装。
至此,我们的主程序就大功告成了!怎么样,是不是依然非常简单。
最后只要在commander中加入我们的新命令就好了。
/src/imgMin.js代码如下:
const path = require('path'); const fs = require('fs'); const chalk = require('chalk'); const defaultConf = require('./config/default'); const { promisify } = require('util'); const readdir = promisify(fs.readdir); const stat = promisify(fs.stat); const regMap = require('./helper/regMap'); const { validate, leftCount, imgMin } = require('./helper/tinify'); class ImgMin { constructor(conf) { this.conf = Object.assign({}, defaultConf, conf); this.imgs = 0; } async isDir(filePath) { try { const stats = await stat(filePath); if(stats.isDirectory()){ return true; } return false; } catch (error) { return false; } } async findImg(filePath) { try { const isDirectory = await this.isDir(filePath); if(!isDirectory){ return; } const files = await readdir(filePath); for(let file of files){ // 这里不能用forEach,只能用for循环 // 加上await,则是一张张异步压缩图片,如果中间出错,则部分成功 // 不加await,则是同步发起压缩图片请求,异步写入,如果中间出错,则全部失败 // 这里为了压缩更快,采用同步写法 // await this.getImg(file); const fullPath = path.join(filePath, file); this.getImg(fullPath); } } catch (error) { console.log(error); } } async getImg(file) { const stats = await stat(file); // 如果是文件夹,则递归调用findImg if(stats.isDirectory()){ this.findImg(); }else if(stats.isFile()){ if(regMap.isTinyPic.test(file)){ this.imgs ++; const left = leftCount(); // 剩余数判断,解决同步时剩余数不足导致的全部图片压缩失败问题 if(this.imgs > left || left < 0){ console.log(chalk.red(`当前key的可用剩余数不足!${file} 压缩失败!`)); return; } await imgMin(file); }else{ console.log(chalk.red(`不支持的文件格式 ${file}`)); } } } async start() { try { const isValidated = await validate(this.conf.key); if(!isValidated){ return; } const filePath = this.conf.imgMinPath; await this.findImg(filePath); } catch (error) { console.log(error); } } } module.exports = ImgMin;
/src/helper/tinify.js代码如下:
const fs = require('fs'); const tinify = require('tinify'); const chalk = require('chalk'); const { promisify } = require('util'); const readFile = promisify(fs.readFile); function setKey(key) { tinify.key = key; } async function validate(key) { console.log(chalk.green('正在认证tinyPng的key...')); setKey(key); return new Promise(resolve => { tinify.validate((err) => { if(err){ console.log(err); return resolve(false); } console.log(chalk.green('认证成功!')); const left = leftCount(); if(left <= 0){ console.log(chalk.red('当前key的剩余可用数已用尽,请更换key重试!')); return resolve(false); } console.log(chalk.green(`当前key剩余可用数为 ${left}`)); resolve(true); }); }); }; function compressionCount() { return tinify.compressionCount; }; function leftCount() { const total = 500; return total - Number(compressionCount()); }; function writeFilePromise(file, content, cb) { return new Promise((resolve, reject) => { fs.writeFile(file, content, (err) => { if(err){ return reject(err); } cb && cb(); resolve(); }); }); }; function toBufferPromise(sourceData) { return new Promise((resolve, reject) => { tinify.fromBuffer(sourceData).toBuffer((err, resultData) => { if (err) { return reject(err); } resolve(resultData); }) }); }; async function imgMin(img) { try { console.log(chalk.blue(`开始压缩图片 ${img}`)); const sourceData = await readFile(img); const resultData = await toBufferPromise(sourceData); await writeFilePromise(img, resultData, () => console.log(chalk.green(`图片压缩成功 ${img}`))); } catch (error) { console.log(error); } }; module.exports = { validate, compressionCount, leftCount, imgMin };
命令行工具
在index.js中,我们加入以下代码
program .command('imgMin') .alias('p') .option('-k, --key [key]', `Tinypng's key, Required`) .option('-p, --path [path]', `Compress directory. By default, the /images in the current working directory are taken. Please enter an absolute path such as /Users/admin/Documents/xx...`) .description('Compress your images by tinypng.') .action(options => { let conf = {}; if(!options.key){ console.log(chalk.red(`Please enter your tinypng's key by "gp p -k [key]"`)); return; } options.key && (conf.key = options.key); options.path && (conf.imgMinPath = options.path); const imgMin = new ImgMin(conf); imgMin.start(); });
commander具体的用法本章就不再重复了,相信有心的同学通过上章的学习已经掌握基本用法了~
这样,我们就完成了我们的需求,再将其更新到npm中,我们就可以通过gp p -k [key]
来压缩我们的图片。
项目下载
npm i get_picture -g
参考链接
- 该项目的git链接 github.com/1eeing/get_…
- tinypng官网 tinypng.com/
- commander git链接 github.com/tj/commande…
相关推荐
-
简单了解JS中的几种遍历 框架
2019-3-11
-
更好用的css命名方式——BEM命名 框架
2019-6-11
-
RN 技术探索:Hermes Engine 初探 框架
2019-8-16
-
vue初始化项目,构建vuex的后台管理项目架子 框架
2019-6-2
-
Redux入门教程(快速上手) 框架
2018-11-10
-
超详细多表头位置计算实现 框架
2019-9-11
-
打造 Vue.js 可复用组件 框架
2019-3-10
-
vuex持久化插件-解决浏览器刷新数据消失问题 框架
2019-4-15
-
Node.js ELK 日志规范 框架
2019-8-21
-
从零开始搭建Vue组件库 VV-UI 框架
2019-3-7