Remember vite2 X packaging optimization process

Posted by Albright on Sat, 26 Feb 2022 01:43:14 +0100

background

The latest requirement is to embed h5 in the client webview and use vite2 X and vue3. When the first version was tested, it was found that after packaging, the total package size was more than 4M, so there was a lot of room for optimization.

Tool chain description

  1. Currently vite2 X is packaged based on rollup, not esbuild. See here
  2. Use rollup plugin visualizer for packaging analysis. After packaging, a stats will be generated by default in the root directory HTML file
  3. Use vite plugin imagemin for image compression. Each package will be compressed once, which will take up the time of construction and have space worthy of optimization. It will not be discussed here first
  4. @Vitejs / plugin legacy vite supports modern browsers that support ESM by default. This plug-in is used to be compatible with browsers that do not support ESM. See here
  5. The configuration files and scripts involved in this article are placed in my warehouse: https://github.com/Rockergmail/imagemin-script

Before optimization

vite.config.js is configured as follows

import { UserConfigExport, ConfigEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import { viteVConsole } from 'vite-plugin-vconsole';
import { resolve } from 'path';
import legacy from '@vitejs/plugin-legacy';
import { visualizer } from 'rollup-plugin-visualizer';
export default ({ mode }: ConfigEnv): UserConfigExport => {
    return {
        plugins: [
            vue(),
            styleImport({
                libs: [
                    {
                        libraryName: 'vant',
                        esModule: true,
                        resolveStyle: (name) => `vant/es/${name}/style`
                    }
                ]
            }),
            viteVConsole({
                entry: resolve(__dirname, './src/main.ts').replace(/\\/g, '/'),
                localEnabled: mode !== 'prod', // dev environment
                enabled: mode !== 'prod', // build production
                config: {
                    maxLogNumber: 1000,
                    theme: 'light'
                }
            }),
            legacy({
                targets: ['ie >= 11'],
                additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
                renderLegacyChunks: true,
                polyfills: [
                  'es.symbol',
                  'es.array.filter',
                  'es.promise',
                  'es.promise.finally',
                  'es/map',
                  'es/set',
                  'es.array.for-each',
                  'es.object.define-properties',
                  'es.object.define-property',
                  'es.object.get-own-property-descriptor',
                  'es.object.get-own-property-descriptors',
                  'es.object.keys',
                  'es.object.to-string',
                  'web.dom-collections.for-each',
                  'esnext.global-this',
                  'esnext.string.match-all'
                ]
            },
            visualizer()
    ],
        build: {
            target: 'es2015',
            outDir: './dist/',
            cssCodeSplit: true
        }
    };
};

The packaging output information is as follows:

dist/assets/polyfills-legacy.a0c5535b.js   51.67 KiB / gzip: 20.70 KiB
dist/assets/index-legacy.a9899ade.js       150.73 KiB / gzip: 46.17 KiB
dist/assets/vendor-legacy.6772670a.js      1896.50 KiB / gzip: 576.97 KiB

dist/assets/mos_jj@3x.24678237.png                   9.92 KiB
dist/assets/mos_meituan@3x.91fce938.png              14.19 KiB
dist/assets/mos_xiecheng@3x.0ca58b53.png             11.89 KiB
dist/assets/mos_score_bg.eb2dc424.png                15.92 KiB
dist/assets/mos_good.f267c228.png                    36.00 KiB
dist/assets/mos_bad.58afe320.png                     28.50 KiB
dist/assets/mos_bonus_points_bg@3x.e18c09a9.png      175.78 KiB
dist/assets/mos_bg_null.8ae1448c.png                 55.00 KiB
dist/assets/mos_price_bad.919aaddf.png               46.72 KiB
dist/assets/mos_price_good.563cd841.png              62.14 KiB
dist/assets/mos_bg_copper.515f59b3.png               86.77 KiB
dist/assets/mos_bg_silver.4e31d711.png               82.74 KiB
dist/assets/mos_bg_gold.c1f61403.png                 93.59 KiB
dist/assets/mos_copper.483f2a6f.png                  79.09 KiB
dist/assets/mos_silver.7da7643a.png                  71.10 KiB
dist/assets/mos_bg_good1.d6390420.png                146.27 KiB
dist/assets/mos_gold.fa42d3e3.png                    72.31 KiB
dist/assets/mos_bg_null2.01d64f5d.png                7.25 KiB
dist/assets/mos_bg_gold2.64b6b5e9.png                18.66 KiB
dist/assets/mos_bg_bad1.1ceacacc.png                 176.29 KiB
dist/assets/mos_null.753e5b08.png                    35.62 KiB
dist/assets/mos_index_bg@3x.3cf17a3f.png             210.25 KiB
dist/assets/mos_bg_copper1.9941fb6f.png              136.03 KiB
dist/assets/mos_bg_null1.aa018f69.png                74.54 KiB
dist/assets/mos_bg_silver1.e4fc875c.png              149.86 KiB
dist/assets/mos_bg_gold1.9e337e71.png                157.11 KiB
dist/assets/mos_qa.8c12f693.png                      440.17 KiB
dist/assets/mos_applyfor_img_desc1@3x.b87c6d38.png   639.01 KiB
dist/assets/mos_applyfor_img_desc2@3x.f63db53c.png   669.85 KiB
dist/index.html                                      1.95 KiB
dist/assets/index.fa1d3dc8.css                       43.33 KiB / gzip: 5.95 KiB
dist/assets/polyfills-modern.3ab1202f.js             17.48 KiB / gzip: 7.16 KiB
dist/assets/index.1cfeddd0.js                        93.03 KiB / gzip: 37.47 KiB
dist/assets/vendor.d320968a.css                      116.42 KiB / gzip: 37.62 KiB
dist/assets/vendor.88c712d4.js                       1730.56 KiB / gzip: 530.72 KiB

(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
Done in 60.29s.

From the packaged output information, we can see:

  1. vendor-legacy.6772670a.js after packaging, there are still 576.97 KiB after 1896.50 KiB / gzip
  2. vendor. 88c712d4. After JS packaging, there are still 530.72 KiB after 1730.56 KiB / gzip
  3. Individual picture resources are too large
  4. The construction took 79.07 seconds, so there is room for optimization

Packaging Optimization

Use the rollup plugin visualizer plug-in to generate stats after packaging HTML, we can see the following chart

It should be noted that the package size displayed by the rollup plugin visualizer icon is the size when chunk s are divided but not compressed.

It can be observed that:

  1. zender and echarts take up most of the space and need to get rid of it first
  2. vant/es and @vue have been packaged on demand, and there is no room for optimization
  3. loadash/es has repeatedly checked that it is not used in the business code. Why is it packaged?

Optimize echarts packaging

Because I'm not familiar with echarts, I hope it was encapsulated by others when I introduced it. I chose vue3 echarts library, but from the packaging results, I packed all echarts.

So we decided to replace vue3 echarts with official ones.

After packaging, zender and echarts are still in the vendor without treeshaking, and they are all imported (echart only uses radar components and canvas renderer, not svg renderer and other components)

It can be seen that lodash no longer exists, which was originally caused by vue3 echarts. But echarts still needs to exclude it from vendor s. Here are two ideas:

  1. Use echarts as externals. When packaging, import * as echots from 'echots' will be processed into window ['echots'] (the processing results will be different according to the output library target. Here, only the browser side is considered, which is the processing result of AMD specification).

    • This scheme has one disadvantage: it will still be packaged in full. Although it can be found on [echarts official website]( https://echarts.apache.org/zh... )Customization, but lack of flexibility. If the project needs to introduce other modules, it needs to be customized again.
  2. Pack ecarts as an independent chunk, which is the chunk after treeshaking

When you see vite's alarm during packaging, there are two schemes to package echarts as an independent chunk

(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.

import() dynamic import

Change the code to:

const { echartsUse = use, echartsInit = init } = import('echarts/core');
// Radar charts are introduced, and the suffix of charts is Chart
const { RadarChart } = import('echarts/charts');
// Radar chart components are introduced, and the suffix of components is Component
const { RadarComponent } = import('echarts/components');
// When introducing the Canvas renderer, note that the introduction of Canvas renderer or SVGRenderer is a necessary step
const { CanvasRenderer } = import('echarts/renderers');
// Register required components

Output after packaging

dist/assets/core-legacy.fb1211d8.js                             5.18 KiB / gzip: 2.33 KiB
dist/assets/createSeriesData-legacy.a0c30666.js                 5.94 KiB / gzip: 2.47 KiB
dist/assets/renderers-legacy.b888ddd4.js                        30.54 KiB / gzip: 11.63 KiB
dist/assets/polyfills-legacy.a0c5535b.js                        51.67 KiB / gzip: 20.70 KiB
dist/assets/graphic-legacy.d43ff24a.js                          113.12 KiB / gzip: 38.24 KiB
dist/assets/index-legacy.158e4856.js                            152.88 KiB / gzip: 46.49 KiB
dist/assets/customGraphicKeyframeAnimation-legacy.171ce259.js   123.95 KiB / gzip: 41.24 KiB
dist/assets/charts-legacy.d5f2354e.js                           238.20 KiB / gzip: 77.00 KiB
dist/assets/Axis-legacy.ff4470f0.js                             258.47 KiB / gzip: 88.32 KiB
dist/assets/components-legacy.86e016d9.js                       226.12 KiB / gzip: 71.46 KiB
dist/assets/vendor-legacy.5481a04c.js                           644.12 KiB / gzip: 196.07 KiB
dist/assets/core.afbe1b14.js                             4.13 KiB / gzip: 1.93 KiB
dist/assets/createSeriesData.ee016422.js                 5.82 KiB / gzip: 2.42 KiB
dist/assets/renderers.453d5aab.js                        30.52 KiB / gzip: 11.53 KiB
dist/assets/index.242992b7.css                           45.04 KiB / gzip: 6.09 KiB
dist/assets/polyfills-modern.3ab1202f.js                 17.48 KiB / gzip: 7.16 KiB
dist/assets/index.854b8d89.js                            94.24 KiB / gzip: 37.96 KiB
dist/assets/graphic.c1bb426e.js                          111.41 KiB / gzip: 37.28 KiB
dist/assets/vendor.d320968a.css                          116.42 KiB / gzip: 37.62 KiB
dist/assets/customGraphicKeyframeAnimation.0a83d4fc.js   123.80 KiB / gzip: 40.83 KiB
dist/assets/charts.d2837108.js                           237.92 KiB / gzip: 76.78 KiB
dist/assets/components.df137286.js                       225.47 KiB / gzip: 70.87 KiB
dist/assets/Axis.804e702b.js                             259.07 KiB / gzip: 87.15 KiB
dist/assets/vendor.6476629b.js                           489.00 KiB / gzip: 151.32 KiB

It can be seen that echarts are packaged independently, without occupying the space of the vendor, and are packaged according to the modules in echarts

However, we can still see that the modules not introduced are packaged: we can still see the svg renderer and components other than radar.

manualChunks

Change the code back:

// Radar charts are introduced, and the suffix of charts is Chart
import { RadarChart } from 'echarts/charts';
// Radar chart components are introduced, and the suffix of components is Component
import { RadarComponent } from 'echarts/components';
// When introducing the Canvas renderer, note that the introduction of Canvas renderer or SVGRenderer is a necessary step
import { CanvasRenderer } from 'echarts/renderers';

In vite config. JS add

build: {
      rollupOptions: {
        output: {
          manualChunks: {
            echarts: ['echarts']
          }
        }
      }
}

Output when packaging

dist/assets/polyfills-legacy.a0c5535b.js   51.67 KiB / gzip: 20.70 KiB
dist/assets/echarts-legacy.fa6a2a05.js     361.40 KiB / gzip: 120.08 KiB
dist/assets/index-legacy.a57b6cea.js       812.68 KiB / gzip: 244.17 KiB
dist/assets/polyfills-modern.3ab1202f.js             17.48 KiB / gzip: 7.16 KiB
dist/assets/index.1cfae15f.css                       159.75 KiB / gzip: 44.24 KiB
dist/assets/echarts.c2408918.js                      360.93 KiB / gzip: 119.38 KiB
dist/assets/index.e28d5487.js                        600.23 KiB / gzip: 191.01 KiB
Done in 41.78s.

You can see that the contents of the vendor package are directly entered into the index package, and there are more echarts packages

The echarts package just packages the referenced components

Optimize legacy package

The configuration of legacy is copied from other projects, and the compatibility of IE > = 11 version is considered. This project only needs to consider the mobile terminal, so it doesn't need to consider ie. Just set it as the default configuration

legacy({
  targets: ['defaults', 'not IE 11']
})

Output after packaging:

dist/assets/polyfills-legacy.749f4000.js   68.82 KiB / gzip: 27.57 KiB
dist/assets/echarts-legacy.fa6a2a05.js     360.15 KiB / gzip: 119.61 KiB
dist/assets/index-legacy.a57b6cea.js       758.60 KiB / gzip: 235.17 KiB
Done in 32.22s.

Image compression optimization

Picture compression, in awesome-vite Find vite plugin imagemin and configure it as follows:

viteImagemin({
    gifsicle: {
        optimizationLevel: 7,
        interlaced: false,
    },
    optipng: {
        optimizationLevel: 7,
    },
    mozjpeg: {
        quality: 20,
    },
    pngquant: {
        quality: [0.8, 0.9],
        speed: 4,
    },
    svgo: {
        plugins: [
        {
            name: 'removeViewBox',
        },
        {
            name: 'removeEmptyAttrs',
            active: false,
        },
        ],
    },
})

Repackage to see the effect:

[vite-plugin-imagemin]- compressed image resource successfully:
dist/assets/mos_meituan@3x.91fce938.png             -73%  14.19kb / tiny: 3.85kb
dist/assets/mos_jj@3x.24678237.png                  -66%  9.92kb / tiny: 3.45kb
dist/assets/mos_bg_gold2.64b6b5e9.png               -91%  18.66kb / tiny: 1.84kb
dist/assets/mos_xiecheng@3x.0ca58b53.png            -76%  11.89kb / tiny: 2.95kb
dist/assets/mos_bg_null2.01d64f5d.png               -91%  7.25kb / tiny: 0.71kb
dist/assets/mos_good.f267c228.png                   -76%  36.00kb / tiny: 8.83kb
dist/assets/mos_bad.58afe320.png                    -78%  28.50kb / tiny: 6.32kb
dist/assets/mos_silver.7da7643a.png                 -79%  71.10kb / tiny: 14.95kb
dist/assets/mos_copper.483f2a6f.png                 -81%  79.09kb / tiny: 15.76kb
dist/assets/mos_gold.fa42d3e3.png                   -78%  72.31kb / tiny: 15.93kb
dist/assets/mos_null.753e5b08.png                   -75%  35.62kb / tiny: 8.92kb
dist/assets/mos_bonus_points_bg@3x.e18c09a9.png     -91%  175.78kb / tiny: 16.21kb
dist/assets/mos_score_bg.eb2dc424.png               -18%  15.92kb / tiny: 13.06kb
dist/assets/mos_bg_copper.515f59b3.png              -76%  86.77kb / tiny: 21.26kb
dist/assets/mos_bg_gold.c1f61403.png                -74%  93.59kb / tiny: 24.36kb
dist/assets/mos_bg_silver.4e31d711.png              -72%  82.74kb / tiny: 23.70kb
dist/assets/mos_price_bad.919aaddf.png              -65%  46.72kb / tiny: 16.47kb
dist/assets/mos_bg_null.8ae1448c.png                -66%  55.00kb / tiny: 18.97kb
dist/assets/mos_price_good.563cd841.png             -72%  62.14kb / tiny: 17.86kb
dist/assets/mos_bg_null1.aa018f69.png               -48%  74.54kb / tiny: 38.92kb
dist/assets/mos_qa.8c12f693.png                     -77%  440.17kb / tiny: 103.91kb
dist/assets/mos_bg_gold1.9e337e71.png               -66%  157.11kb / tiny: 54.89kb
dist/assets/mos_bg_good1.d6390420.png               -65%  146.27kb / tiny: 52.56kb
dist/assets/mos_bg_copper1.9941fb6f.png             -60%  136.03kb / tiny: 55.20kb
dist/assets/mos_applyfor_img_desc2@3x.f63db53c.png  -70%  669.85kb / tiny: 204.32kb
dist/assets/mos_bg_bad1.1ceacacc.png                -68%  176.29kb / tiny: 56.69kb
dist/assets/mos_bg_silver1.e4fc875c.png             -63%  149.86kb / tiny: 56.70kb
dist/assets/mos_applyfor_img_desc1@3x.b87c6d38.png  -71%  639.01kb / tiny: 189.78kb
dist/assets/mos_index_bg@3x.3cf17a3f.png            -80%  210.25kb / tiny: 42.16kb
Done in 79.86s.67

In this way, the problem of excessive picture resources is solved. Picture resources can also be uploaded to CDN for acceleration.

There will be a problem here: image compression will be carried out every time.

Although the advantage is that it will not affect the original picture, it will lead to repeated work and occupy packaging time. Since compressed images are needed for production, there is no need to retain the source images. So I referred to it vite-plugin-imagemin and imagemin A script is written to realize the following functions:

  1. Automatically execute this script for image compression before packaging
  2. Generate an imagemin after compression map. JSON files record which images have been compressed and do not need secondary compression
  3. Before compressing, check whether the modification time value of the current picture is within In the imagemin file, if it is, it will be filtered. If it is not, it needs to be compressed
  4. The compressed image is processed to cover the original path

Yes, packages JSON adds a pre script, and when executing yarn build:test, it will automatically execute yarn prebuild:test first

"scripts": {
    "prebuild:test": "node scripts/imagemin.mjs",
    "build:test": "vite build --mode test"
  },

Here are scripts / imagemin MJS code

import { promises as fs } from 'fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { globby } from 'globby';
import chalk from 'chalk';
import convertToUnixPath from 'slash';
import ora from 'ora';
import imagemin from 'imagemin';
import imageminGifsicle from 'imagemin-gifsicle';
import imageminOptpng from 'imagemin-optipng';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
import imageminSvgo from 'imagemin-svgo';

// 1. Create imagemin map. If the JSON cache table has been processed, it will not be processed. After processing, it will be updated to imagemin map. json
// 2. You need to overwrite the original image. There are multiple folders under assets/images, so you need to solve the path problem of dest and use imagemin Buffer to rewrite
// 3. Some pictures will become larger after compression. In this case, the written file will not be overwritten, but the cached file will be written, and the timestamp is the timestamp of the old file itself
// 4. For more picture type plug-ins, see https://github.com/orgs/imagemin/repositories?type=all

// Cache file
let cacheFilename = '../imagemin.map.json';
// Picture file directory
const input = ['src/assets/images/**/*.{jpg,png,svg,gif}'];
// plug-in unit
const plugins = [
  imageminGifsicle({
    optimizationLevel: 7,
    interlaced: false
  }),
  imageminOptpng({
    optimizationLevel: 7
  }),
  imageminMozjpeg({
    quality: 80
  }),
  imageminPngquant({
    quality: [0.8, 0.9],
    speed: 4
  }),
  imageminSvgo({
    plugins: [
      {
        name: 'removeViewBox'
      },
      {
        name: 'removeEmptyAttrs',
        active: false
      }
    ]
  })
];
const debug = false;
let tinyMap = new Map();
let filePaths = [];
let cache, cachePath;
let handles = [];
let time;
const spinner = ora('Picture compression in progress...');
(async () => {
  const unixFilePaths = input.map((path) => convertToUnixPath(path));
  cachePath = path.resolve(
    path.dirname(fileURLToPath(import.meta.url)),
    cacheFilename
  );
  cache = await fs.readFile(cachePath);
  cache = JSON.parse(cache.toString() || '{}');
  // Match file path by wildcard
  filePaths = await globby(unixFilePaths, { onlyFiles: true });
  // If the file is not in imagemin map. JSON, then join the queue;
  // If the file is in.imagemin map. JSON, and the modification time is inconsistent, then join the queue;
  filePaths = await filter(filePaths, async (filePath) => {
    let ctimeMs = cache[filePath];
    let mtimeMs = (await fs.stat(filePath)).mtimeMs;
    if (!ctimeMs) {
      debug && console.log(filePath + 'Not listed in cache');
      tinyMap.set(filePath, {
        mtimeMs
      });
      return true;
      // The system timestamp is larger than date Now() is more accurate, with three more digits after the decimal point, so it is considered to be an effective cache within 1ms
    } else {
      if (Math.abs(ctimeMs - mtimeMs) > 1) {
        debug &&
          console.log(`
          ${filePath}Listed in the cache but expired, ${ctimeMs} ${mtimeMs} differ ${
            ctimeMs - mtimeMs
          }`);
        tinyMap.set(filePath, {
          mtimeMs
        });
        return true;
      } else {
        // debug && console. Log (filepath + 'listed in cache');
        return false;
      }
    }
  });
  debug && console.log(filePaths);
  await processFiles();
})();
// To process a single file, call imagemin Buffer processing
async function processFile(filePath) {
  let buffer = await fs.readFile(filePath);
  let content;
  try {
    content = await imagemin.buffer(buffer, {
      plugins
    });

    const size = content.byteLength,
      oldSize = buffer.byteLength;

    if (tinyMap.get(filePath)) {
      tinyMap.set(filePath, {
        ...tinyMap.get(filePath),
        size: size / 1024,
        oldSize: oldSize / 1024,
        ratio: size / oldSize - 1
      });
    } else {
      tinyMap.set(filePath, {
        size: size / 1024,
        oldSize: oldSize / 1024,
        ratio: size / oldSize - 1
      });
    }

    return content;
  } catch (error) {
    console.error('imagemin error:' + filePath);
  }
}
// Batch processing
async function processFiles() {
  if (!filePaths.length) {
    return;
  }
  spinner.start();
  time = Date.now();
  handles = filePaths.map(async (filePath) => {
    let content = await processFile(filePath);
    return {
      filePath,
      content
    };
  });
  handles = await Promise.all(handles);
  await generateFiles();
}
// Generate files and overwrite source files
async function generateFiles() {
  if (handles.length) {
    handles = handles.map(async (item) => {
      const { filePath, content } = item;
      if (content) {
        if (tinyMap.get(filePath).ratio < 0) {
          await fs.writeFile(filePath, content);
          cache[filePath] = Date.now();
        } else {
          // There is a situation that it becomes larger after compression. This situation does not cover the original image, but will be recorded in the cache table, and the recorded timestamp is the timestamp of the old file itself
          cache[filePath] = tinyMap.get(filePath).mtimeMs;
        }
      }
    });
    handles = await Promise.all(handles);
    handleOutputLogger();
    generateCache();
  }
}
// Generate cache file
async function generateCache() {
  await fs.writeFile(cachePath, Buffer.from(JSON.stringify(cache)), {
    encoding: 'utf-8'
  });
}
// Output results
function handleOutputLogger() {
  spinner.stop();
  console.info('Picture compression succeeded');
  time = (Date.now() - time) / 1000 + 's';
  const keyLengths = Array.from(tinyMap.keys(), (name) => name.length);
  const valueLengths = Array.from(
    tinyMap.values(),
    (value) => `${Math.floor(100 * value.ratio)}`.length
  );

  const maxKeyLength = Math.max(...keyLengths);
  const valueKeyLength = Math.max(...valueLengths);
  tinyMap.forEach((value, name) => {
    let { ratio } = value;
    const { size, oldSize } = value;
    ratio = Math.floor(100 * ratio);
    const fr = `${ratio}`;

    // There is a situation that it becomes larger after compression. This situation does not cover the original image, so this situation shows 0%
    const denseRatio =
      ratio > 0
        ? // ? chalk.red(`+${fr}%`)
          chalk.green(`0%`)
        : ratio <= 0
        ? chalk.green(`${fr}%`)
        : '';

    const sizeStr =
      ratio <= 0
        ? `${oldSize.toFixed(2)}kb / tiny: ${size.toFixed(2)}kb`
        : `${oldSize.toFixed(2)}kb / tiny: ${oldSize.toFixed(2)}kb`;

    console.info(
      chalk.dim(
        chalk.blueBright(name) +
          ' '.repeat(2 + maxKeyLength - name.length) +
          chalk.gray(
            `${denseRatio} ${' '.repeat(valueKeyLength - fr.length)}`
          ) +
          ' ' +
          chalk.dim(sizeStr)
      )
    );
  });
  console.info('Total image compression time', time);
}
// The filter does not support asynchronous processing. Use map to simulate the filter
// https://stackoverflow.com/questions/33355528/filtering-an-array-with-a-function-that-returns-a-promise/46842181#46842181
async function filter(arr, callback) {
  const fail = Symbol();
  return (
    await Promise.all(
      arr.map(async (item) => ((await callback(item)) ? item : fail))
    )
  ).filter((i) => i !== fail);
}

The output of execution information is as follows

$ yarn build:test
yarn run v1.22.17
warning package.json: No license field
$ node scripts/imagemin.mjs
 Picture compression succeeded
src/assets/images/comment/arrow_down_grey.png        -53%  0.40kb / tiny: 0.19kb
src/assets/images/mos/arrow_down_grey.png            -53%  0.40kb / tiny: 0.19kb
src/assets/images/mos/arrow_right_black.png          -47%  0.36kb / tiny: 0.20kb
src/assets/images/mos/back@3x.png                    -6%   0.30kb / tiny: 0.29kb
src/assets/images/mos/basepoint-right.png            -21%  0.21kb / tiny: 0.17kb
src/assets/images/mos/back_left_black@3x.png         -13%  0.31kb / tiny: 0.27kb
src/assets/images/mos/bg_mos_improve@3x.png          0%   58.90kb / tiny: 58.90kb
src/assets/images/mos/ic_0.png                       -16%  0.28kb / tiny: 0.24kb
src/assets/images/mos/ic_0_active.png                -17%  0.28kb / tiny: 0.24kb
src/assets/images/mos/ic_1.png                       -24%  0.46kb / tiny: 0.36kb
src/assets/images/mos/ic_1_active.png                -26%  0.49kb / tiny: 0.36kb
src/assets/images/mos/ic_2.png                       -21%  1.06kb / tiny: 0.85kb
src/assets/images/mos/ic_2_active.png                -15%  1.08kb / tiny: 0.92kb
src/assets/images/mos/ic_3.png                       -21%  0.88kb / tiny: 0.70kb
src/assets/images/mos/ic_3_active.png                -18%  0.88kb / tiny: 0.73kb
src/assets/images/mos/ic_4.png                       0%    0.13kb / tiny: 0.13kb
src/assets/images/mos/ic_4_active.png                0%    0.13kb / tiny: 0.13kb
src/assets/images/mos/ic_5.png                       -14%  0.63kb / tiny: 0.54kb
src/assets/images/mos/ic_5_active.png                -12%  0.62kb / tiny: 0.55kb
src/assets/images/mos/mos_addPhoto_bg@3x.png         -83%  13.88kb / tiny: 2.42kb
src/assets/images/mos/mos_add_photo@3x.png           -79%  9.27kb / tiny: 2.02kb
src/assets/images/mos/mos_applyfor_img_desc1@3x.png  -71%  639.01kb / tiny: 190.52kb
src/assets/images/mos/mos_applyfor_img_desc2@3x.png  -70%  669.85kb / tiny: 205.11kb
src/assets/images/mos/mos_applyFor_tip@3x.png        -72%  1.55kb / tiny: 0.44kb
src/assets/images/mos/mos_au@3x.png                  -82%  173.61kb / tiny: 31.70kb
src/assets/images/mos/mos_bad.png                    -78%  28.50kb / tiny: 6.32kb
src/assets/images/mos/mos_bg_bad1.png                -68%  176.29kb / tiny: 56.82kb
src/assets/images/mos/mos_bg_bad2.png                -71%  1.62kb / tiny: 0.48kb
src/assets/images/mos/mos_bg_copper.png              -76%  86.77kb / tiny: 21.29kb
src/assets/images/mos/mos_bg_copper1.png             -60%  136.03kb / tiny: 55.38kb
src/assets/images/mos/mos_bg_copper2.png             -71%  1.54kb / tiny: 0.45kb
src/assets/images/mos/mos_bg_gold.png                -74%  93.59kb / tiny: 24.39kb
src/assets/images/mos/mos_bg_gold1.png               -66%  157.11kb / tiny: 54.96kb
src/assets/images/mos/mos_bg_gold2.png               -90%  18.66kb / tiny: 1.87kb
src/assets/images/mos/mos_bg_good1.png               -64%  146.27kb / tiny: 52.75kb
src/assets/images/mos/mos_bg_good2.png               -71%  1.48kb / tiny: 0.43kb
src/assets/images/mos/mos_bg_null.png                -66%  55.00kb / tiny: 19.00kb
src/assets/images/mos/mos_bg_null1.png               -48%  74.54kb / tiny: 39.06kb
src/assets/images/mos/mos_bg_null2.png               -91%  7.25kb / tiny: 0.72kb
src/assets/images/mos/mos_bg_silver.png              -72%  82.74kb / tiny: 23.72kb
src/assets/images/mos/mos_bg_silver1.png             -63%  149.86kb / tiny: 56.85kb
src/assets/images/mos/mos_bg_silver2.png             -72%  1.53kb / tiny: 0.44kb
src/assets/images/mos/mos_bonus_add@3x.png           -57%  1.69kb / tiny: 0.73kb
src/assets/images/mos/mos_bonus_points_bg@3x.png     -91%  175.78kb / tiny: 16.24kb
src/assets/images/mos/mos_copper.png                 -81%  79.09kb / tiny: 15.78kb
src/assets/images/mos/mos_gold.png                   -78%  72.31kb / tiny: 15.94kb
src/assets/images/mos/mos_gold@3x.png                -79%  148.05kb / tiny: 31.80kb
src/assets/images/mos/mos_good.png                   -76%  36.00kb / tiny: 8.83kb
src/assets/images/mos/mos_index_bg@3x.png            -80%  210.25kb / tiny: 42.21kb
src/assets/images/mos/mos_jj@3x.png                  -66%  9.92kb / tiny: 3.46kb
src/assets/images/mos/mos_main_bg.png                -71%  57.49kb / tiny: 16.94kb
src/assets/images/mos/mos_main_bg1.png               -72%  16.49kb / tiny: 4.72kb
src/assets/images/mos/mos_main_bg2.png               -64%  41.01kb / tiny: 14.95kb
src/assets/images/mos/mos_meituan@3x.png             -73%  14.19kb / tiny: 3.87kb
src/assets/images/mos/mos_null.png                   -75%  35.62kb / tiny: 8.93kb
src/assets/images/mos/mos_price_bad.png              -65%  46.72kb / tiny: 16.49kb
src/assets/images/mos/mos_price_good.png             -72%  62.14kb / tiny: 17.88kb
src/assets/images/mos/mos_qa.png                     -77%  440.17kb / tiny: 104.05kb
src/assets/images/mos/mos_score_bg.png               -18%  15.92kb / tiny: 13.07kb
src/assets/images/mos/mos_shadow.png                 -45%  0.50kb / tiny: 0.28kb
src/assets/images/mos/mos_silver.png                 -79%  71.10kb / tiny: 14.96kb
src/assets/images/mos/mos_silver@3x.png              -82%  159.11kb / tiny: 30.06kb
src/assets/images/mos/mos_xiecheng@3x.png            -76%  11.89kb / tiny: 2.95kb
 Total image compression time 122.469s
$ vite build --mode test
D:\wehotel-hyt-h5\src\main.ts test
vite v2.8.4 building for test...
transforming (1056) node_modules\call-bind\callBound.jsUse of eval is strongly discouraged, as it poses security risks and may cause issues with minification
✓ 1086 modules transformed.
dist/assets/polyfills-legacy.749f4000.js   68.82 KiB / gzip: 27.57 KiB
dist/assets/echarts-legacy.fa6a2a05.js     360.15 KiB / gzip: 119.61 KiB
dist/assets/index-legacy.ac103818.js       773.37 KiB / gzip: 243.56 KiB

(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
dist/assets/mos_good.7bf63890.png                    8.83 KiB
dist/assets/mos_bad.6fa9e0dc.png                     6.32 KiB
dist/assets/mos_bonus_points_bg@3x.55ee32a1.png      16.24 KiB
dist/assets/mos_copper.9a60b8a1.png                  15.78 KiB
dist/assets/mos_silver.f28117bd.png                  14.96 KiB
dist/assets/mos_bg_good1.f6f8697a.png                52.75 KiB
dist/assets/mos_gold.31ebe01d.png                    15.94 KiB
dist/assets/mos_bg_bad1.72c4e590.png                 56.82 KiB
dist/assets/mos_null.10696c0f.png                    8.93 KiB
dist/assets/mos_bg_copper1.60acd92a.png              55.38 KiB
dist/assets/mos_bg_silver1.92272192.png              56.85 KiB
dist/assets/mos_bg_null1.357a30a0.png                39.06 KiB
dist/assets/mos_bg_gold1.a15e5641.png                54.96 KiB
dist/assets/mos_qa.51ee2b68.png                      104.05 KiB
dist/assets/mos_score_bg.0f990b30.png                13.07 KiB
dist/assets/mos_bg_copper.db603582.png               21.29 KiB
dist/assets/mos_bg_silver.a768f625.png               23.72 KiB
dist/assets/mos_bg_gold.ddd22ca4.png                 24.39 KiB
dist/assets/mos_bg_null.ea3f4937.png                 19.00 KiB
dist/assets/mos_price_bad.5bafec2a.png               16.49 KiB
dist/assets/mos_price_good.378eeaff.png              17.88 KiB
dist/assets/mos_index_bg@3x.ceab3831.png             42.21 KiB
dist/assets/mos_applyfor_img_desc1@3x.9d293ba9.png   190.52 KiB
dist/assets/mos_applyfor_img_desc2@3x.1ec8156e.png   205.11 KiB
dist/index.html                                      6.23 KiB
dist/assets/index.cfc146ac.css                       162.39 KiB / gzip: 44.63 KiB
dist/assets/echarts.c2408918.js                      360.93 KiB / gzip: 119.38 KiB
dist/assets/index.1b17a995.js                        612.49 KiB / gzip: 199.82 KiB

(!) Some chunks are larger than 500 KiB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
Done in 159.94s.

It can be seen that the first packaging takes an additional 122.469s, and the second packaging takes an average of 159.94 - 122.469 = 37.47s without adding pictures. If a new picture is added later, only the new picture will be compressed.

This scheme also has disadvantages:

  1. Overwrite the original image. It's inconvenient to see the effect when setting the parameters of the compression plug-in. You need to back it up first
  2. The picture files matching the wildcard suffix in the specified directory will be compressed, instead of which pictures are referenced by the business component and which pictures need to be compressed

Remarks about imagemin:

  1. The imagemin package needs to be executed in node > = 12.20.0; It will be slow to download in China. You need to download it in package JSON with field "Resolutions": {"bin wrapper": "NPM: Bin wrapper China"}
  2. imagemin and several other packages are in ESM format and are not compatible with the commonjs specification, so it is better to change the suffix of the script to mjs, another scheme is in package JSON adds the field {"type": "module"}, but considering that other scripts need to use the commonjs specification, they don't do so

summary

Through packaging analysis, it is confirmed that image compression leads to long construction time (79.07 s), and the following factors lead to too large main package (568.19 kiB gzied in total: vendor 530.72 KiB gized, index 37.47 KiB gized):

  1. echarts full packaging
  2. Redundancy exists in legacy package
  3. lodash shouldn't exist

Solution:

  1. Through the manual chunks scheme, ecarts are packaged independently and on demand, reducing the volume of the main package
  2. Reduce legacy packages by removing redundant configurations
  3. Use the eckarts solution to solve the problem of lodash packaged due to the introduction of vue3 eckarts
  4. Through the script, compress the picture before construction, avoid repeated compression next time through caching, and optimize the construction process

achievements:
The main package is reduced from 568.19 kiB gized to 199.82 KiB gized, and the volume is reduced by 64.8%
The construction time is reduced from 79.07 s to 37.47 s, with a 52.6% reduction

Postscript

Although manual chunks is configured under the vue3 eckarts scheme, or manual chunks is configured under the import() scheme, eckarts can be packaged independently, but treeshaking is not performed

Topics: vite rollup