第 19 章:Vite + 工程化
学习目标
- 配置 Vite 常用选项
- 用环境变量区分 dev / prod
- 配置 alias 简化路径
- 选型 plugin(UnoCSS / Vue DevTools)
一、最小 vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
});💡 逐行翻译:
tsimport { defineConfig } from 'vite'; // ① Vite 配置工具(类型补全) import vue from '@vitejs/plugin-vue'; // ② Vue 插件(让 Vite 认 .vue 文件) import { fileURLToPath, URL } from 'node:url'; // ③ Node 内置的 URL 处理工具 export default defineConfig({ // ④ 配置入口,defineConfig 包一下有 TS 提示 plugins: [vue()], // ⑤ 启用 Vue 插件(没它 .vue 文件不识别,必加) resolve: { // ⑥ 路径解析配置 alias: { // ⑦ 别名:用 '@' 代替 './src' '@': fileURLToPath(new URL('./src', import.meta.url)), // ⑧ 转成绝对路径 }, }, });三个 import 各干嘛:
import 作用 defineConfigVite 配置函数,自带 TS 类型提示,不写也能跑 @vitejs/plugin-vueVue 必备插件,没它 .vue文件直接报错fileURLToPath, URL把 URL 形式转成本地路径(替代 __dirname,因为 ESM 里没有)
fileURLToPath(new URL('./src', import.meta.url))翻译:等价于path.resolve(__dirname, './src'),得到src/的绝对路径。import.meta.url是当前文件 URL,.src是相对路径,new URL(...)拼成完整 URL,fileURLToPath转成本地系统路径。Vite 配置是 ESM 模式,没有__dirname,必须这样绕。别名实际效果:
ts// 原来 import Hello from '../../../components/Hello.vue'; // 现在 import Hello from '@/components/Hello.vue'; // @ = src/
二、环境变量
# .env.development
VITE_API_BASE=http://localhost:3000
# .env.production
VITE_API_BASE=https://api.example.comconst base = import.meta.env.VITE_API_BASE;⚠️ 坑 1:自定义变量必须
VITE_前缀,否则读不到(出于安全)。
三、常用配置
export default defineConfig({
server: {
port: 5173,
open: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api/, ''),
},
},
},
build: {
outDir: 'dist',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
element: ['element-plus'],
},
},
},
},
});💡 逐行翻译:
server(开发服务器)
tsserver: { port: 5173, // ① dev 时端口 http://localhost:5173 open: true, // ② 启动后自动打开浏览器 proxy: { // ③ 代理配置:解决本地跨域 '/api': { // ④ 匹配前端发出的 /api 开头的请求 target: 'http://localhost:3000', // ⑤ 转发到后端 3000 端口 changeOrigin: true, // ⑥ 改请求头 Host(避免后端校验失败) rewrite: (p) => p.replace(/^\/api/, ''), // ⑦ 去掉 /api 前缀 // /api/users → /users }, }, },build(生产构建)
tsbuild: { outDir: 'dist', // ① 产物输出到 dist/ 目录 sourcemap: false, // ② 不生成 .map 文件(关掉省体积,报错定位会难) rollupOptions: { // ③ Rollup 打包选项(Vite 用 Rollup) output: { manualChunks: { // ④ 手动分包:把指定依赖拆成单独 chunk vue: ['vue', 'vue-router', 'pinia'], // ⑤ vue 全家桶 → 一个 chunk element: ['element-plus'], // ⑥ element-plus → 单独 chunk }, }, }, },
proxy工作流程:浏览器 GET /api/users → Vite dev server(5173)收到 → 匹配 '/api' 规则 → 改 Host 头 + 去掉 /api 前缀 → 转发到 http://localhost:3000/users → 后端返回数据为啥要 proxy:前端 5173 调后端 3000,浏览器会报跨域;Vite 在中间帮你转发,绕开浏览器的跨域检查。
manualChunks拆包效果:没拆: dist/assets/index-abc.js (3 MB,全混一起,首加载巨慢) 拆了: dist/assets/index-main.js (几百 KB,业务代码) dist/assets/vue-vendor.js (几十 KB,vue 全家桶,长期缓存) dist/assets/element-vendor.js (几百 KB,ElementPlus,长期缓存) ↑ 浏览器缓存后,只下变了的那块,没变的不用重下三个关键参数:
参数 作用 不写会怎样 changeOrigin: true改请求头 Host 后端校验 Host 失败,403 rewrite去掉/api路径调整 后端路径对不上,404 manualChunks拆包缓存优化 改一行代码整个包重下,慢
四、别名与路径
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@@': fileURLToPath(new URL('./', import.meta.url)),
},
},// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["src/*"]
}
}
}⚠️ 坑 2:
tsconfig.json的 paths 必须和 Vite alias 同步,否则 TS 报错。
💡
paths只负责编译前的 TS 类型检查,不影响运行;运行时找文件还是看 Vite alias。TypeScript 检查代码时,根据
paths解析路径——只管类型检查 / IDE 提示 / 跳转,不影响运行时。三个时点对照:
你保存代码 ↓ ① IDE / `tsc` 检查(用 tsconfig.json 的 paths) ← 这个时点才用到 paths - 报错/不报错 - 提示/不提示 - 跳转/不跳转 ↓ ② Vite 启动 dev / build(用 vite.config.ts 的 alias) ← 这个时点用 alias - 真去磁盘找文件 - 把 .vue / .ts 编译成浏览器能跑的代码 ↓ ③ 浏览器加载页面 ← 跟两个配置都没关系了 - 直接看打包后的代码
paths只在 ① 用——TS 看完一眼,任务结束;真正干活的是 Vite 的alias(②)。一句话总结:
tsconfig paths= 编译前 TS 看一眼用的(类型检查 + 提示);Vite alias= 真去读文件。前者是"体检报告",后者才是"干活的"。
五、常用插件
npm i -D @vitejs/plugin-vue unocss/vite vite-plugin-vue-devtoolsimport vue from '@vitejs/plugin-vue';
import vueDevTools from 'vite-plugin-vue-devtools';
import UnoCSS from 'unocss/vite';
export default defineConfig({
plugins: [vue(), vueDevTools(), UnoCSS()],
});六、CSS 预处理
npm i -D sass<style lang="scss" scoped>
.title {
font-size: 20px;
&:hover {
color: red;
}
}
</style>七、SVG 组件化
npm i -D vite-svg-loader// vite.config.ts
import svgLoader from 'vite-svg-loader';
export default defineConfig({
plugins: [vue(), svgLoader()],
});<script setup lang="ts">
import Logo from '@/assets/logo.svg?component';
</script>
<template>
<Logo />
</template>⚠️ 坑 3:Vite 5+ 已经默认支持
import svg from './a.svg?url'拿 URL,vite-svg-loader主要用来当组件用。
💡
vite-svg-loader= 把 SVG 转成 Vue 组件(?component),好处是组件能改颜色 / 加事件 / 响应式;单纯显示用?url就行,Vite 5+ 自带。三种 SVG 用法对照:
vue<!-- ① 当组件用(本节写法) --> <script setup> import Logo from './logo.svg?component' // ?component = 转成组件 </script> <template><Logo class="icon" /></template> <!-- 优点:可改 color / size / 加 @click,响应式 --> <!-- ② 拿 URL,自己写 <img>(Vite 5+ 自带,不用装插件) --> <script setup> import logoUrl from './logo.svg?url' </script> <template><img :src="logoUrl" /></template> <!-- 缺点:静态展示,不能改颜色,只能 CSS 整图调 --> <!-- ③ 拿原始字符串(很少用) --> import logoRaw from './logo.svg?raw' // 拿到 "<svg>...</svg>" 字符串
?component后缀翻译:Vite 的"导入修饰符",告诉 Vite "别给我 URL,把 SVG 转成 Vue 组件"。啥时候用哪个:
想干嘛 用啥 想改颜色 / 大小 / 加事件 ?component+vite-svg-loader(本节)纯展示,静态 logo / 图片 ?url+<img>(Vite 自带,不装插件)想用 CSS 控制颜色 必须组件模式( <img>改不了 SVG 内部 fill)
八、自动导入
npm i -D unplugin-auto-import unplugin-vue-componentsimport AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
AutoImport({ resolvers: [ElementPlusResolver()] }),
Components({ resolvers: [ElementPlusResolver()] }),
],
});💡 自动导入 = 不用写
import直接用——ref/computed/<el-button>全部不用手动 import。两个插件各管啥:
插件 负责 不装会怎样 AutoImport 自动导入 JS API( ref/computed/watch)每个文件都要 import { ref, computed } from 'vue'Components 自动注册 UI 组件( <el-button>/<el-card>)每个文件都要 import { ElButton } from 'element-plus'实际效果对照:
vue<!-- ❌ 不装自动导入:每个文件都得手动写 import --> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue' import { ElButton, ElInput } from 'element-plus' const name = ref('zs') </script> <!-- ✅ 装了自动导入:啥都不写,直接用 --> <script setup lang="ts"> const name = ref('zs') // ref 自动从 'vue' 导入 </script> <template> <ElButton>{{ name }}</ElButton> <!-- ElButton 自动从 'element-plus' 导入+注册 --> </template>
ElementPlusResolver()是啥:告诉自动导入"我要支持 Element Plus"。不传 resolvers → 默认只支持 Vue / Vue Router / Pinia 的 API,UI 库不生效。自动生成
.d.ts类型:ts// auto-imports.d.ts(自动生成,别手改) declare global { const ref: typeof import('vue')['ref'] const computed: typeof import('vue')['computed'] }
tsconfig.json的include要包含它(一般不用手改)。坑 / 注意点:
坑 解决 TS 报"找不到 ref" 检查 auto-imports.d.ts在 tsconfiginclude里新人接手不知道 ref哪来的这是个缺点——可读性变差,大项目慎用 改了配置文件不生效 重启 dev server 不同 UI 库想混用 加多个 resolver: resolvers: [ElementPlusResolver(), AntDesignVueResolver()]
九、本章小结
| 配置 | 作用 |
|---|---|
alias | 路径别名 |
server.proxy | 跨域代理 |
build.rollupOptions | 分包 |
import.meta.env | 环境变量 |
| UnoCSS / AutoImport | 提效 |
动手练习
- 配置 alias:把
@指向src - 代理 /api:前端 5173 调后端 3000
- 自动导入:让 Element Plus 组件和 API 自动可用
下一章:第 20 章:TypeScript + Vue →