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()],
}),
],
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)