Dear, Hello, I'm "front-end Xiaoxin", 😇 He has been engaged in front-end development and Android development for a long time, is keen on technology, and goes farther and farther on the road of programming
preface:
It's strange to say that there are some "pseudo programmers" in many technical communication groups. For example, in the dialogue below, the online image compression website wants to compress a large number of its own images. It's too troublesome to go to the group and ask what to do?
Solve this problem from the programmer's point of view:
- Fishing at work: one by one, one by one.
- Tyrant krypton gold method: simple programming through the open API of the website for batch processing. Of course, the more you process, you need to pay some fees.
- Show the technical method: it is suitable for reviewing your programming knowledge in a reasonable number and a rare opportunity, and can do a good job.
- other:...
Preparation before coding:
- We chose to show the technical method to do today's Demo. I also think it is a programmer's choice (I lost it to the artist...);
The quality of a product also needs to be polished and optimized gradually. Tinypng is still a popular product among programmers. We still choose tinypng and use other professional tools to do our own things. It's beautiful!.
Idea introduction:
- Recursively get the files in the local folder
- Filter file, format must be. Jpg. PNG, size less than 5MB. (folder recursion)
- Only one file is processed at a time (the limit of 20 can be bypassed)
- Process the returned data to get the remote optimized picture address
- Retrieve pictures and update local pictures
A pure node implementation does not rely on any other code snippets
Coding implementation:
Only applicable 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;
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; rv,2.0.1) Gecko/20100101 Firefox/4.0.1", "Mozilla/5.0 (Windows NT 6.1; rv,2.0.1) Gecko/20100101 Firefox/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.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)", ];
Define log and output log to file:
// Define log and support outputting log to file class Log { options = { flags: "a", // append mode encoding: "utf8", // utf8 encoding }; logger = {}; /** * Initialize 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 Output err information */ error(message) { if (message) { this.logger.error(message); console.error(message); } } } // Instantiate Log object const Tlog = new Log();
Define TinyPng object:
class TinyPng { // Configuration information: suffix format and maximum file size are limited by reception and cannot be adjusted config = { files: [], entryFolder: "./", deepLoop: false, extension: [".jpg", ".png"], max: 5200000, // 5MB == 5242848.754299136 min: 100000, // 100KB }; // Successful processing count successCount = 0; // Failed processing count failCount = 0; /** * TinyPng constructor * @param {*} entry Entry file * @param {*} 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 the pending files that match the adjustment in the incoming entry directory this.fileFilter(this.config.entryFolder); Tlog.log(`Configuration of this execution script:`); Object.keys(this.config).forEach((key) => { if (key !== "files") { Tlog.log(`to configure ${key}: ${this.config[key]}`); } }); Tlog.log(`Number of files waiting to be processed: ${this.config.files.length}`); } /** * Perform compression */ compress() { Tlog.log("Start image compression,One moment please..."); 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: ${this.successCount}Zhang, Success rate ${ this.successCount / this.config.files.length }` ); }) .catch((error) => { Tlog.error(error); }); } } /** * Filter the pending folder to get the list of pending files * @param {*} folder Pending folder * @param {*} files List of pending files */ fileFilter(folder) { // Read 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 / suffix 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); } // Deep recursive processing of folders is required else if (this.config.deepLoop && fileStat.isDirectory()) { this.fileFilter(fullFilePath); } }); } /** * TinyPng Configuration generation method of remote compressed HTTPS request */ 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 Remote compression HTTPS request * @param {string} img Documents to be processed * @success { * "input": { "size": 887, "type": "image/png" }, * "output": { "size": 785, "type": "image/png", "width": 81, "height": 81, "ratio": 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 circularly to request picture 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 succeeded : Optimized proportion: ${( (1 - obj.output.ratio) * 100 ).toFixed(2)}% ,Original size: ${(obj.input.size / 1024).toFixed( 2 )}KB ,Compression 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;
Entry script:
/** * Some image processing failed due to network reasons and technical limitations such as anti brushing of third-party interface */ 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();
Perform the demo:
Logging:
Conclusion:
Programmers still need to simplify the repetitive work. A few years ago, this script reduced the front-end project of more than 150 M to 20 ~ 30 megabytes. How can you have such a project? Is your project very large? To tell you the truth, it's caused by non-standard. You have to deal with the files accumulated over the years one by one. Do you think it's reliable? What if you just compressed a pile of big pictures submitted by other colleagues? Then you'd better change the script and add it to the plug-in at compile time. Perfect!
Welcome to my official account, "front-end little Xin", and push the original technical article for the first time.