TingPng interface compression, support compression failure to compress, support command line call, support NPM installation call. Don’t say much, do something.

Compression show

Tools you need to use

Listr provides a list of terminally executable tasks cli-table’ allows unicode auxiliary tables (i.e. table rendering results) to be rendered on the command line from the Node.js script Generate command line commands.

The directory structure

Project │ README. Md │ package. Json. │ gitignore └ ─ ─ ─ bin │ │ esigntiny └ ─ ─ ─ the demo | | │ images │ index. The js └ ─ ─ ─ the lib | │ Index. js | │ inittiny.js | │ esigntiny.js | │ compresstiny.js | │ compressfailedimg.js | │ resultToChalk. Js | │ utilsCopy the code

Bin is used to store the executable Node file, which is used to invoke mode on the command line. Demo provides scripts that can be run directly and images that need to be compressed. Lib is the code source file.

Commander Generates a command line command

#! Run the /usr/bin/env node command in //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')

// Command line prompt
program
  .version(require('.. /package.json').version, '-v --version')
  .usage('<command> [options]')
  .option('-i, --input'.'input directory [require]')
  .option('-o, --output'.'output directory')

// Register the start command
program.command('start').description('start compress images').action(start)

// Parse the command line
program.parse(process.argv)

Enter esigntiny start on the command line to execute this function
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() }Copy the code

Lib code analysis

The implementation logic is very simple. First, obtain the image address of the input recursively, then call tinyPng’s interface to compress and get the compressed image address, and then request the image address to be written into the target file.

lib/index.js

const { normalizeOptions } = require('./utils.js')
const initTiny = require('./initTiny.js')
const compressTiny = require('./compressTiny.js')

const esigntiny = async function (options) {
  // Initializes user parameter transmission
  options = normalizeOptions(options)
  // 1. Initialize the program recursively to get the image address
  // 2. Compress images
  const taskExample = compressTiny(initTiny(options))

  taskExample.run().catch((err) = > console.log(err))
}

module.exports = esigntiny
Copy the code

Index.js is used to normalize parameters, create listr instances, and create tasks that fetch and compress images.

initTiny

const Listr = require('listr')
// Get the image address recursively
const { getImsges } = require('./utils.js')

module.exports = function (options) {
  const taskExample = new Listr()
  taskExample.add(getFiles(options))
  return taskExample
}

function getFiles(options) {
  return {
    title: 'Get all images'.task: (ctx, task) = > {
      ctx.options = options
      ctx.images = getImsges(options.input)
      task.title = ` were found${ctx.images.length}Picture `
      if (ctx.images.length === 0) {
        Promise.reject('Picture not found')}},}}Copy the code

compressTiny

const resultToChalk = require('./resultToChalk.js')
const compressFailedImg = require('./compressFailedImg.js')
const { tinyFun } = require('./utils.js')

module.exports = function (taskExample) {
  taskExample.add({
    title: 'Compress image'.task: async (ctx, task) => {
	  // Get compression results for all images
      const imagesRsult = await Promise.all(tinyFun(ctx))
      // Failed to compress the image at the same time
      const failedList = await resultToChalk(imagesRsult)
      await compressFailedImg(failedList, ctx.options)
    },
  })

  return taskExample
}
Copy the code

Get the compression result of all images, then draw the result table, and then enable the image compression again.

TinyFun image compression

/** * Compress each file returns a promise * compressFile wraps each request link */
const tinyFun = (ctx) = > {
  const { images, options } = ctx
  return images.map((item) = > {
    return compressFile(item, options)
  })
}

/** * compressed file */
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: 'Compression failed'.code: 500,}))return
            }
            resolve(await getImageData(info, options, filePath))
          } catch (e) {
            // console.log(e, '))0')
            resolve(
              getMessage({
                info,
                filePath,
                msg: 'Interface request rejected'.code: 500,}))}})}))})})}/** * read the image, write the file */
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: 'Compression succeeded'.info: {
              oldSize,
              newSize,
              imageUrl,
            },
          })
        )
      })
    })
  })
}
/** * interface text prompt */
const getMessage = ({ msg, code, info, filePath }) = > {
  return {
    code: code || 400.msg: msg || 'success'.data: {
      filePath,
      info,
    },
  }
}
Copy the code

CompressFile wraps each request link and creates a readable stream request tinyPng compressed image. Resolve all results and hand error handling to resultToChalk. Compression success returns the image address after compression success, create request to write the specified file address.

ResultToChalk Indicates the result

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: ,
// })

// Get the data to print
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 = 'Total number of pictures:${ imagesRsult.length }, compression success:${successNum}, compression failed:${failedNum}, compression ratio:${
    ((totalOldSize - totalNewSize) / totalOldSize).toFixed(2) * 100
  }` (%)
  console.log(chalk.red(resStr))

  // Enable failed compression after 2 seconds
  await sleep(2000)

  return failedList
}
Copy the code

CompressFailedImg fails and compresses again

const { tinyFun } = require('./utils.js')
const Listr = require('listr')
const resultToChalk = require('./resultToChalk.js')

let compressTimes = 1
// Start a new Listr and create an image compression task
module.exports = async function compressFailedImg(failedList, options) {
  const taskExample = new Listr()
  if (compressTimes-- > 0) {
    taskExample.add({
      title: 'Failed to compress image again'.task: async (ctx, task) => {
        const imagesRsult = await Promise.all(
          tinyFun({
            images: failedList,
            options,
          })
        )
        // Failed to compress the image at the same time
        await resultToChalk(imagesRsult)
      },
    })
    taskExample.run().catch((err) = > {
      console.log(err)
    })
  }
}
Copy the code

CompressFailedImg starts the new compression Listr, also calls tinyFun to get the compressed result, which is then handed to resultToChalk for processing.

Ok, basically complete the compression tool, gitHub address I hope you can enjoy.