Vue3.x&Vite2.0简易版打包优化

Vue3.x&Vite2.0简易版打包优化,第1张

build配置文件 安装基础插件
npm i @vitejs/plugin-vue  -D
npm i vite-svg-loader -D
npm i @vitejs/plugin-legacy -D
npm i  @vitejs/plugin-vue-jsx -D
npm i vite-plugin-windicss -D
npm i vite-plugin-mock -D
npm i vite-plugin-live-reload -D
npm i vite-plugin-remove-console -D
tsconfig.json

compilerOptions添加以下命令行

 "skipLibCheck": true,
plugins.ts
import vue from "@vitejs/plugin-vue";
import svgLoader from "vite-svg-loader";
import legacy from "@vitejs/plugin-legacy";
import vueJsx from "@vitejs/plugin-vue-jsx";
import WindiCSS from "vite-plugin-windicss";
import { viteMockServe } from "vite-plugin-mock";
import liveReload from "vite-plugin-live-reload";
import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
export function getPluginsList(command, VITE_LEGACY) {
  const prodMock = true;
  const lifecycle = process.env.npm_lifecycle_event;
  return [
    vue(),
    // jsx、tsx语法支持
    vueJsx(),
    WindiCSS(),
    // 线上环境删除console
    removeConsole(),
    // 修改layout文件夹下的文件时自动重载浏览器 解决 https://github.com/xiaoxian521/vue-pure-admin/issues/170
    liveReload(["src/layout/**/*", "src/router/**/*"]),

    // svg组件化支持
    svgLoader(),

    // mock支持
    viteMockServe({
      mockPath: "mock",
      localEnabled: command === "serve",
      prodEnabled: command !== "serve" && prodMock,
      injectCode: `
          import { setupProdMockServer } from './mockProdServer';
          setupProdMockServer();
        `,
      logger: true,
    }),
    // 是否为打包后的文件提供传统浏览器兼容性支持
    VITE_LEGACY
      ? legacy({
          targets: ["ie >= 11"],
          additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
        })
      : null,
    // 打包分析
    lifecycle === "report"
      ? visualizer({ open: true, brotliSize: true, filename: "report.html" })
      : null,
  ];
}

index.ts
// 处理环境变量
const warpperEnv = (envConf: any): any => {
  // 此处为默认值,无需修改
  const ret: any = {
    VITE_PORT: 8848,
    VITE_PUBLIC_PATH: "",
    VITE_PROXY_DOMAIN: "",
    VITE_PROXY_DOMAIN_REAL: "",
    VITE_ROUTER_HISTORY: "",
    VITE_LEGACY: false,
  };

  for (const envName of Object.keys(envConf)) {
    let realName = envConf[envName].replace(/\n/g, "\n");
    realName =
      realName === "true" ? true : realName === "false" ? false : realName;

    if (envName === "VITE_PORT") {
      realName = Number(realName);
    }
    ret[envName] = realName;
    if (typeof realName === "string") {
      process.env[envName] = realName;
    } else if (typeof realName === "object") {
      process.env[envName] = JSON.stringify(realName);
    }
  }
  return ret;
};

// 跨域代理重写
const regExps = (value: string, reg: string): string => {
  return value.replace(new RegExp(reg, "g"), "");
};

export { warpperEnv, regExps };

引入build vite.config.ts
import { resolve } from "path";
import { warpperEnv, regExps } from "./build";
import { getPluginsList } from "./build/plugins";
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";

// 当前执行node命令时文件夹的地址(工作目录)
const root: string = process.cwd();

// 路径查找
const pathResolve = (dir: string): string => {
  return resolve(__dirname, ".", dir);
};

// 设置别名
const alias: Record<string, string> = {
  "/@": pathResolve("src"),
  "@build": pathResolve("build"),
  //解决开发环境下的警告
  "vue-i18n": "vue-i18n/dist/vue-i18n.cjs.js"
};

export default ({ command, mode }: ConfigEnv): UserConfigExport => {
  const {
    VITE_PORT,
    VITE_LEGACY,
    VITE_PUBLIC_PATH,
    VITE_PROXY_DOMAIN,
    VITE_PROXY_DOMAIN_REAL
  } = warpperEnv(loadEnv(mode, root));
  return {
    base: VITE_PUBLIC_PATH,
    root,
    resolve: {
      alias
    },
    css: {
      // https://github.com/vitejs/vite/issues/5833
      postcss: {
        plugins: [
          {
            postcssPlugin: "internal:charset-removal",
            AtRule: {
              charset: atRule => {
                if (atRule.name === "charset") {
                  atRule.remove();
                }
              }
            }
          }
        ]
      }
    },
    // 服务端渲染
    server: {
      // 是否开启 https
      https: false,
      // 端口号
      port: VITE_PORT,
      host: "0.0.0.0",
      // 本地跨域代理
      proxy:
        VITE_PROXY_DOMAIN_REAL.length > 0
          ? {
              [VITE_PROXY_DOMAIN]: {
                target: VITE_PROXY_DOMAIN_REAL,
                // ws: true,
                changeOrigin: true,
                rewrite: (path: string) => regExps(path, VITE_PROXY_DOMAIN)
              }
            }
          : null
    },
    plugins: getPluginsList(command, VITE_LEGACY),
    optimizeDeps: {
      include: [
        "pinia",
        "vue-i18n",
        "lodash-es",
        "@vueuse/core",
        "@iconify/vue",
        "element-plus/lib/locale/lang/en",
        "element-plus/lib/locale/lang/zh-cn",
        "vxe-table/lib/locale/lang/zh-CN",
        "vxe-table/lib/locale/lang/en-US"
      ],
      exclude: ["@zougt/vite-plugin-theme-preprocessor/dist/browser-utils"]
    },
    build: {
      sourcemap: false,
      brotliSize: false,
      // 消除打包大小超过500kb警告
      chunkSizeWarningLimit: 2000
    },
    define: {
      __INTLIFY_PROD_DEVTOOLS__: false
    }
  };
};

安装 rollup-plugin-visualizer
npm i rollup-plugin-visualizer -D
import { visualizer } from 'rollup-plugin-visualizer';
npm build 生成打包分析

图片压缩优化 安装vite-plugin-imagemin
npm i vite-plugin-imagemin -D
添加host文件配置
199.232.4.133 raw.githubusercontent.com
若安装仍报错 图片压缩优化

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

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);
}
loadsh优化 原引入方式
export { cloneDeep, throttle, debounce } from 'lodash'

在 vite.config.js 添加

build: {
    rollupOptions: {
      output: {
        manualChunks:{
          lodash: ['lodash']
        }
      }
    }
  }
优化echarts

echarts 包只打包引用到的组件

// 引入雷达图图表,图表后缀都为 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']
          }
        }
      }
}
优化ElementUI-Plus

elementPlus、AntD-Vue2.x官方已提供优化方案以elementPlus为例

将原本的全量引入改为以下引入方式

首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件

npm install -D unplugin-vue-components unplugin-auto-import
在 vite.config.js 添加

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 
export default {
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/web/1322693.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存