15 KiB
前端构建
:::tip 说明
前端使用 Vite 构建,支持开发服务器和生产构建。vite.config.ts 通过环境变量和插件系统管理构建配置,生产环境推荐使用 Nginx 部署。
:::
构建架构
vite.config.ts
│
├─ loadEnv(mode) ← 按 mode 加载 .env / .env.{mode}
├─ wrapperEnv() ← 类型转换(string→number/boolean/JSON)
├─ createVitePlugins() ← 组装插件链
├─ createProxy() ← 生成代理配置
│
▼
┌──────────────────────────────────────────────────────┐
│ Vite 构建 │
│ ├─ 开发:vite dev(HMR + proxy) │
│ └─ 生产:vite build → postBuild → app.config.js │
└──────────────────────────────────────────────────────┘
开发环境
# 安装依赖
pnpm install
# 启动开发服务器(HMR 热更新)
pnpm dev
# 等效命令
pnpm serve
开发服务器默认运行在 http://localhost:8001(端口由 VITE_PORT 控制),通过 Vite 代理将 /api 请求转发到后端。
多环境开发命令
| 命令 | mode | 加载的 env 文件 | 说明 |
|---|---|---|---|
pnpm dev |
development |
.env + .env.development |
本地开发(默认) |
环境变量体系
env 文件加载规则
Vite 按 mode 加载环境变量,后面的文件覆盖前面的:
| 文件 | 加载时机 | 说明 |
|---|---|---|
.env |
所有模式 | 通用配置(端口、标题、短名) |
.env.development |
pnpm dev |
开发配置(代理、mock) |
.env.production |
pnpm build |
生产配置 |
.env.test |
pnpm build:test |
测试环境 |
.env.uat |
pnpm build:uat |
UAT 环境 |
.env.dev |
pnpm build:dev |
开发联调环境 |
环境变量完整参考
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
VITE_PORT |
number |
8001 |
开发服务器端口 |
VITE_PUBLIC_PATH |
string |
/ |
部署基础路径(子目录部署时修改) |
VITE_GLOB_APP_TITLE |
string |
Django+AntDesign版 |
应用标题(注入 HTML) |
VITE_GLOB_APP_SHORT_NAME |
string |
Django+AntDesign版 |
应用短名(用于生成运行时配置变量名) |
VITE_GLOB_API_URL |
string |
空 | API 基础路径(留空使用相对路径) |
VITE_GLOB_API_URL_PREFIX |
string |
/api |
API 前缀 |
VITE_GLOB_FILE_URL |
string |
- | 文件访问域名(拼接上传文件 URL) |
VITE_GLOB_UPLOAD_URL |
string |
- | 图片上传地址 |
VITE_GLOB_IMG_URL |
string |
- | 图片前缀地址 |
VITE_GLOB_PROD_MOCK |
boolean |
false |
生产环境是否开启 mock |
VITE_USE_MOCK |
boolean |
false |
开发环境是否开启 mock |
VITE_DROP_CONSOLE |
boolean |
true |
构建时删除 console |
VITE_PROXY |
JSON |
[["/api","http://127.0.0.1:8031/api/v1"]] |
跨域代理配置(JSON 数组) |
VITE_BUILD_COMPRESS |
string |
'none' |
压缩方式:gzip / brotli / none |
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE |
boolean |
false |
压缩后是否删除原文件 |
代理配置格式
VITE_PROXY 是 JSON 格式的二维数组,每个元素 [前缀, 目标地址]:
# 单个代理
VITE_PROXY=[["/api","http://127.0.0.1:8031/api/v1"]]
# 多个代理(注意不要换行)
VITE_PROXY=[["/api","http://127.0.0.1:8031/api/v1"],["/ws","ws://127.0.0.1:8031/ws"]]
代理生成逻辑(build/vite/proxy.ts):
// 输入: [["/api", "http://127.0.0.1:8031/api/v1"]]
// 输出:
{
'/api': {
target: 'http://127.0.0.1:8031/api/v1',
changeOrigin: true, // 修改 Origin 头
ws: true, // 支持 WebSocket
rewrite: (path) => path.replace(/^\/api/, ''), // 去掉前缀
}
}
代码中使用环境变量
// import.meta.env 包含所有 VITE_ 开头的环境变量
const apiBaseUrl = import.meta.env.VITE_GLOB_API_URL;
const fileUrl = import.meta.env.VITE_GLOB_FILE_URL;
// 类型声明在 types/global.d.ts
interface ImportMetaEnv {
readonly VITE_GLOB_API_URL: string;
readonly VITE_GLOB_FILE_URL: string;
// ...
}
wrapperEnv 类型转换
build/utils.ts 中的 wrapperEnv 函数自动转换环境变量类型:
| 原始值 | 转换结果 |
|---|---|
"true" |
true(布尔) |
"false" |
false(布尔) |
"8001"(VITE_PORT) |
8001(数字) |
'[["/api","http://..."]]'(VITE_PROXY) |
[["/api","http://..."]](JSON 解析) |
| 其他字符串 | 保持原样 |
插件系统
build/vite/plugin/index.ts 组装所有 Vite 插件:
插件列表
| 插件 | 说明 | 生效时机 |
|---|---|---|
@vitejs/plugin-vue |
Vue3 SFC 支持 | 始终 |
@vitejs/plugin-vue-jsx |
JSX/TSX 支持 | 始终 |
vite-plugin-vue-setup-extend |
<script setup> 支持 name 属性 |
始终 |
unplugin-auto-import |
Vue/Router/Pinia API 自动导入 | 始终 |
vite-plugin-html |
HTML 模板注入(标题、运行时配置) | 始终 |
vite-plugin-mock |
Mock 数据服务 | VITE_USE_MOCK=true |
rollup-plugin-visualizer |
打包体积分析 | REPORT=true |
vite-plugin-compression |
gzip/brotli 压缩 | 仅构建时 |
自动导入
unplugin-auto-import 自动导入以下 API,无需手动 import:
// 以下 API 可直接使用,无需 import
import { ref, computed, watch, onMounted } from 'vue'; // 自动
import { useRouter, useRoute } from 'vue-router'; // 自动
import { defineStore } from 'pinia'; // 自动
自动生成类型声明文件 auto-imports.d.ts。
HTML 模板注入
vite-plugin-html 在构建时:
- 将
VITE_GLOB_APP_TITLE注入到index.html的<title>标签 - 注入
app.config.js的<script>标签(运行时配置)
<!-- 构建后的 index.html -->
<title>Django+AntDesign版</title>
<script src="/app.config.js?v=1.0.0-1725000000000"></script>
运行时配置(app.config.js)
构建后自动生成 dist/app.config.js,包含 VITE_GLOB_ 开头的环境变量:
// dist/app.config.js(构建后自动生成,不可手动修改)
// 变量名由 VITE_GLOB_APP_SHORT_NAME 自动生成
window.__{SHORT_NAME}__PRODUCTION__CONF__ = {
"VITE_GLOB_API_URL": "",
"VITE_GLOB_API_URL_PREFIX": "/api",
"VITE_GLOB_FILE_URL": "http://file.django.antdvue",
"VITE_GLOB_APP_TITLE": "Django+AntDesign版",
// ...
};
Object.freeze(window.__{SHORT_NAME}__PRODUCTION__CONF__);
:::tip 变量名说明
运行时配置变量名由 VITE_GLOB_APP_SHORT_NAME 环境变量自动生成,格式为 __{SHORT_NAME}__{ENV}__CONF__。VITE_GLOB_APP_SHORT_NAME 只允许字母和下划线。
:::
:::warning 为什么需要运行时配置?
import.meta.env 的值在构建时被静态替换,构建后无法修改。而 app.config.js 是运行时加载的,部署时只需修改这个文件即可切换 API 地址等配置,无需重新构建。
:::
打包体积分析
# 生成打包体积报告(自动打开浏览器)
pnpm report
生成 node_modules/.cache/visualizer/stats.html,可视化展示各模块占比。
生产构建
构建命令
| 命令 | mode | 说明 |
|---|---|---|
pnpm build |
production |
生产构建(默认) |
pnpm build:prod |
production |
等效于 pnpm build |
pnpm build:test |
test |
测试环境构建 |
pnpm build:uat |
uat |
UAT 环境构建 |
pnpm build:dev |
dev |
开发联调环境构建 |
pnpm build:no-cache |
production |
清除缓存后构建 |
构建流程
pnpm build
│
├─ 1. cross-env NODE_ENV=production
│ 设置 Node 环境变量
│
├─ 2. vite build --mode production
│ 加载 .env + .env.production
│ 执行 Vite 构建(代码编译、Tree-shaking、代码分割)
│ 输出到 dist/
│
└─ 3. esno ./build/script/postBuild.ts
运行 postBuild 脚本
生成 dist/app.config.js(运行时配置)
构建产物
dist/
├── index.html # 入口 HTML(注入标题和 app.config.js)
├── app.config.js # 运行时配置(VITE_GLOB_* 变量)
├── favicon.ico # 网站图标
└── assets/
├── index-[hash].js # 主包(Vue、Ant Design Vue、Pinia 等)
├── index-[hash].css # 全局样式
├── [name]-[hash].js # 路由懒加载 chunk(按视图分割)
└── ... # 字体、图片等静态资源
代码分割策略
Vite 自动进行代码分割,主要手段:
1. 路由懒加载(最大收益)
// router 中使用 import.meta.glob 自动分割
const viewsModules = import.meta.glob('../views/**/*.{vue,tsx}');
// 每个视图组件被打包为独立 chunk
// 首屏只加载当前路由的 chunk,其他按需加载
2. 第三方库分割
Vite 自动将 node_modules 中的库分割为独立 chunk:
| chunk | 内容 |
|---|---|
vendor-vue.js |
vue + vue-router + pinia |
vendor-antd.js |
ant-design-vue |
vendor-echarts.js |
echarts(如果使用) |
vendor-*.js |
其他大型依赖 |
3. 预构建依赖
optimizeDeps.include 预构建以下依赖(开发时加速冷启动):
optimizeDeps: {
include: ['dayjs', '@ant-design/icons-vue'],
}
应用初始化
src/main.ts 是前端入口,初始化顺序:
async function bootstrap() {
const app = createApp(App);
// 1. 全局注册 Ant Design Vue 组件
setupAntd(app);
// 3. 全局注册自定义组件(BasicTable、BasicForm 等)
setupCustomComponents(app);
// 4. 注册全局指令(v-perm、v-perms、v-scrollBar)
setupDirectives(app);
// 5. 挂载 Pinia 状态管理
setupStore(app);
// 6. 挂载路由
await setupRouter(app);
// 7. 路由就绪后挂载应用
await router.isReady();
app.mount('#app', true);
}
全局注册清单
插件注册(src/plugins/):
| 注册项 | 文件 | 说明 |
|---|---|---|
| Ant Design Vue | antdv.ts |
全局引入组件 |
| 自定义组件 | customComponents.ts |
BasicTable、BasicForm、PageWrapper 等 |
| 自定义指令 | directives.ts |
v-perm、v-perms、v-scrollBar |
自定义指令:
| 指令 | 语法 | 说明 |
|---|---|---|
v-perm |
v-perm="['sys:user:add']" |
包含其中任一权限即显示 |
v-perms |
v-perms="['sys:user:add','sys:user:edit']" |
包含所有权限才显示 |
v-scrollBar |
v-scrollBar |
自定义滚动条样式 |
Nginx 部署
基础配置
server {
listen 80;
server_name your-domain.com;
charset utf-8;
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_min_length 1000;
gzip_comp_level 6;
# 前端静态文件
location / {
root /path/to/dist;
index index.html;
try_files $uri $uri/ /index.html; # SPA 路由回退
# 静态资源缓存(带 hash 的文件长期缓存)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
# API 代理
location /api/ {
proxy_pass http://127.0.0.1:8031/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时配置
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
}
# 文件访问代理(上传文件)
location /api/file/ {
alias /path/to/uploads/;
expires 7d;
add_header Cache-Control "public";
}
}
子目录部署
如果部署在子目录(如 https://example.com/admin/):
# .env.production
VITE_PUBLIC_PATH=/admin/
location /admin/ {
alias /path/to/dist/;
index index.html;
try_files $uri $uri/ /admin/index.html;
}
Docker 部署
前端独立部署
FROM nginx:alpine
# 复制构建产物
COPY dist/ /usr/share/nginx/html/
# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
前后端一体部署
项目根目录的 docker-compose.yml 和 Dockerfile 支持前后端一体部署,详见 Docker 部署 章节。
构建信息注入
vite.config.ts 通过 define 注入构建信息:
define: {
__APP_INFO__: JSON.stringify({
pkg: { dependencies, devDependencies, name, version },
lastBuildTime: formatToDateTime(new Date()),
}),
},
代码中访问:
const appInfo = __APP_INFO__;
console.log(appInfo.pkg.version); // package.json 版本
console.log(appInfo.lastBuildTime); // 最后构建时间
常见问题
开发环境 API 请求 404
原因:代理配置错误或后端未启动。
排查:
- 检查
.env.development中VITE_PROXY格式是否正确 - 检查后端是否运行在
http://127.0.0.1:8031 - 检查浏览器 Network 面板中请求的实际 URL
构建后页面空白
可能原因:
VITE_PUBLIC_PATH配置与实际部署路径不一致- Nginx 缺少
try_files $uri $uri/ /index.html导致刷新 404 app.config.js未正确加载(检查 HTML 中的 script 标签)
解决:检查 dist/index.html 中的资源路径是否正确。
构建体积过大
排查:
# 生成打包体积分析报告
pnpm report
优化手段:
- 路由懒加载(已默认启用)
- 按需引入 Ant Design Vue 组件(当前为全量引入)
- 大型依赖(echarts、wangeditor)按需引入
- 开启 gzip 压缩(
VITE_BUILD_COMPRESS=gzip)
环境变量不生效
原因:Vite 只识别 VITE_ 开头的环境变量。
排查:
- 变量名必须以
VITE_开头 - 修改
.env文件后需要重启开发服务器 - 确认加载的 env 文件与当前 mode 匹配
热更新不工作
可能原因:
- 文件路径大小写不一致(Windows 不敏感,但 Vite 敏感)
- 修改了
vite.config.ts(需要重启) - 修改了
tsconfig.json(需要重启)
总结
前端使用 Vite 构建,通过 .env 文件体系管理多环境配置,wrapperEnv 自动类型转换。插件链包括 Vue3 / JSX / 自动导入 / HTML 注入 / Mock / 压缩 / 体积分析。生产构建自动生成 app.config.js 运行时配置,支持不重新构建切换 API 地址。代码分割通过路由懒加载和第三方库自动分割实现。部署推荐 Nginx,需配置 try_files 回退和 gzip 压缩。