关于vite:记一次vite2x打包优化过程

72次阅读

共计 22483 个字符,预计需要花费 57 分钟才能阅读完成。

背景

最近做的需要是在客户端 webview 内嵌 h5,应用的 vite2.xvue3 来开发,在第一版提测的时候,发现打包之后,总包大小有 4M 多,有很大的优化的空间。

工具链阐明

  1. 目前 vite2.x 是基于 rollup 打包的,而不是 esbuild,详见这里
  2. 应用 rollup-plugin-visualizer 进行打包剖析,打包之后,会在根目录默认生成一个 stats.html 文件
  3. 应用 vite-plugin-imagemin 进行图片压缩。每次打包都会压缩一次,会占用构建耗时,有值得优化的空间,此处先不作探讨
  4. @vitejs/plugin-legacy vite 默认对浏览器反对的基线是反对 ESM 的古代浏览器,这个插件就是用来兼容不反对 ESM 的浏览器,详见这里
  5. 本文波及到的配置文件和脚本都放在我的仓库了:https://github.com/Rockergmail/imagemin-script

优化前

vite.config.js 配置如下

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
        }
    };
};

打包输入信息如下:

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.

从打包输入信息咱们可看出:

  1. vendor-legacy.6772670a.js 打包之后 1896.50 KiB / gzip 之后依然有 576.97 KiB
  2. vendor.88c712d4.js 打包之后 1730.56 KiB / gzip 之后依然有 530.72 KiB
  3. 个别图片资源过大
  4. 构建用了 79.07 秒,存在优化空间

打包优化

应用 rollup-plugin-visualizer 插件,打包之后生成 stats.html,咱们能够看到以下的图表

须要留神的是,rollup-plugin-visualizer 的图标显示的包大小,是分了 chunk 但还没有压缩的时候的大小。

能够察看到:

  1. zenderecharts 占用了大部分的空间,首先须要把这部分解决掉
  2. vant/es@vue 曾经是按需打包了,没有能够优化的空间
  3. loadash/es 再三查看过业务代码中没有用到,为什么还会打包进去?

优化 echarts 打包

因为对 echarts 不算相熟,所以在引入的时候心愿是他人封装好的,选的是 vue3-echarts 这个库,然而从打包后果来看,是把 echarts 全量打包进来了。

所以果决把vue3-echarts 间接换成官网的 echarts

打包之后 zenderecharts 还是在 vendor 内,且没有做 treeshaking,还是全量引进来了(echart 我只用到了 radar 组件,以及 canvas 渲染器,并没有应用svg 渲染器和其余组件)

能够看到,此事 lodash 曾经不存在了,原来是 vue3-echarts 导致的。但 echarts 仍然须要把它排除在 vendor 之外。这里有两个思路:

  1. echarts 作为外置(externals)。在打包的时候,会将 import * as echarts from 'echarts' 解决成 window['echarts'] (依据输入库指标,解决的后果也会不同,此处只思考了浏览器端,是 AMD 标准 的处理结果)。

    • 此计划存在一个毛病:仍然会全量打包。只管能够在 [echarts 官网这里] (https://echarts.apache.org/zh…) 定制化,然而不足灵活性,如果我的项目须要另外引入其余模块,又须要从新定制化。
  2. echarts 作为独立的 chunk 打包,且是 treeshaking 后的 chunk

看到打包时 vite 的告警,有两个计划能够实现 echarts 作为独立的 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()动静引入

把代码改成:

const {echartsUse = use, echartsInit = init} = import('echarts/core');
// 引入雷达图图表,图表后缀都为 Chart
const {RadarChart} = import('echarts/charts');
// 引入雷达图组件,组件后缀都为 Component
const {RadarComponent} = import('echarts/components');
// 引入 Canvas 渲染器,留神引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
const {CanvasRenderer} = import('echarts/renderers');
// 注册必须的组件

打包之后输入

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

能够看到,确实是把 echarts 独立打包进去了,没有占用 vendor 的空间,而且是按 echarts 外面的模块来打包

然而咱们仍然能够看到把没有引入的模块打包进来了:还是能够看到 svg 渲染器、看到除了 radar 之外的组件。

manualChunks

把代码改回去:

// 引入雷达图图表,图表后缀都为 Chart
import {RadarChart} from 'echarts/charts';
// 引入雷达图组件,组件后缀都为 Component
import {RadarComponent} from 'echarts/components';
// 引入 Canvas 渲染器,留神引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import {CanvasRenderer} from 'echarts/renderers';

vite.config.js 增加

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

打包时候的输入

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.

能够看到,vendor 包的内容间接打进了 index包,且多出了 echarts

echarts 包也只是打包了援用到的组件

优化 legacy 包

legacy的配置,是照搬其余我的项目的,思考了 ie >= 11 版本的兼容。这个我的项目只须要思考挪动端,就能够不须要思考 ie 了。间接置为默认配置就好

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

打包之后的输入:

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.

图片压缩优化

图片压缩,在 awesome-vite 找到 vite-plugin-imagemin,配置如下:

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,
        },
        ],
    },
})

从新打包看看成果:

[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

这样就解决了图片资源过大的问题,图片资源后续还能够思考上传到 CDN 减速。

这里会存在一个问题:每次打包都会走一次图片压缩。

尽管益处是不会对原图片产生影响,然而会导致重复劳动,占用打包工夫。既然生产是须要用压缩后的图片了,那么对源图片就没有必要留住了。所以我参考了 vite-plugin-imagemin 和 imagemin 写了一个脚本,能够实现如下性能:

  1. 打包之前主动执行这个脚本进行图片压缩
  2. 压缩之后生成一个 imagemin.map.json 文件,是记录哪些图片曾经压缩过了,不须要二次压缩
  3. 压缩之前查看以后图片的 批改工夫 值是否在 .imagemin 文件内,在的话则过滤走,不在才须要压缩
  4. 压缩之后的图片,笼罩本来门路的解决

packages.json 增加前置脚本,执行 yarn build:test 的时候就会主动先执行 yarn prebuild:test

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

上面是 scripts/imagemin.mjs 的代码

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. 建设 imagemin.map.json 缓存表,如果曾经解决过,则不再解决,解决过就更新到 imagemin.map.json
// 2. 须要笼罩原图,assets/images 下有多个文件夹,所以须要解决 dest 的门路问题,须要用 imagemin.buffer 来重写
// 3. 有些图片在压缩完之后会变得更大,这种状况不笼罩写入文件,然而要写入缓存文件,且工夫戳是旧文件本人的工夫戳
// 4. 更多图片类型的插件见 https://github.com/orgs/imagemin/repositories?type=all

// 缓存文件
let cacheFilename = '../imagemin.map.json';
// 图片文件目录
const input = ['src/assets/images/**/*.{jpg,png,svg,gif}'];
// 插件
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('图片压缩中...');
(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() || '{}');
  // 通过通配符匹配文件门路
  filePaths = await globby(unixFilePaths, { onlyFiles: true});
  // 如果文件不在 imagemin.map.json 上,则退出队列;// 如果文件在 imagemin.map.json 上,且批改工夫不统一,则退出队列;filePaths = await filter(filePaths, async (filePath) => {let ctimeMs = cache[filePath];
    let mtimeMs = (await fs.stat(filePath)).mtimeMs;
    if (!ctimeMs) {debug && console.log(filePath + '不在缓存入列');
      tinyMap.set(filePath, {mtimeMs});
      return true;
      // 零碎工夫戳,比 Date.now()更精准,多了小数点后三位,所以管制在 1ms 内都认为是无效缓存} else {if (Math.abs(ctimeMs - mtimeMs) > 1) {
        debug &&
          console.log(`
          ${filePath}在缓存但过期了而入列,${ctimeMs} ${mtimeMs} 相差 ${ctimeMs - mtimeMs}`);
        tinyMap.set(filePath, {mtimeMs});
        return true;
      } else {// debug && console.log(filePath + '在缓存而入列');
        return false;
      }
    }
  });
  debug && console.log(filePaths);
  await processFiles();})();
// 解决单个文件,调用 imagemin.buffer 解决
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);
  }
}
// 批量解决
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();}
// 生成文件并笼罩源文件
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 {
          // 存在压缩之后反而变大的状况,这种状况不笼罩原图,但会记录到缓存表中,且记录的工夫戳是旧文件本人的工夫戳
          cache[filePath] = tinyMap.get(filePath).mtimeMs;
        }
      }
    });
    handles = await Promise.all(handles);
    handleOutputLogger();
    generateCache();}
}
// 生成缓存文件
async function generateCache() {await fs.writeFile(cachePath, Buffer.from(JSON.stringify(cache)), {encoding: 'utf-8'});
}
// 输入后果
function handleOutputLogger() {spinner.stop();
  console.info('图片压缩胜利');
  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}`;

    // 存在压缩之后反而变大的状况,这种状况不笼罩原图,所以这种状况显示 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('图片压缩总耗时', time);
}
// filter 不反对异步解决,用 map 来模仿 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);
}

执行信息输入如下

$ yarn build:test
yarn run v1.22.17
warning package.json: No license field
$ node scripts/imagemin.mjs
图片压缩胜利
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
图片压缩总耗时 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.

能够看到,第一次打包的时候须要额定耗时 122.469s,第二次打包在不增加图片的状况下均匀耗时 159.94 - 122.469 = 37.47s。如果前面增加了新图片,也只对新图片进行压缩。

这种计划也有毛病:

  1. 笼罩原图,在设置压缩插件参数的时候,不不便看成果,须要先备份好
  2. 对指定目录下匹配通配符后缀的图片文件都会进行压缩,而不是业务组件援用了哪些图片,哪些图片才须要压缩

对于 imagemin 的备注:

  1. imagemin 这个包须要在 node >= 12.20.0 执行;在国内下载会比较慢,须要在 package.json 加字段 "resolutions": {"bin-wrapper": "npm:bin-wrapper-china"}
  2. imagemin 以及其余几个包都是 ESM 格局的包,没有做 commonjs 标准的兼容,所以在 node 环境执行我写的脚本,最好把脚本的后缀改为 .mjs,另外一种计划是在 package.json 加字段 {"type": "module"},但思考到其余脚本须要应用commonjs 标准,就没这么做了

总结

通过打包剖析,确认了图片压缩导致了构建工夫长(79.07 s),确认了以下因素导致了主包过大(共 568.19 kiB gzied:vendor 530.72 KiB gized,index 37.47 KiB gized):

  1. echarts 全量打包
  2. legacy 包存在冗余
  3. lodash 本不应该存在

解决方案:

  1. 通过 manualChunks 计划把 echarts 独立打包并按需打包,缩小主包体积
  2. 通过去掉冗余配置,加重 legacy
  3. 改用 echarts 计划,解决因为 vue3-echarts 引入而打包进来 lodash 的问题
  4. 通过脚本,在构建之前压缩图片,并通过缓存防止下次反复压缩,优化构建过程

成绩:
主包从 568.19 kiB gized 缩小到 199.82 KiB gized,体积缩小了 64.8%
构建过程耗时从 79.07 s 缩小到 37.47 s,耗时缩小了 52.6%

后记

只管在 vue3-echarts 计划下配置 manualChunks,或者 在 import() 计划下配置 manualChunks,尽管能够把 echarts 独立打包,然而没有进行 treeshaking

正文完
 0