利用tingPng接口压缩,支持压缩失败重新压缩,支持命令行调用,支持npm 安装调用。话不多说,开干。
压缩展示
需要用到的工具
Listr提供终端可执行的任务列表
cli-table'允许从node.js脚本在命令行上呈现unicode辅助表(也就是表格呈现结果)chalk命令行上呈现不同颜色的文字minimist解析命令行参数commander生成命令行命令。
目录结构
project
│ README.md
│ package.json
│ .gitignore
└───bin
│ │ esigntiny
└───demo
| │ images
| │ index.js
└───lib
| │ index.js
| │ initTiny.js
| │ esigntiny.js
| │ compressTiny.js
| │ compressFailedImg.js
| │ resultToChalk.js
| │ utils.js
bin用来存放可执行的node文件,用于在命令行调用方式。demo提供了可直接运行的脚本和需要压缩的图片。lib是代码源文件。
commander生成命令行命令
#!/usr/bin/env node //node环境下执行
const program = require('commander')
const esigntiny = require('../lib')
const path = require('path')
const argv = require('minimist')(process.argv.slice(2))
let input = argv.i || argv.input
let output = argv.o || argv.output
const { getCwd } = require('../lib/utils.js')
// 命令行提示
program
.version(require('../package.json').version, '-v --version')
.usage('<command> [options]')
.option('-i, --input', 'input directory [require]')
.option('-o, --output', 'output directory')
// 注册start命令
program.command('start').description('start compress images').action(start)
// 解析命令行
program.parse(process.argv)
// 命令行输入 esigntiny start执行此函数
function start() {
if (!input) {
console.log('require input directory')
return
}
input = path.resolve(getCwd(), input)
if (!!output) output = path.resolve(getCwd(), output)
esigntiny({
input: input,
output: output,
})
}
if (!program.args.length) {
program.outputHelp()
}
lib代码分析
实现的逻辑非常简单,首先递归获取
input的image地址,然后调用tinyPng的接口压缩拿到压缩后的图片地址,然后请求图片地址写入目标文件。
lib/index.js
const { normalizeOptions } = require('./utils.js')
const initTiny = require('./initTiny.js')
const compressTiny = require('./compressTiny.js')
const esigntiny = async function (options) {
// 初始化用户传参
options = normalizeOptions(options)
// 1.初始化程序,递归拿到图片地址
// 2.压缩图片
const taskExample = compressTiny(initTiny(options))
taskExample.run().catch((err) => console.log(err))
}
module.exports = esigntiny
index.js主要是标准化参数,创建listr实例,创建获取图片,压缩图片的task。
initTiny
const Listr = require('listr')
// 递归获取image地址
const { getImsges } = require('./utils.js')
module.exports = function (options) {
const taskExample = new Listr()
taskExample.add(getFiles(options))
return taskExample
}
function getFiles(options) {
return {
title: '获取所有图片数量',
task: (ctx, task) => {
ctx.options = options
ctx.images = getImsges(options.input)
task.title = `共找到${ctx.images.length}张图`
if (ctx.images.length === 0) {
Promise.reject('未找到图片')
}
},
}
}
compressTiny
const resultToChalk = require('./resultToChalk.js')
const compressFailedImg = require('./compressFailedImg.js')
const { tinyFun } = require('./utils.js')
module.exports = function (taskExample) {
taskExample.add({
title: '压缩图片',
task: async (ctx, task) => {
// 获取所有的图片的压缩结果
const imagesRsult = await Promise.all(tinyFun(ctx))
// 在次压缩失败的图片
const failedList = await resultToChalk(imagesRsult)
await compressFailedImg(failedList, ctx.options)
},
})
return taskExample
}
获取所有图片的压缩结果,然后绘制结果表格,然后开启图片的再次压缩。
tinyFun 图片压缩
/**
* 将每个文件压缩返回promise
* compressFile包装每一个请求链接
*/
const tinyFun = (ctx) => {
const { images, options } = ctx
return images.map((item) => {
return compressFile(item, options)
})
}
/**
* 压缩文件
*/
const compressFile = (filePath, options) => {
return new Promise((resolve, reject) => {
createReadStream(filePath).pipe(
request(parms, (res) => {
res.on('data', async (info) => {
try {
info = JSON.parse(info.toString())
// console.log('[[[[[]]]]]', info)
if (/^\s*</g.test(info) || info.error) {
resolve(
getMessage({
info,
filePath,
msg: '压缩失败',
code: 500,
})
)
return
}
resolve(await getImageData(info, options, filePath))
} catch (e) {
// console.log(e, '))0')
resolve(
getMessage({
info,
filePath,
msg: '接口请求被拒绝',
code: 500,
})
)
}
})
})
)
})
}
/**
* 读取图片,写入文件
*/
const getImageData = (imageInfo, options, filePath) => {
let output = options.output
const input = options.input
const imageUrl = imageInfo.output.url
const oldSize = (imageInfo.input.size / 1024).toFixed(2)
const newSize = (imageInfo.output.size / 1024).toFixed(2)
return new Promise((resolve, reject) => {
get(imageUrl, (res) => {
const outDir = path.dirname(output)
output = filePath.replace(input, output)
if (!existsSync(outDir)) {
mkdirSync(outDir)
}
res.pipe(createWriteStream(output))
res.on('end', function () {
resolve(
getMessage({
code: 200,
filePath,
msg: '压缩成功',
info: {
oldSize,
newSize,
imageUrl,
},
})
)
})
})
})
}
/**
* 接口的文案提示
*/
const getMessage = ({ msg, code, info, filePath }) => {
return {
code: code || 400,
msg: msg || '成功',
data: {
filePath,
info,
},
}
}
将每个文件压缩返回
promise,compressFile包装每一个请求链接,创建可读流请求tinyPng压缩图片,注意这里要将所有的结果都resolve出去,错误处理交给resultToChalk函数,压缩成功会返回压缩成功之后的图片地址,创建请求写入指定的文件地址。
resultToChalk结果打印
const Table = require('cli-table')
const chalk = require('chalk')
const path = require('path')
const { sleep } = require('./utils.js')
const headArr = {
head: ['name', 'status', 'old-size(kb)', 'new-size(kb)', 'compress ratio(%)'],
}
// const table = new Table({
// head: ,
// })
// 获取要打印的数据
const getSuccessInfo = (result) => {
const data = result.data
const info = data.info
const fileName = path.basename(data.filePath)
const compressRatio = parseFloat(
((info.oldSize - info.newSize) / info.oldSize) * 100
).toFixed(2)
return [fileName, 'success', info.oldSize, info.newSize, compressRatio]
}
module.exports = async function (imagesRsult) {
let totalNewSize = 0,
totalOldSize = 0,
successNum = 0,
failedNum = 0,
failedList = [],
table = new Table(headArr)
if (imagesRsult && imagesRsult.length) {
imagesRsult.forEach((result) => {
const filePath = result.data.filePath
if (result.code === 200) {
totalNewSize += +result.data.info.newSize
totalOldSize += +result.data.info.oldSize
successNum += 1
table.push(getSuccessInfo(result))
} else {
const fileName = path.basename(filePath)
failedNum += 1
failedList.push(filePath)
table.push([fileName, 'failed'])
}
})
}
await sleep(1000)
console.log(table.toString())
const resStr = `图片总数量:${
imagesRsult.length
}, 压缩成功:${successNum}, 压缩失败:${failedNum}, 压缩比:${
((totalOldSize - totalNewSize) / totalOldSize).toFixed(2) * 100
} (%)`
console.log(chalk.red(resStr))
// 2秒后开启失败的压缩
await sleep(2000)
return failedList
}
compressFailedImg 失败结果再次压缩
const { tinyFun } = require('./utils.js')
const Listr = require('listr')
const resultToChalk = require('./resultToChalk.js')
let compressTimes = 1
// 开启新的Listr,创建图片压缩task
module.exports = async function compressFailedImg(failedList, options) {
const taskExample = new Listr()
if (compressTimes-- > 0) {
taskExample.add({
title: '再次压缩失败图片',
task: async (ctx, task) => {
const imagesRsult = await Promise.all(
tinyFun({
images: failedList,
options,
})
)
// 在次压缩失败的图片
await resultToChalk(imagesRsult)
},
})
taskExample.run().catch((err) => {
console.log(err)
})
}
}
compressFailedImg开启新的压缩Listr,同样调用tinyFun获取压缩后的结果,然后交给resultToChalk处理。
好了基本上就完成了压缩小工具,gitHub地址 希望大家能喜欢。