preface

“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”

Recently, I developed a small program for home page revision, which involves the updating of many pictures. Since pictures are packaged in the project, in the web development scene, reducing the code volume is a direction of performance optimization, but not to the extent of penny-pinching. However, in the small program scenario, because the main package is limited to **2M ** and the total package is limited to 20M in the upload stage of the code package, it will face the risk of not being able to distribute if the main package exceeds 20M, so the optimization of the code package size becomes particularly important.

Generally, the output images of interaction designers are 1x, 2X and 3X times, which are relatively large in size. We can reduce the size of images as much as possible while ensuring high definition through Tinypng.com/.

One by one to drag compression, check the compression quality, time-consuming and laborious. Can you solve this by automatically compressing images? This article will implement a command line scaffolding tool by implementing an autocompress image command.

  • Automatic compression of image scripts
  • The command line tool chassis is built
  • Release the NPM package and install it

Automatic compression of image scripts

Requirements describe

  1. It should be able to select the image compression on demand and return the compression size and data for users to check the quality of the picture.
  2. If necessary, provide online preview images of the UI.

Thought analysis

You can use either online compression or local compression at any time the developer chooses. At the beginning, I was thinking about automatically compressing all images of a project when the project is packaged. Since the taro project is used, no relevant scheme has been found. In addition, considering the automatic compression of all images when the PC project introduced packaging compression in the past, it will greatly increase the packaging time of the project, depending on imagemin, Imagemin-Jpegtran, Imagemin-Pngquant packages, Imagemin-pngquant will not be installed when you install imagemin-pngquant. One of the reasons is that the library is based on some underlying language, so it cannot be installed directly. You need to install another dependency on libpng on your computer, which is very difficult.

Therefore, the idea is changed, because the general development of new features when adding new images, need to be compressed. Then you can provide an automatic compression script that automatically compresses the image selected by the developer. Automatic compression scripts can use local compression or online compression, this article focuses on online compression, automatic request to tinypng.com/ compression image.

Code implementation

  1. Global configuration. You can obtain the EntryFolder after entering the command line.
const tinyConfig = {
  files: [].entry: ' '.deepLoop: false.// Whether to recurse
  replace: false.// Whether to overwrite the source file
  Exts: ['.jpg'.'.png'.'jpeg'].Max: 5120000 // 5MB
}
Copy the code
  1. When reading a local image, some files with non-image suffixes will be filtered.
/** * filter folder to get the list of files to be processed */
function fileFilter(sourcePath) {
  const fileStat = fs.statSync(sourcePath)
  if (fileStat.isDirectory()) {
    fs.readdirSync(sourcePath).forEach((file) = > {
      const fullFilePath = path.join(sourcePath, file)
      // Read file information
      const fileStat = fs.statSync(fullFilePath)
      // Filter the size and suffix
      if (
        fileStat.size <= tinyConfig.max &&
        fileStat.isFile() &&
        tinyConfig.exts.includes(path.extname(file))
      ) {
        tinyConfig.files.push(fullFilePath)
      } else if (tinyConfig.deepLoop && fileStat.isDirectory()) {
        // Whether to deeply recurse
        fileFilter(fullFilePath)
      }
    })
  } else {
    if (
      fileStat.size <= tinyConfig.max &&
      fileStat.isFile() &&
      tinyConfig.exts.includes(path.extname(sourcePath))
    ) {
      tinyConfig.files.push(sourcePath)
    }
  }
}
Copy the code
  1. To upload pictures, you can manually upload a picture to the tinyPng website and then check the NetWork’s request response message. We can construct such request message to realize automatic script processing.
/** * Tiny remotely compresses the CONFIGURATION of HTTPS requests to construct browser requests */
function 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('. '), // Forge random IP addresses to avoid restrictions
      'Postman-Token': Date.now(),
      'Cache-control': 'no-cache'.'Content-type': 'application/x-www-form-urlencoded'.'User-Agent': 'the Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}}}/** * TinyPng * SUCCESS {* "input": {"size": 887, "type": "image/ PNG "}, * "output": {* "size": 785, "type" : "image/PNG", "width" : 81, the "height" : 81, "thewire" : 0.885. * "url" : "https://tinypng.com/web/output/xxx" * } * } */
function fileUpload(imgPath) {
  const req = https.request(getAjaxOptions(), (res) = > {
    res.on('data'.(buf) = > {
      const obj = JSON.parse(buf.toString())
      if (obj.error) {
        console.log('Compression failed! \n Current file:${imgPath} \n ${obj.message}`)}else {
        fileUpdate(imgPath, obj) // Update the file locally
      }
    })
  })
  req.write(fs.readFileSync(imgPath), 'binary')
  req.on('error'.(err) = > {
    console.error('Request error \n Current file:${imgPath} \n ${err}`)
  })
  req.end()
}
Copy the code
  1. Get the compressed image and write it to the local disk.
/** * request compressed image, update to local path */
function fileUpdate(entryImgPath, obj) {
  const url = new URL(obj.output.url)
  const req = https.request(url, (res) = > {
    let body = ' '
    res.setEncoding('binary')
    res.on('data'.(data) = > {
      body += data
    })
    res.on('end'.() = > {
      const [filename, extendsion] = entryImgPath.split('. ')
      if(! tinyConfig.replace) {// Whether to overwrite the source file
        entryImgPath = filename + '_tiny' + '. ' + extendsion
      }
      fs.writeFile(entryImgPath, body, 'binary'.(err) = > {
        if (err) return console.log(err)
        let log = 'Compression succeeded:'
        log += 'Optimized ratio:The ${((1 - obj.output.ratio) * 100).toFixed(2)}%, `
        log += 'Original size:${(obj.input.size / 1024).toFixed(2)}KB, `
        log += 'Compressed size:${(obj.output.size / 1024).toFixed(2)}KB, `
        log += ` file:${entryImgPath}`
        console.log(log)
      })
    })
  })
  req.on('error'.(e) = > console.log(e))
  req.end()
}
Copy the code

Once the script is written, you can first write the tinyConfig read images folder path and then test it with Node index.js.

tinyConfig.entry = './tests/'
fileFilter(tinyConfig.entry)
tinyConfig.files.forEach((img) = > fileUpload(img))
Copy the code

With the script testing ok, we then want to select the compressed image path from the command line.

The command line tool chassis is built

Learn about command-line tools

Command-line tools running on terminals, such as the MAC shell, are the user interface of the system, providing an interface for users to interact with the kernel (command interpreter)

The Shell can execute

  • Internal command
  • The application
  • A shell script

User input command -> submit to Shell -> whether it is a built-in command -> find the file of the command in the system and call into memory to execute -> system function call in the kernel.

In Linux, executable files are also classified:

  • Built-in commands: Interpreters of some commonly used commands are constructed inside the Shell for efficiency.

  • External commands: commands stored in the /bin or /sbin directories.

  • Utilities: Utilities stored in /usr/bin, /usr/sbin, /usr/share, and /usr/local/bin.

  • User programs: User programs can be run as Shell commands after being compiled to generate executable files

  • Shell scripts: batch files written in the Shell language that can be run as Shell commands.

All of the above can be called command line tools. For example, clear, ls, and PWD are system commands that can be executed on the terminal. They are called system built-in commands. You can use which to see where they came from:

$ which clear
/usr/bin/clear

$ which vue # vue CLI scaffolding
/usr/local/bin/vue
Copy the code

Use ls -lah $(which vue) to further parse the command:

$  ls -lah $(which vue)
lrwxr-xr-x  1 naluduo233  admin    65B  9 27 15:13 /usr/local/bin/vue -> .. /.. /.. /Users/naluduo233/.config/yarn/global/node_modules/.bin/vueCopy the code

/usr/local/bin/vue is the directory installed under YARN.

These /usr/local/bin directories are in the environment variable PATH. “Commands in the PATH of the environment variable can be executed anywhere in the shell terminal”.

The principle of Node global command line: “environment change Path” + “symbolic link”

  1. NPM globally downloads a package to a path/usr/local/lib/node_modulesYarn The same path is used~/.config/yarn/global/node_modules)
  2. According to the library’s package.jsonbinField to mount the corresponding command line PATH to the PATH PATH by symbolic index
  3. Add x permissions to the corresponding binary script (executable file permissions)

Build the Node command-line tool chassis

  1. First create the entry file, index.js, and declare it in package.json
{
  "bin": {
    "naluduo": "./bin/naluduo.js" // Specify the name of the final command line tool in the bin field of package.json}}Copy the code
  1. innaluduo.jsSpecify the execution environment, add a line at the beginning of which explains how to use the Node interpreter to execute the script, and how to locate the node interpreter using env node.
#! /usr/bin/env node
Copy the code
  1. As mentioned above, we need to install the global package locally, because we have not released this package, and need to debug locally.

    You can go to the root directory of the project, and yarn Link uses the package.json of the package to perform global soft links.

yarn link
Copy the code
  1. Then it can be viewed that Naluduo has been placed in/usr/local/binUnder.
$ which naluduo
/usr/local/bin/naluduo
$ ls -lah $(which naluduo)
lrwxr-xr-x  1 naluduo233  admin    65B 10 31 23:36 /usr/local/bin/naluduo -> .. /.. /.. /Users/naluduo233/.config/yarn/link/nalu-cli/bin/naluduo.jsCopy the code

Yarn Link copies the project to the yarn configuration and makes the related soft link. The script in /usr/local/bin can be parsed and executed anywhere on the shell terminal.

naluduo 
Copy the code

Running the command directly will execute the footsteps file we wrote.

Parse user entry command, add automatic compression picture command

Now let’s add the autocompress image command, which we want to execute like this:

naluduo tinyimg -r -d  # -r, replace whether to replace source files, -d, deep whether to recursively process folders
Copy the code

Then enter the selection to compress the picture folder or picture path, select can enter the automatic compression picture link.

The problem to solve now is to parse the user input command, which can be obtained from process.argv:

$ node cmd.js 1 2 3

// Output: [
//   '/usr/local/bin/node', / /'/Users/shanyue/cmd.js', / /'1', / /'2', / /'3',
// ]

Copy the code

Argv can be customized by parsing process.argv to take a variety of arguments as input to the command line. Parsing parameters follows the basic rules of POSIX compatibility: format, optional, required, shorthand, description, help, and so on.

For quick development, use the third-party libraries commander, YARN Add Commander –dev, and write the entry file

const { Command } = require('commander')
const program = new Command()
+ const { tinyimg } = require('./commands/tinyimg')

program
  .command('tinyimg')
  .description('Compress image')
  .option('-d, --deep'.'Do I recursively process image folders?'.false)
  .option('-r, --replace'.'Overwrite source file'.false)
  .action((commandAndOptions) = > { // The argument is an option object
    tinyimg(commandAndOptions)
  })

program.version('0.1.0 from') // After the version is set, the command line outputs the current version
program.parse(process.argv) // Parses the arguments entered on the command line
Copy the code

You can now enter Naluduo for testing:

$ naluduo
Usage: naluduo [options] [command]

Options:
  -V, --version      output the version number
  -h, --help         display help for commandCommands: tinyimg [options] Compressed imagehelp [command]     display help for command
Copy the code

Run naluduo tinyimg -d -r to obtain commandAndOptions:

{
  deepLoop: true.replace: true
}
Copy the code

After the introduction of the image command, the next thing to do is to let the user select the image path to compress, and then pass in the Tinyimg script.

Add interactivity and let the user select the image path to compress

On the Web, Input can be used to present colorful forms such as switches, multiple selections, radio selections, Input fields, and so on. In command line tools, multiple libraries can be borrowed to achieve strong interactivity.

Naluduo233 mainly uses the Inquirer. Js library and its plug-in Inquirer -file-tree- Selection -prompt to achieve user – defined image selection path.

yarn add inquirer inquirer-file-tree-selection-prompt --dev
Copy the code

Now modify the autocompress image script to accept parameters entered from the command line.

const inquirer = require('inquirer')
const inquirerFileTreeSelection = require('inquirer-file-tree-selection-prompt')

inquirer.registerPrompt('file-tree-selection', inquirerFileTreeSelection)

exports.tinyimg = async (commandAndOptions) => {
  const answer = await inquirer.prompt([
    {
      name: 'path'./ / key
      type: 'file-tree-selection'.message: '(Required) Compressed image folder path/file '}])const { path } = answer

  tinyConfig.entry = path
  tinyConfig.replace = commandAndOptions && commandAndOptions.replace
  tinyConfig.deepLoop = commandAndOptions && commandAndOptions.deepLoop

  fileFilter(tinyConfig.entry)
  console.log('Script configuration for this execution:', tinyConfig)
  console.log('Number of files waiting for processing:', tinyConfig.files.length)
  tinyConfig.files.forEach((img) = > fileUpload(img))
}

Copy the code

Finally, the result is as follows:

$ naluduo tinyimg -r -d ? (Mandatory) Compressed image folder/file ↓.(root directory)/ →.git/.gitignore readme. md → bin/ → node_modules/ package-lock.json Package. json → SRC / → tests/ (Move up and down to reveal more choices)Copy the code

Select the Tests folder:

$ naluduo tinyimg ? (Mandatory) Compressed image folder Path/files ~/Documents/ Develop /nalu-cli/tests Configuration for this script: {files: [], entry:'~/Documents/develop/nalu-cli/tests',
  deepLoop: undefined,
  replace: false,
  exts: [ '.jpg'.'.png'.'.jpeg'], Max: 5120000} Number of files waiting to be processed: 3Copy the code

Automatic compression image compression yield is high, but also encounter compression quality is not good, at this time you can consider using PhotoShop to manually check the quality of the image as a backstop solution.

Release the NPM package and install it

Now that the command line tools are written locally, you can publish them to the NPM repository so that everyone can use your command line tools. Let’s first add the published configuration to package.json:

{
  "name": "nalu-cli".// Specify the package name. Before publishing, check the NPM website to make sure that the package name you want to use is already occupied.
  "version": "0.1.0 from"."main": "src/index.js".// Specify the entry file for the package
  "private": false."bin": {
    "naluduo": "./bin/naluduo.js"}}Copy the code

Then login and publish. Note that if you are a new user, be sure to activate the email sent to you by NPM, otherwise the 403 error will appear. Once activated, log in again and republish.

$ npm login # login
Username: xxx
Password: 
Email: (this IS public) xxx
Logged inAs naluduo233 on XXX $NPM publish NPM notice NPM notice 📦 [email protected] NPM notice === Tarball Contents === NPM Notice 435B SRC /index.js NPM notice 47B bin/naluduo.js NPM notice 4.6KB SRC /commands/tinyimg.js NPM notice 455B package.json npm notice 75B README.md npm notice === Tarball Details === npm notice name: nalu-cli npm notice version: 0.1.0 NPM notice Package Size: 2.7KB NPM notice Unpacked size: 5.6KB NPM notice SHasum: 4f71dd896bac28c4c1b284975d4df1c737e43292 npm notice integrity: sha512-+NkZL5w0VFAGB[...] ODzboPWQ4Q7FA== NPM notice Total Files: 5 NPM notice + [email protected]Copy the code

After successful publishing, download the command line tool and use the tests. NPX Naluduo tiny can also be called directly.

$ npm i -g naluduo
Copy the code

Naluduo: There was an error when executing naluduo, commander was not found.

$ naluduo 
Error: Cannot find module 'commander'Require stack: - / Users/kayliang /. NVM/versions/node/v14.17.5 / lib/node_modules/nalu - cli/SRC/index. The js - / Users/kayliang/NVM/versions/node/v14.17.5 / lib/node_modules/nalu - cli/bin/naluduo. JsCopy the code

Nalu -cli: no node_modules package. Package. json:

 "devDependencies": {
    "commander": "^ 8.3.0"."inquirer": "^ 8.2.0"."inquirer-file-tree-selection-prompt": "^ 1.0.13"
 }
Copy the code

Dependencies are placed in devDependencies. DevDependencies are the same as dependencies when writing project code, but they should be strictly separated when publishing libraries and packages. Otherwise the package declared in devDependencies will not be installed.

summary

This article mainly starts from the automatic compression image requirements, and then builds a command line tool step by step, we mainly learned:

  • Globally executable command line tool principles
  • How to develop a command tool using Node
  • And how to distribute and use NPM packages

For now, this is just the beginning, and many scripts can be wrapped into commands.

Code address github.com/naluduo233/… Continuously updated…

The resources

  • How to create a professional command-line tool using Node?
  • Large Front-end Advanced Node.js series P6 Essential scaffolding /CI building ability (PART 2)
  • NodeJs Interactive command line tool Inquirer. Js unpacking guide
  • Node automatically uses Tinypng