Skip to content

前端构建 ​

说明

前端使用 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     │
└──────────────────────────────────────────────────────┘

开发环境 ​

bash
# 安装依赖
pnpm install

# 启动开发服务器(HMR 热更新)
pnpm dev

# 等效命令
pnpm serve

开发服务器默认运行在 http://localhost:8001(端口由 VITE_PORT 控制),通过 Vite 代理将 /api 请求转发到后端。

多环境开发命令 ​

命令mode加载的 env 文件说明
pnpm devdevelopment.env + .env.development本地开发(默认)

环境变量体系 ​

env 文件加载规则 ​

Vite 按 mode 加载环境变量,后面的文件覆盖前面的:

文件加载时机说明
.env所有模式通用配置(端口、标题、短名)
.env.developmentpnpm dev开发配置(代理、mock)
.env.productionpnpm build生产配置
.env.testpnpm build:test测试环境
.env.uatpnpm build:uatUAT 环境
.env.devpnpm build:dev开发联调环境

环境变量完整参考 ​

变量类型默认值说明
VITE_PORTnumber8001开发服务器端口
VITE_PUBLIC_PATHstring/部署基础路径(子目录部署时修改)
VITE_GLOB_APP_TITLEstringDjango+NaiveUI版应用标题(注入 HTML)
VITE_GLOB_APP_SHORT_NAMEstringDjango+NaiveUI版应用短名(用于生成运行时配置变量名)
VITE_GLOB_API_URLstring空API 基础路径(留空使用相对路径)
VITE_GLOB_API_URL_PREFIXstring/apiAPI 前缀
VITE_GLOB_FILE_URLstring-文件访问域名(拼接上传文件 URL)
VITE_GLOB_UPLOAD_URLstring-图片上传地址
VITE_GLOB_IMG_URLstring-图片前缀地址
VITE_GLOB_PROD_MOCKbooleanfalse生产环境是否开启 mock
VITE_USE_MOCKbooleanfalse开发环境是否开启 mock
VITE_DROP_CONSOLEbooleantrue构建时删除 console
VITE_PROXYJSON[["/api","http://127.0.0.1:8031/api/v1"]]跨域代理配置(JSON 数组)
VITE_BUILD_COMPRESSstring'none'压缩方式:gzip / brotli / none
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILEbooleanfalse压缩后是否删除原文件

代理配置格式 ​

VITE_PROXY 是 JSON 格式的二维数组,每个元素 [前缀, 目标地址]:

bash
# 单个代理
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):

typescript
// 输入: [["/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/, ''),  // 去掉前缀
  }
}

代码中使用环境变量 ​

typescript
// 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-vueVue3 SFC 支持始终
@vitejs/plugin-vue-jsxJSX/TSX 支持始终
vite-plugin-vue-setup-extend<script setup> 支持 name 属性始终
unplugin-auto-importVue/Router/Pinia API 自动导入始终
vite-plugin-htmlHTML 模板注入(标题、运行时配置)始终
vite-plugin-mockMock 数据服务VITE_USE_MOCK=true
rollup-plugin-visualizer打包体积分析REPORT=true
vite-plugin-compressiongzip/brotli 压缩仅构建时

自动导入 ​

unplugin-auto-import 自动导入以下 API,无需手动 import:

typescript
// 以下 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 在构建时:

  1. 将 VITE_GLOB_APP_TITLE 注入到 index.html 的 <title> 标签
  2. 注入 app.config.js 的 <script> 标签(运行时配置)
html
<!-- 构建后的 index.html -->
<title>Django+NaiveUI版</title>
<script src="/app.config.js?v=1.0.0-1725000000000"></script>

运行时配置(app.config.js) ​

构建后自动生成 dist/app.config.js,包含 VITE_GLOB_ 开头的环境变量:

javascript
// 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.naivevue",
  "VITE_GLOB_APP_TITLE": "Django+NaiveUI版",
  // ...
};
Object.freeze(window.__{SHORT_NAME}__PRODUCTION__CONF__);

变量名说明

运行时配置变量名由 VITE_GLOB_APP_SHORT_NAME 环境变量自动生成,格式为 __{SHORT_NAME}__{ENV}__CONF__。VITE_GLOB_APP_SHORT_NAME 只允许字母和下划线。

为什么需要运行时配置?

import.meta.env 的值在构建时被静态替换,构建后无法修改。而 app.config.js 是运行时加载的,部署时只需修改这个文件即可切换 API 地址等配置,无需重新构建。

打包体积分析 ​

bash
# 生成打包体积报告(自动打开浏览器)
pnpm report

生成 node_modules/.cache/visualizer/stats.html,可视化展示各模块占比。


生产构建 ​

构建命令 ​

命令mode说明
pnpm buildproduction生产构建(默认)
pnpm build:prodproduction等效于 pnpm build
pnpm build:testtest测试环境构建
pnpm build:uatuatUAT 环境构建
pnpm build:devdev开发联调环境构建
pnpm build:no-cacheproduction清除缓存后构建

构建流程 ​

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、NaiveUI、Pinia 等)
    ├── index-[hash].css          # 全局样式
    ├── [name]-[hash].js          # 路由懒加载 chunk(按视图分割)
    └── ...                       # 字体、图片等静态资源

代码分割策略 ​

Vite 自动进行代码分割,主要手段:

1. 路由懒加载(最大收益)

typescript
// router 中使用 import.meta.glob 自动分割
const viewsModules = import.meta.glob('../views/**/*.{vue,tsx}');

// 每个视图组件被打包为独立 chunk
// 首屏只加载当前路由的 chunk,其他按需加载

2. 第三方库分割

Vite 自动将 node_modules 中的库分割为独立 chunk:

chunk内容
vendor-vue.jsvue + vue-router + pinia
vendor-naive.jsnaive-ui
vendor-echarts.jsecharts(如果使用)
vendor-*.js其他大型依赖

3. 预构建依赖

optimizeDeps.include 预构建以下依赖(开发时加速冷启动):

typescript
optimizeDeps: {
  include: ['dayjs', '@vicons/ionicons5', '@vicons/antd'],
}

应用初始化 ​

src/main.ts 是前端入口,初始化顺序:

typescript
async function bootstrap() {
  const app = createApp(App);

  // 1. 全局引入 NaiveUI(中文包)
  setupNaive(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/):

注册项文件说明
NaiveUInaive.ts全局引入 + 中文包
自定义组件customComponents.tsBasicTable、BasicForm、PageWrapper 等
自定义指令directives.tsv-perm、v-perms、v-scrollBar

自定义指令:

指令语法说明
v-permv-perm="['sys:user:add']"包含其中任一权限即显示
v-permsv-perms="['sys:user:add','sys:user:edit']"包含所有权限才显示
v-scrollBarv-scrollBar自定义滚动条样式

Nginx 部署 ​

基础配置 ​

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/):

bash
# .env.production
VITE_PUBLIC_PATH=/admin/
nginx
location /admin/ {
    alias /path/to/dist/;
    index index.html;
    try_files $uri $uri/ /admin/index.html;
}

Docker 部署 ​

前端独立部署 ​

dockerfile
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 注入构建信息:

typescript
define: {
  __APP_INFO__: JSON.stringify({
    pkg: { dependencies, devDependencies, name, version },
    lastBuildTime: formatToDateTime(new Date()),
  }),
},

代码中访问:

typescript
const appInfo = __APP_INFO__;
console.log(appInfo.pkg.version);       // package.json 版本
console.log(appInfo.lastBuildTime);     // 最后构建时间

常见问题 ​

开发环境 API 请求 404 ​

原因:代理配置错误或后端未启动。

排查:

  1. 检查 .env.development 中 VITE_PROXY 格式是否正确
  2. 检查后端是否运行在 http://127.0.0.1:8031
  3. 检查浏览器 Network 面板中请求的实际 URL

构建后页面空白 ​

可能原因:

  1. VITE_PUBLIC_PATH 配置与实际部署路径不一致
  2. Nginx 缺少 try_files $uri $uri/ /index.html 导致刷新 404
  3. app.config.js 未正确加载(检查 HTML 中的 script 标签)

解决:检查 dist/index.html 中的资源路径是否正确。

构建体积过大 ​

排查:

bash
# 生成打包体积分析报告
pnpm report

优化手段:

  1. 路由懒加载(已默认启用)
  2. 按需引入 NaiveUI 组件(当前为全量引入)
  3. 大型依赖(echarts、wangeditor)按需引入
  4. 开启 gzip 压缩(VITE_BUILD_COMPRESS=gzip)

环境变量不生效 ​

原因:Vite 只识别 VITE_ 开头的环境变量。

排查:

  1. 变量名必须以 VITE_ 开头
  2. 修改 .env 文件后需要重启开发服务器
  3. 确认加载的 env 文件与当前 mode 匹配

热更新不工作 ​

可能原因:

  1. 文件路径大小写不一致(Windows 不敏感,但 Vite 敏感)
  2. 修改了 vite.config.ts(需要重启)
  3. 修改了 tsconfig.json(需要重启)

总结 ​

前端使用 Vite 构建,通过 .env 文件体系管理多环境配置,wrapperEnv 自动类型转换。插件链包括 Vue3 / JSX / 自动导入 / HTML 注入 / Mock / 压缩 / 体积分析。生产构建自动生成 app.config.js 运行时配置,支持不重新构建切换 API 地址。代码分割通过路由懒加载和第三方库自动分割实现。部署推荐 Nginx,需配置 try_files 回退和 gzip 压缩。

小蚂蚁云团队 · 提供技术支持