Repository: facebook/react
Pull request: [DevTools] Optimize Images yarn command (part 2)

## Summary

Automated node script for improving images: continuing #19972 

This PR run automatically generates optimize images (reduce file size) and overwrites them. See #19972 for details and the background.

To run, simply:
```
yarn improve-images
```

## Test Plan

![Screen Shot 2021-07-27 at 09 56 28](https://user-images.githubusercontent.com/19968375/127093325-44e63b42-019d-4c30-a2a8-376f1a3aab1c.png)

*Example output*


Changed files:
filename: packages/react-devtools-extensions/improveImages.mjs
patch: @@ -0,0 +1,90 @@
+import fs from 'fs'
+import find from 'find'
+import filesize from 'filesize'
+import imagemin from 'imagemin'
+import imageminGifsicle from 'imagemin-gifsicle'
+import imageminJpegtran from 'imagemin-jpegtran'
+import imageminOptipng from 'imagemin-optipng'
+import imageminSvgo from 'imagemin-svgo'
+import parseFilepath from 'parse-filepath'
+import chalk from 'chalk'
+
+const plugins = [
+  imageminGifsicle({}),
+  imageminJpegtran({}),
+  imageminOptipng({}),
+  imageminSvgo({})
+]
+
+let savedSize = 0
+
+const run = async () => {
+  const regex = new RegExp(/\.gif|\.jpeg|\.jpg|\.png$/)
+
+  const files = find.fileSync(regex, 'icons/');
+
+  for (const file of files) {
+    await optimized(file)
+  }
+
+  if (savedSize > 0) {
+    console.info(`\n🎉 You saved ${readableSize(savedSize)}.`)
+  } else {
+    console.info(`\n🎉 Nothing to optimize.`)
+  }
+}
+
+const size = (filename) => {
+  return fs.statSync(filename).size
+}
+
+const readableSize = (size) => {
+  return filesize(size, { round: 5 })
+}
+
+const optimized = async (filename) => {
+  let output = parseFilepath(filename).dir || './'
+
+  const fileSizeBefore = size(filename)
+
+  if (fileSizeBefore === 0){
+    console.info(chalk.blue(`Skipping ${filename}, it has ${readableSize(fileSizeBefore)}`))
+    return
+  }
+
+  const pluginsOptions = {
+    destination: output,
+    plugins
+  }
+
+  const filenameBackup = `${filename}.bak`
+  fs.copyFileSync(filename, filenameBackup)
+
+  try {
+    await imagemin([filename], pluginsOptions)
+
+    const fileSizeAfter = size(filename)
+    const fileSizeDiff = fileSizeBefore - fileSizeAfter
+    if (fileSizeDiff > 0){
+      savedSize += fileSizeDiff
+      console.info(chalk.green(`Optimized ${filename}: ${chalk.yellow(readableSize(fileSizeAfter))}`))
+    } else { // file after same or bigger
+      // restore previous file
+      fs.renameSync(filenameBackup, filename)
+
+      console.info(`${filename} ${chalk.red(`already optimized`)}`)
+    }
+
+  } catch (err) {
+    console.info(chalk.red(`Skip ${filename} due to error when optimizing`));
+  }
+
+  // delete backup file
+  if (fs.existsSync(filenameBackup)) {
+    fs.unlinkSync(filenameBackup)
+  }
+}
+
+(async () => {
+  await run();
+})();

filename: packages/react-devtools-extensions/package.json
patch: @@ -16,6 +16,7 @@
     "test:chrome": "node ./chrome/test",
     "test:firefox": "node ./firefox/test",
     "test:edge": "node ./edge/test",
+    "improve-images": "node ./improveImages.mjs",
     "update-mock-source-maps": "node ./src/__tests__/updateMockSourceMaps.js"
   },
   "devDependencies": {
@@ -33,18 +34,27 @@
     "babel-eslint": "^9.0.0",
     "babel-loader": "^8.0.4",
     "babel-preset-minify": "^0.5.1",
+    "chalk": "^4.1.1",
     "child-process-promise": "^2.2.1",
     "chrome-launch": "^1.1.4",
     "crx": "^5.0.0",
     "css-loader": "^1.0.1",
     "file-loader": "^6.1.0",
+    "filesize": "^6.0.1",
+    "find": "^0.3.0",
     "firefox-profile": "^1.0.2",
     "fs-extra": "^4.0.2",
+    "imagemin": "^8.0.0",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^6.0.0",
+    "imagemin-optipng": "^7.0.0",
+    "imagemin-svgo": "^7.0.0",
     "jest-fetch-mock": "^3.0.3",
     "node-libs-browser": "0.5.3",
     "nullthrows": "^1.0.0",
     "open": "^7.0.2",
     "os-name": "^3.1.0",
+    "parse-filepath": "^1.0.2",
     "raw-loader": "^3.1.0",
     "rollup": "^1.19.4",
     "rollup-plugin-babel": "^4.0.1",