手把手教你擼一個圖片壓縮工具

利用tingPng接口壓縮,支持壓縮失敗從新壓縮,支持命令行調用,支持npm 安裝調用。話很少說,開幹。javascript

壓縮展現

enter image description here

須要用到的工具

Listr 提供終端可執行的任務列表
cli-table' 容許從node.js腳本在命令行上呈現unicode輔助表(也就是表格呈現結果) chalk 命令行上呈現不一樣顏色的文字 minimist 解析命令行參數 commander 生成命令行命令。java

目錄結構

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 是代碼源文件。node

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代碼分析

實現的邏輯很是簡單,首先遞歸獲取inputimage地址,而後調用tinyPng的接口壓縮拿到壓縮後的圖片地址,而後請求圖片地址寫入目標文件。git

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實例,建立獲取圖片,壓縮圖片的taskgithub

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
}
複製代碼

獲取全部圖片的壓縮結果,而後繪製結果表格,而後開啓圖片的再次壓縮。npm

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函數,壓縮成功會返回壓縮成功以後的圖片地址,建立請求寫入指定的文件地址。json

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處理。promise

好了基本上就完成了壓縮小工具,gitHub地址 但願你們能喜歡。markdown

相關文章
相關標籤/搜索