前言
在使用 vue3 + vite 实际开发过程中的一些经验分享,涵盖 vite 构建优化配置项的实践,以及打包配置性能优化的实践
plugin 项目优化汇总
@vitejs/plugin-vue
vite 支持 vue 开发
按需引入组件库 unplugin-vue-components
unplugin-vue-components 插件可以在 Vue 文件中自动引入组件(包括项目自身的组件和各种组件库中的组件)作者是 Vite 生态圈大名鼎鼎的 Anthony Fu。使用此插件后,不需要手动编写 import { Button } from 'vant'
这样的代码了,插件会自动识别 template
中使用的自定义组件并自动注册。
unplugin-vue-components 是由 Vue 官方人员开发的一款自动引入插件,可以省去比如 UI 库的大量 import 语句。
// 1、安装
npm i unplugin-vue-components -D
// 2、配置
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
Components({
dts: true,
// dirs: ['src/components'], // 配置需要默认导入的自定义组件文件夹,该文件夹下的所有组件都会自动import;
resolvers: [ElementPlusResolver()]
}),
]
}
// 3、配置tsconfig.json
{
// ...
"include": ["./components.d.ts"]
}
// 插件的所有默认配置
Components({
// relative paths to the directory to search for components.
// 要搜索组件的目录的相对路径
dirs: ["src/components"],
// valid file extensions for components.
// 组件的有效文件扩展名。
extensions: ["vue"],
// search for subdirectories
// 搜索子目录
deep: true,
// resolvers for custom components
// 自定义组件的解析器
resolvers: [],
// generate `components.d.ts` global declarations,
// also accepts a path for custom filename
// 生成 `components.d.ts` 全局声明,
// 也接受自定义文件名的路径
dts: false,
// Allow subdirectories as namespace prefix for components.
// 允许子目录作为组件的命名空间前缀。
directoryAsNamespace: false,
// 忽略命名空间前缀的子目录路径
// 当`directoryAsNamespace: true` 时有效
// Subdirectory paths for ignoring namespace prefixes
// works when `directoryAsNamespace: true` globalNamespaces: [],
// auto import for directives
// default: `true` for Vue 3, `false` for Vue 2
// Babel is needed to do the transformation for Vue 2, it's disabled by default for performance concerns.
// To install Babel, run: `npm install -D @babel/parser @babel/traverse`
// 自动导入指令
// 默认值:Vue 3 的`true`,Vue 2 的`false`
// 需要 Babel 来为 Vue 2 进行转换,出于性能考虑,它默认处于禁用状态。
directives: true,
// filters for transforming targets
include: [/.vue$/, /.vue?vue/],
exclude: [/[\/]node_modules[\/]/, /[\/].git[\/]/, /[\/].nuxt[\/]/],
});
unplugin-auto-import 的使用
unplugin-auto-import 为 Vite、Webpack、Rollup 和 esbuild 按需自动导入 API,支持 TypeScript
这个插件是为了解决在开发中的导入问题,比如经常不清楚相对路径的问题,这个插件就是解决这个问题。这个插件会在根目录生成一个 auto-import.d.ts,这个文件会将所有的插件导入到 global 中,这样在使用的时候直接就可以使用了
效果
// 引入前
import { ref, computed } from "vue";
const count = ref(0);
const doubled = computed(() => count.value * 2);
//引入后
const count = ref(0);
const doubled = computed(() => count.value * 2);
// 引入前
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
//引入后
export function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
使用
// 1、安装
npm i unplugin-vue-components -D
// 2、配置文件vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
AutoImport({
dts: 'types/auto-imports.d.ts', // 生成配置文件,如果是ts项目,通常我们会把声明文件放在根目录/types中,
// 注意,这个文件夹需要先建好,否则可能导致等下无法往里生成auto-imports.d.ts文件
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
eslintrc: {
enabled: false, // 默认false, true启用。生成一次就可以,避免每次工程启动都生成,一旦生成配置文件之后,最好把enable关掉,即改成false
//否则这个文件每次会在重新加载的时候重新生成,这会导致eslint有时会找不到这个文件。当需要更新配置文件的时候,再重新打开
filepath: './.eslintrc-auto-import.json', // 生成json文件,可以不配置该项,默认就是将生成在根目录
globalsPropValue: true,
},
})
]
})
// 3、添加.eslintrc-auto-import.json
{
"globals": {}
}
// 4、配置.eslintrc
{
...
"extends": [
...
"./.eslintrc-auto-import.json"
]
}
// 5、配置tsconfig.json
{
...
"include": ["./auto-imports.d.ts"]
}
vite-plugin-style-import 使用
当使用 unplugin-vue-components 来引入 ui 库的时候,message, notification 等引入样式不生效。此时就需要安装 vite-plugin-style-import 即可
// vite.config.js
import { defineConfig } from "vite";
import styleImport, {
AndDesignVueResolve,
VantResolve,
ElementPlusResolve,
NutuiResolve,
AntdResolve,
} from "vite-plugin-style-import";
export default defineConfig({
plugins: [
styleImport({
resolves: [
AndDesignVueResolve(),
VantResolve(),
ElementPlusResolve(),
NutuiResolve(),
AntdResolve(),
],
// 自定义规则
libs: [
{
libraryName: "ant-design-vue",
esModule: true,
resolveStyle: (name) => {
return `ant-design-vue/es/${name}/style/index`;
},
},
],
}),
],
// 引用使用less的库要配置一下
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
},
});
jsx/tsx 语法支持 @vitejs/plugin-vue-jsx
此插件支持在 vue3 中使用 jsx/tsx 语法
npm i @vitejs/plugin-vue-jsx -D
vite.config.ts
import { defineConfig, loadEnv } from "vite";
import vuejsx from "@vitejs/plugin-vue-jsx";
export default ({ mode }) =>
defineConfig({
plugins: [vuejsx()],
});
jsx 文件: (jsx 组件中自动跳过生命周期,即 jsx 中没有生命周期,在父组件 onBeforeMount 后执行)
const component = (props: any, context: any) => {
console.log(props);
const onClick = () => {
context.emit("update");
};
return (
<div
style={{
fontSize: 12,
color: "#999",
}}
onClick={() => onClick()}
>
我是jsx函数组件{props.text}
</div>
);
};
export default component;
移动端 vconsole vite-plugin-vconsole
帮助开发者在各个环境下方便使用 VConsole 的功能。可以方便配置区分环境,根据环境动态加载 VConsole,支持多页面配置。
// 安装
npm i vite-plugin-vconsole -D
// tips: 如果引用path提示不存在,可以引入@types/node包
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { viteVConsole } from 'vite-plugin-vconsole';
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
console.log(mode)
const config: UserConfig = {
plugins: [
vue(),
viteVConsole({
entry: resolve('src/main.ts'), // 或者可以使用这个配置: [path.resolve('src/main.ts')]
enabled: mode !== 'production', // 可自行结合 mode 和 command 进行判断
config: {
maxLogNumber: 1000,
theme: 'dark'
}
})
],
}
return config
})
svg 作为组件使用 [vite-plugin-svg-icons]
方便在项目中使用 .svg 文件
//1、 安装:
npm i vite-plugin-svg-icons -D
//2、 vite.config.js配置
import { defineConfig,loadEnv } from 'vite'
import {createSvgIconsPlugin} from 'vite-plugin-svg-icons';
const path = require("path");
export default ({ mode }) => defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定要缓存的文件夹
iconDirs: [resolve(process.cwd(), 'src/assets/svg')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]'
})
]
})
//3、main.js添加
import 'virtual:svg-icons-register'
//4、模块中使用
<svg style="width: 30px; height: 30px" fill="red">
<use xlink:href="#icon-order" fill="blue"></use>
</svg>
// 更多配置说明
viteSvgIcons({
// 指定图标文件夹,数组形式,可以有多个
iconDirs: [path.resolve(process.cwd(), 'icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
// 自定义插入位置
inject?: 'body-last' | 'body-first',
// 设置图标样式类名
className?: 'svg-icon',
// 设置svg元素属性
svgoOptions: {},
// 指定标题内容
title?: false,
// 生成sprite
svgSprite: false,
// 导入图标接口
runtimeIconImport: 'import-all' | 'async',
})
封装组件,新建 SvgIcon.vue
SvgIcon.vue 可结合unplugin-vue-components
放置在src/components
下,做自动引入
<!-- 页面中使用 -->
<svg-icon name="order" class="icon"></svg-icon>
<template>
<svg
aria-hidden="true"
class="svg-icon"
:fill="props.color"
:width="width"
:height="height"
>
<use :xlink:href="symbolId" />
</svg>
</template>
<script setup name="svg-icon">
// import { computed } from 'vue'
const props = defineProps({
prefix: {
type: String,
default: "icon",
},
name: {
type: String,
required: true,
},
size: {
type: String,
default: "1em",
},
width: {
type: String,
},
height: {
type: String,
},
color: {
type: String,
default: "currentColor",
},
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
const width = computed(() => (props.width ? props.width : props.size));
const height = computed(() => (props.height ? props.height : props.size));
</script>
<style scope>
.svg-icon {
fill: currentColor;
outline: none;
font-size: 14px;
}
</style>
自适应 px 转 vw postcss-px-to-viewport-8-plugin
// 安装
npm install postcss-px-to-viewport-8-plugin -D
// vite.config.js 配置
import { defineConfig } from 'vite'
import pxtovw from 'postcss-px-to-viewport-8-plugin'
export default defineConfig({
css: {
postcss: {
plugins: [
// 执行插件px转vw
pxtovw({
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 375, // 设计稿的视口宽度
viewportUnit: 'vw' // 希望使用的视口单位
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*'], // 能转化为vw的属性列表
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: undefined, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: undefined, // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1920 // 横屏时使用的视口宽度
})
]
}
}
})
打包分析插件 rollup-plugin-visualizer
打包后,会在根目录下生成一个 stats.html 文件
// 安装
npm install rollup-plugin-visualizer -D
// vite.config.js 配置
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [visualizer()]
})
文件资源 vite-plugin-compression
使用 gzip 或者 brotli 来压缩资源
// 安装
npm i vite-plugin-compression -D
// vite.config.ts
import { defineConfig,loadEnv} from 'vite'
import viteCompression from 'vite-plugin-compression';
export default ({ mode }) => defineConfig({
plugins: [
viteCompression()
]
})
图片资源压缩 vite-plugin-imagemin
npm i vite-plugin-imagemin -D
// vite.config.ts
import { defineConfig} from 'vite'
import viteImagemin from 'vite-plugin-imagemin'
export default ({ mode }) => defineConfig({
plugins: [
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-vue-images
自动导入图像,同级目录的文件名不能重复!
npm i vite-plugin-vue-images -D
import { defineConfig,loadEnv} from 'vite'
import ViteImages from 'vite-plugin-vue-images'
export default ({ mode }) => defineConfig({
plugins: [
ViteImages({
dirs: ['src/assets'], // 图像目录的相对路径
extensions: ['jpg', 'jpeg', 'png', 'svg', 'webp'], // 有效的图像扩展
customResolvers:[], // 覆盖名称->图像路径解析的默认行为
customSearchRegex: '([a-zA-Z0-9]+)', // 重写搜索要替换的变量的Regex。
}),
]
假设有以下文件及路径
logo.png: src/assets/logo.png
name1.jpg: src/assets/test/name1.jpg
使用方式:
<template>
<div class="home">
<img :src="Logo" />
<img :src="TestName1" />
</div>
</template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>
插件将转换为:
<template>
<div class="home">
<img :src="Logo" />
<img :src="TestName1" />
</div>
</template>
<script setup lang="ts">
import Logo from "@/assets/logo.png";
import TestName1 from "@/assets/test/name1.jpg";
</script>
<style lang="less" scoped></style>
CDN 加速
使用 CDN 未必会加快速度,只能减小打包体积,因为对应 js 和 css 需要从远程地址读取
方案一:(推荐)使用 CDN 管理插件vite-plugin-cdn-import
npm install vite-plugin-cdn-import --save-dev
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 文档的用法会报错, 要这样引入才可以
import { autoComplete, Plugin as importToCDN } from 'vite-plugin-cdn-import';
export default defineConfig({
plugins: [vue(),
importToCDN({
prodUrl: 'https://unpkg.com/{name}@{version}/{path}',
modules: [
autoComplete('vue'),
autoComplete('axios'),
{
name: 'element-plus',
var: 'ElementPlus', //根据main.js中定义的来
version: '2.2.17',
path: 'dist/index.full.js',
css: 'dist/index.css'
},
{
name: '@element-plus/icons-vue',
var: 'ElementPlusIconsVue', //根据main.js中定义的来
version: '2.0.9',
path: 'dist/index.iife.min.js'
},
],
})
],
})
方法二: rollup 的 external 功能结合插件 rollup-plugin-external-globals
// 安装
npm install rollup-plugin-external-globals -D
// vite.config.ts
import externalGlobals from 'rollup-plugin-external-globals';
const globals = externalGlobals({
axios: 'axios'
});
export default defineConfig({
build: {
rollupOptions: {
external: ['axios'],
plugins: [globals],
}
}
})
// 在 index.html 模版中引入对应库的CDN。或者使用插件vite-plugin-html
<script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
辅助作用的插件
SEO 优化,vite 实现预渲染vite-plugin-prerender
打包完成后,会根据配置的路由地址,在 dist 文件中生成对应的 index.html 文件
【在 dist 根目录下、dist/chat、dist/design 目录下都会生成 index.html 文件】
npm i vite-plugin-prerender -D
import vitePrerender from 'vite-plugin-prerender'
import path from 'path'
export default () => {
return {
plugins: [
vitePrerender({
// Required - The path to the vite-outputted app to prerender.
staticDir: path.join(__dirname, 'dist'),
// Required - Routes to render.
routes: ['/', '/chat', '/design'],
}),
],
}
}
setup 语法糖 name 增强 vite-plugin-vue-setup-extend
setup 语法糖 name 增强,使 vue3 语法糖支持 name 属性
vue3 语法糖默认是没有 name 属性的,在我们使用 keep-alive 的时候需要用到 name
npm i vite-plugin-vue-setup-extend -D
import { defineConfig} from 'vite'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
export default ({ mode }) => defineConfig({
plugins: [
vueSetupExtend()
]
}
使用
<script setup lang="ts" name="home"></script>
html 注入动态数据插件 vite-plugin-html
一个针对 index.html,提供压缩和基于 ejs 模板功能的 vite 插件
通过搭配 .env 文件,可以在开发或构建项目时,对 index.html 注入动态数据,例如替换网站标题 安装
npm i vite-plugin-html -D
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="./favicon.ico" />
<link rel="stylesheet" href="./public/reset.css" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<title><%- title %></title>
</head>
<body>
<div id="app"></div>
<%- injectScript %>
</body>
</html>
vite.config.ts
import { defineConfig, loadEnv } from "vite";
import { createHtmlPlugin } from "vite-plugin-html";
export default ({ mode }) =>
defineConfig({
// mode 环境变量名,若配置有.env.test,启动时 --mode test,这里的mode就是test
plugins: [
createHtmlPlugin({
minify: true,
/**
* 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
* @default src/main.ts
*/
entry: "/src/main.ts",
/**
* 需要注入 index.html ejs 模版的数据
*/
inject: {
data: {
// 查找.env.test文件里面的VITE_PROJECT_TITLE,请以VITE_标识开头
title: loadEnv(mode, process.cwd()).VITE_PROJECT_TITLE,
injectScript: `<script src="/inject.js"></script>`,
},
},
}),
],
});
重启 vite 服务 vite-plugin-restart
通过监听文件修改,自动重启 vite 服务
最常用的场景就是监听 vite.config.js 和 .env.development 文件,修改 vite 配置文件和环境配置文件,是需要重启 vite 才会生效,通过这个插件,在修改上述两个文件则不需要重新运行
npm i vite-plugin-restart -D
配置:vite.config.js
import ViteRestart from "vite-plugin-restart";
export default {
plugins: [
ViteRestart({
restart: ["vite.config.js"],
}),
],
};
Vite 构建打包优化
设置路径别名
这样可以在引入文件时,不再需要使用相对路径,自定义起点路径,方便各个层级的代码引入。比如以下的代码示例,可以方便我们使用 src 目录下的文件。
// vite.config.ts
import { resolve } from 'path'
resolve: {
// 结合tsconfig使用
alias: {
'@': resolve(__dirname, 'src'),
components: resolve(__dirname, 'src/components'),
assets: resolve(__dirname, 'src/assets'),
views: resolve(__dirname, 'src/views')
}
},
// 结合ts使用
// tsconfig.json
{
"compilerOptions": {
...
"paths": {
"@/*": ["./src/*"],
"components/*": ["./src/components/*"],
"assets/*": ["./src/assets/*"],
"views/*": ["./src/views/*"]
}
},
}
less 引入全局公共样式
// vite.config.ts
export default defineConfig({
css: {
preprocessorOptions: {
less: {
additionalData: `@import "@/styles/common.less";`,
}
},
},
})
// src\styles\common.less
.ellipsis {
overflow: hidden; /* 超出部分被隐藏 */
text-overflow: ellipsis; /* 被裁剪的文本使用省略号表示 */
white-space: nowrap; /* 禁止换行,文本在一行内显示 */
}
// 任意less文件
.text {
.ellipsis;
}
配置打包文件分类输出
build: {
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称
entryFileNames: 'js/[name]-[hash].js', // 包的入口文件名称
assetFileNames: '[ext]/[name]-[hash].[ext]', // 资源文件像 字体,图片等
}
}
}
// 不建议配置
代码分割/产物拆包manualChunks
一般来说,如果不对产物进行代码分割(或者拆包),全部打包到一个 chunk 中,会产生如下的问题:
- 首屏加载的代码体积过大,即使是当前页面不需要的代码也会进行加载。
- 线上缓存复用率极低,改动一行代码即可导致整个 bundle 产物缓存失效。
而 Vite 中内置如下的代码拆包能力:
- CSS 代码分割,即实现一个 chunk 对应一个 css 文件。
- 默认有一套拆包策略,将应用的代码和第三方库的代码分别打包成两份产物,并对于动态 import 的模块单独打包成一个 chunk。
当然,我们也可以通过 manualChunks 参数进行自定义配置。
// vite.config.ts
{
build {
rollupOptions: {
output: {
// 1. 对象配置
manualChunks: {
// 将 React 相关库打包成单独的 chunk 中
'react-vendor': ['react', 'react-dom'],
// 将 Lodash 库的代码单独打包
'lodash': ['lodash-es'],
// 将组件库的代码打包
'library': ['antd'],
},
// 2. 函数配置
if (id.includes('antd') || id.includes('@arco-design/web-react')) {
return 'library';
}
if (id.includes('lodash')) {
return 'lodash';
}
if (id.includes('react')) {
return 'react';
}
},
}
},
}
代码压缩+剔除 console+debugger
esbuild or terser, 默认启动的是 esbuild, esbuild 比 terser 快 20-40 倍,压缩率只差 1%-2%
esbuild:
只删除 console.log 和debugger
import { defineConfig } from "vite";
export default defineConfig({
esbuild: {
pure: ["console.log"], // 删除 console.log
drop: ["debugger"], // 删除 debugger
},
});
// 建议配置
删除所有的console语句和debugger,包括 console.log、console.warn、console.error 等
import {defineConfig} from "vite";
export default defineConfig({
esbuild:{
drop: ['console,'debugger'], // 删除 所有的console 和 debugger
}
})
terser:
vite 4.X 版本已经不集成 terser,需要自行下载
npm i terser -D
只删除 console.log 和 debugger
import { defineConfig } from "vite";
export default defineConfig({
build: {
minify: "terser", // 启用 terser 压缩
terserOptions: {
compress: {
pure_funcs: ["console.log"], // 只删除 console.log
drop_debugger: true, // 删除 debugger
},
},
},
});
删除所有的console语句和debugger,包括 console.log、console.warn、console.error 等
import { defineConfig } from "vite";
export default defineConfig({
build: {
minify: "terser", // 启用 terser 压缩
terserOptions: {
compress: {
drop_console: true, // 删除所有 console
drop_debugger: true, // 删除 debugger
},
format: {
comments: false, // 删除所有注释
},
},
},
});
其他打包配置优化
build: {
chunkSizeWarningLimit: 2000,// chunk 超过 2000kb 之后进行提示
cssCodeSplit: true, //css 拆分
sourcemap: false, //不生成sourcemap
assetsInlineLimit: 5000, //小于该值 图片将打包成Base64
},
标签:vue,plugin,default,import,分享,config,vite,defineConfig
From: https://www.cnblogs.com/wp-leonard/p/17864133.html