“This is the 13th day of my participation in the November Gwen Challenge. See details: The Last Gwen Challenge 2021”.

Dear, hello, everyone. I am “Front-end Xiaoxin”. πŸ˜‡ has been engaged in front-end development and Android development for a long time


Preface:

It is strange to say that there are some “pseudo programmers” in many technical communication groups. For example, in this dialog below, a large number of pictures should be compressed by online image compression websites, and they all run in the group to ask how to do.

Solve the problem from a programmer’s point of view:
  1. Work touch fish law: a piece of a piece, dry a calculate a piece.
  2. Local money method: through the site open API simple programming for batch processing, of course, you need to pay some fees for more processing.
  3. Showcasing techniques: A rare opportunity to brush up on your programming knowledge in reasonable quantities and get the job done.
  4. Others:…

Preparation before coding:

  1. We chose the Demo technique for today’s Demo, and I think it was a programmer’s choice (leave it to the artists…). ;
  2. The quality of a product also needs to be polished and optimized gradually,tinypngAmong programmers or spread a relatively easy to use product, we still choosetinypngUsing someone else’s professional tools to do your own thing, beautiful! .

Introduction of ideas:

  1. Recursively get files in a local folder
  2. Filter file, the format must be.jpg.png and the size must be less than 5MB.(Folder recursion)
  3. Process only one file at a time (you can get around the limit of 20)
  4. Process returned data to get remote optimized picture address
  5. Fetch image update local image
  6. A pure Node implementation does not rely on any other snippets of code

Coding implementation:

This applies only to modules provided by Node:
const fs = require("fs");
const { Console } = require("console");
const path = require("path");
const https = require("https");
const URL = require("url").URL;
Copy the code
Common browser id to prevent the same id from being intercepted by the server:
const USER_AGENT = [
  "Mozilla / 5.0 (Macintosh; U; Intel Mac OS X 10_6_8; En-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"."Mozilla / 5.0 (Windows; U; Windows NT 6.1; En-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"."Mozilla / 5.0 (Macintosh; Intel Mac OS X 10.6; The rv, Gecko / 20100101 Firefox 2.0.1) / 4.0.1. ""."Mozilla / 5.0 (Windows NT 6.1; The rv, Gecko / 20100101 Firefox 2.0.1) / 4.0.1. ""."Opera / 9.80 (Macintosh; Intel Mac OS X 10.6.8; U; En) Presto / 2.8.131 Version / 11.11"."Opera / 9.80 (Windows NT 6.1; U; En) Presto / 2.8.131 Version / 11.11"."Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"."Mozilla / 4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)"."Mozilla / 4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)"."Mozilla / 4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident / 4.0; SE 2. MetaSr 1.0 X; SE 2. MetaSr 1.0 X; The.net CLR 2.0.50727; SE 2. MetaSr 1.0 X)",];Copy the code
Define log to support output log to a file:
// Define log to support the output of logs to files
class Log {
  options = {
    flags: "a"./ / append mode
    encoding: "utf8"./ / utf8 encoding
  };

  logger = {};

  /** * Initializes the print configuration */
  constructor() {
    this.logger = new Console({
      stdout: fs.createWriteStream("./log.tinypng.stdout.log".this.options),
      stderr: fs.createWriteStream("./log.tinypng.stderr.log".this.options),
    });
  }

  /** * log level *@param {*} Message Output information */
  log(message) {
    if (message) {
      this.logger.log(message);
      console.log(message); }}/** * Error level *@param {*} Message Displays err information */
  error(message) {
    if (message) {
      this.logger.error(message);
      console.error(message); }}}// instantiate the Log object
const Tlog = new Log();
Copy the code
Define TinyPng objects:
class TinyPng {
  // Configuration information: suffix format and maximum file size cannot be adjusted due to receiving restrictions
  config = {
    files: [].entryFolder: ". /".deepLoop: false.extension: [".jpg".".png"].max: 5200000./ / 5 MB = = 5242848.754299136
    min: 100000.// 100KB
  };

  // Successfully handle count
  successCount = 0;
  // Failure processing counts
  failCount = 0;

  /** * TinyPng constructor *@param {*} Entry Entry file *@param {*} Is deep recursive */
  constructor(entry, deep) {
    console.log(USER_AGENT[Math.floor(Math.random() * 10)]);
    if(entry ! =undefined) {
      this.config.entryFolder = entry;
    }
    if(deep ! =undefined) {
      this.config.deepLoop = deep;
    }
    // Filter pending files that match the adjustment in the incoming entry directory
    this.fileFilter(this.config.entryFolder);
    Tlog.log('Configuration of the script executed this time:');
    Object.keys(this.config).forEach((key) = > {
      if(key ! = ="files") {
        Tlog.log(` configuration${key}:The ${this.config[key]}`); }}); Tlog.log('Number of files waiting to be processed:The ${this.config.files.length}`);
  }

  /** * Perform compression */
  compress() {
    Tlog.log("Start image compression, please wait...");
    let asyncAll = [];
    if (this.config.files.length > 0) {
      this.config.files.forEach((img) = > {
        asyncAll.push(this.fileUpload(img));
      });
      Promise.all(asyncAll)
        .then(() = > {
          Tlog.log(
            'Processing completed: success:The ${this.successCount}Zhang, success rateThe ${this.successCount / this.config.files.length
            }`
          );
        })
        .catch((error) = >{ Tlog.error(error); }); }}/** * Filter folder to get the list of files to be processed *@param {*} Folder Indicates the folder to be processed@param {*} Files List of files to be processed */
  fileFilter(folder) {
    // Read the folder
    fs.readdirSync(folder).forEach((file) = > {
      let fullFilePath = path.join(folder, file);
      // Read file information
      let fileStat = fs.statSync(fullFilePath);
      // Filter file security/size limit/name extension
      if (
        fileStat.size <= this.config.max &&
        fileStat.size >= this.config.min &&
        fileStat.isFile() &&
        this.config.extension.includes(path.extname(file))
      ) {
        this.config.files.push(fullFilePath);
      }
      // Do deep recursive processing of folders
      else if (this.config.deepLoop && fileStat.isDirectory()) {
        this.fileFilter(fullFilePath); }}); }/** * TinyPng remote compression HTTPS request configuration generation method */
  getAjaxOptions() {
    return {
      method: "POST".hostname: "tinypng.com".path: "/web/shrink".headers: {
        rejectUnauthorized: false."X-Forwarded-For": Array(4)
          .fill(1)
          .map(() = > parseInt(Math.random() * 254 + 1))
          .join("."),
        "Postman-Token": Date.now(),
        "Cache-Control": "no-cache"."Content-Type": "application/x-www-form-urlencoded"."User-Agent": USER_AGENT[Math.floor(Math.random() * 10)],}}; }/** * TinyPng remotely compresses HTTPS requests *@param {string} Img File to be processed *@success { * "input": { "size": 887, "type": "image/png" }, * "output" : {" size ": 785," type ":" image/PNG ", "width" : 81, "height" : 81, "thewire" : 0.885, "url" : "https://tinypng.com/web/output/7aztz90nq5p9545zch8gjzqg5ubdatd6" } * } *@error  {"error": "Bad request", "message" : "Request is invalid"}* /
  fileUpload(imgPath) {
    return new Promise((resolve) = > {
      let req = https.request(this.getAjaxOptions(), (res) = > {
        res.on("data".async (buf) => {
          let obj = JSON.parse(buf.toString());
          if (obj.error) {
            Tlog.log('Compression failed! \n Current file:${imgPath} \n ${obj.message}`);
          } else {
            resolve(await this.fileUpdate(imgPath, obj)); }}); }); req.write(fs.readFileSync(imgPath),"binary");
      req.on("error".(e) = > {
        Tlog.log('Request error! \n Current file:${imgPath} \n, ${e}`);
      });
      req.end();
    }).catch((error) = > {
      Tlog.log(error);
    });
  }

  // This method is called in a loop to request image data
  fileUpdate(entryImgPath, obj) {
    return new Promise((resolve) = > {
      let options = new URL(obj.output.url);
      let req = https.request(options, (res) = > {
        let body = "";
        res.setEncoding("binary");
        res.on("data".(data) = > (body += data));
        res.on("end".() = > {
          fs.writeFile(entryImgPath, body, "binary".(err) = > {
            if (err) {
              Tlog.log(err);
            } else {
              this.successCount++;
              let message = 'Compression success: optimized ratio:The ${((1 - obj.output.ratio) *
                100
              ).toFixed(2)}%, original size:${(obj.input.size / 1024).toFixed(
                2
              )}KB, compressed size:${(obj.output.size / 1024).toFixed(
                2
              )}KB, file:${entryImgPath}`; Tlog.log(message); resolve(message); }}); }); }); req.on("error".(e) = > {
        Tlog.log(e);
      });
      req.end();
    }).catch((error) = >{ Tlog.log(error); }); }}module.exports = TinyPng;

Copy the code
Entry script:
/** * Some image processing failed due to network problems and technical limitations of third-party interface brush protection */
const TinyPng = require("./tinypng.compress.img");

function getEntryPath() {
  let i = process.argv.findIndex((i) = > i === "-p");
  if (process.argv[i + 1]) {
    return process.argv[i + 1]; }}new TinyPng(getEntryPath(), true).compress();
Copy the code
Perform demo:

Logging:

Conclusion:

Programmers still need to simplify the repetitive work. A few years ago, this script pushed the front end project of more than 150 meters to 20~30 megabytes. You would think, how can there be such a project? Do you think it is reasonable to deal with the files you have accumulated for many years? Do you think it is reasonable to deal with the files you have just compressed other colleagues and submitted a bunch of large pictures? Then you’d better change the script and add it to the plug-in at compile time, perfect!


Welcome to follow my public account “Front-end Xiaoxin students”, the first time to push original technical articles.