23. 构建、部署与排错
一句话定位
pnpm dev 跑开发、pnpm build 出产物、dist/ 丢进 nginx 即可;本篇给出环境变量全表、public/config.js 运行时配置机制与常见构建报错的修复办法。
一、环境变量全表
代码位置:Web/.env、Web/.env.development、Web/.env.production
| 变量 | 文件 | 默认值 | 说明 |
|---|---|---|---|
VITE_PORT | .env | 8888 | dev server 端口 |
VITE_OPEN | .env | false | dev 时自动打开浏览器 |
VITE_OPEN_CDN | .env | false | 是否走 CDN(src/utils/build.ts 里的 external) |
VITE_PUBLIC_PATH | .env | 空 | 打包 base 路径;部署到子目录时填 /subdir/ |
VITE_SM_PUBLIC_KEY | .env | 空 | 国密 SM2 公钥,留空自动从后台获取 |
DEEPSEEK_API_KEY | .env | "" | 翻译脚本用 |
ENV | .env.development / .env.production | development / production | 环境标识 |
VITE_API_URL | .env.development | http://localhost:5005 | 后端接口地址;生产留空则同源 |
VITE_DEFAULT_USER | .env.development | superAdmin.NET | 登录页默认账号(仅开发) |
VITE_DEFAULT_USER_PASSWORD | .env.development | Admin.NET++010101 | 登录页默认密码(仅开发) |
VITE_BUILD_TIMESTAMP | 构建时注入 | 2026-10-18 18:53:14 | 由 vite.config.ts 生成,webUpdateNotice 用它判断版本 |
命名规则:只有 VITE_ 前缀的变量会进 import.meta.env,但 vite.config.ts 里是用 loadEnv(mode, cwd) 全量读取后写进 config.js 的,所以不带前缀的也能在 window.__env__ 里拿到。
二、public/config.js —— 运行时配置机制
这是本项目最重要的部署特性:接口地址不是编译期常量,而是运行时从 window.__env__ 读。
构建时(vite.config.ts)
const env = loadEnv(mode.mode, process.cwd());
env.VITE_BUILD_TIMESTAMP = new Date().toLocaleString('zh-CN', {...});
fs.writeFileSync('./public/config.js', `window.__env__ = ${JSON.stringify(env, null, 2)} `);
│
▼
运行时(index.html)
<script src="/config.js"></script> ← 在 main.ts 之前加载
<script type="module" src="/src/main.ts"></script>
│
▼
消费方
src/utils/axios-utils.ts : new Configuration({ basePath: import.meta.env.DEV ? '' : window.__env__.VITE_API_URL })
src/utils/request.ts : axios.create({ baseURL: window.__env__.VITE_API_URL })// dist/config.js(可直接改,无需重新打包)
window.__env__ = {
"VITE_OPEN": "false",
"VITE_OPEN_CDN": "false",
"VITE_PORT": "8888",
"VITE_PUBLIC_PATH": "",
"VITE_SM_PUBLIC_KEY": "",
"VITE_API_URL": "https://api.your-domain.com",
"VITE_BUILD_TIMESTAMP": "2026-10-18 18:53:14"
}好处:一套 dist 可部署到任意环境,改 config.js 里的 VITE_API_URL 即可切换后端,不用重新构建。
⚠️ 注意:
config.js是构建产物,每次vite启动/build都会被覆写,不要手工改源文件。- 它在
public/下,会原样拷到dist/根目录,务必与index.html同级。 - 开发环境(
import.meta.env.DEV)不走window.__env__,而是 vite proxy 转发。
三、vite.config.ts 要点
| 区块 | 配置 | 说明 |
|---|---|---|
resolve.alias | /@ → ./src/ | 全站唯一别名 |
resolve.alias | vue-i18n → vue-i18n/dist/vue-i18n.cjs.js | 避免 ESM/CJS 混用报错 |
base | serve 时 './',build 时 env.VITE_PUBLIC_PATH | - |
server.port | env.VITE_PORT | 8888 |
server.host | 0.0.0.0 | 允许局域网访问 |
server.proxy | ^/api、^/[Uu]pload、^/[Ss]se → VITE_API_URL | 开发代理 |
optimizeDeps.include | vue / vue-router / pinia / element-plus / @vueuse/core | 预构建 |
optimizeDeps.exclude | vue-demi / vue-json-pretty | - |
build.outDir | dist | - |
build.chunkSizeWarningLimit | 2000 | 2MB 才告警 |
build.assetsInlineLimit | 4096 | <4KB 内联 base64 |
build.minify | terser,drop_console + drop_debugger | 生产移除 console |
build.modulePreload.polyfill | false | - |
rollupOptions.output.manualChunks | vue 系 → vue-vendor;其它 node_modules 按包名分包 | monaco / vue-element-plus-x / file-viewer 不分包(异步加载) |
css.preprocessorOptions.scss | silenceDeprecations: ['legacy-js-api','global-builtin','fs-importer-cwd','import'] | 屏蔽 sass 弃用告警 |
define | __VUE_I18N_FULL_INSTALL__: false 等 + __NEXT_VERSION__ / __NEXT_NAME__ | - |
插件:
| 插件 | 作用 |
|---|---|
visualizer({ open: false }) | 生成 stats.html 包体积分析 |
CodeInspectorPlugin | 按住热键点击页面定位源码 |
vue() | isCustomElement: tag => tag.startsWith('cropper-') |
vueJsx() | 支持 tsx |
webUpdateNotice | 构建时间戳变化 → 提示「系统更新啦,请刷新页面」 |
vueSetupExtend() | 支持 <script setup name="xxx"> |
compression({ algorithms: ['gzip','brotliCompress'], threshold: 5120, deleteOriginalAssets: false }) | 同时产出 .gz 与 .br |
fileViewerRenderers() | 注入 @file-viewer 渲染器(在线预览) |
vitePluginsAutoI18n({ enabled: false, ... }) | 自动翻译(默认关闭) |
四、pnpm 脚本
| 命令 | 实际执行 |
|---|---|
pnpm dev | vite --open |
pnpm build | node --max-old-space-size=8192 ./node_modules/vite/bin/vite build |
pnpm lintFix | eslint --fix src/ |
pnpm format | prettier --write . |
pnpm buildApi | node script/build-api.js → api_build/build.bat / build.sh |
pnpm buildApi document | → api_build/buildDocument.bat / .sh |
pnpm deployWeb / deployServer / deploy | node script/publish.js web|server|all → 仓库根 Build/vue-release.ps1 / core-release.ps1 |
pnpm translate | node script/translate.cjs |
pnpm genEleIconTypes | node script/gen-ele-icon-types.js |
环境要求:Node >= 22(engines),pnpm 12.14.0(packageManager)。
五、构建产物与 nginx 部署
dist/
├── index.html
├── config.js ← 部署后按需改 VITE_API_URL
├── assets/
│ ├── js/[name]-[hash].js
│ ├── css/[name]-[hash].css
│ └── png|svg|font/[name]-[hash].[ext]
└── (同名 .gz / .br 由 vite-plugin-compression2 生成)nginx 配置参考:
server {
listen 80;
server_name your-domain.com;
root /usr/share/nginx/html;
index index.html;
# 静态预压缩:直接用 vite 产出的 .gz / .br(需 ngx_brotli 模块)
gzip on;
gzip_static on; # 优先返回同路径 .gz
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
gzip_vary on;
brotli_static on; # 优先返回同路径 .br
# 带 hash 的静态资源长缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# config.js / index.html 禁止缓存,否则改了接口地址不生效
location ~* (index\.html|config\.js)$ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# 接口与静态资源反代到后端
location /api/ { proxy_pass http://127.0.0.1:5005; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
location /upload/ { proxy_pass http://127.0.0.1:5005; }
location /Upload/ { proxy_pass http://127.0.0.1:5005; }
location /sse/ { proxy_pass http://127.0.0.1:5005; proxy_buffering off; proxy_read_timeout 3600s; }
# 本项目用 createWebHashHistory(hash 路由),无需 try_files 兜底;
# 若改成 history 模式,则改为:try_files $uri $uri/ /index.html;
location / { try_files $uri $uri/ /index.html; }
}SSE 反代务必
proxy_buffering off,否则服务端推送会被缓冲。
六、常见构建报错与修复
1. 顶层软链丢失(pnpm workspace 场景)
现象:Cannot find module 'xxx' / Failed to resolve import "xxx",但 node_modules 里明明有。
原因:Web/pnpm-workspace.yaml 把 '.' 声明为包,allowBuilds 里列了 vue-demi、@parcel/watcher 等需要构建脚本的包。若安装时跳过了构建(常见于 CI 或 pnpm install --ignore-scripts),这些包的 postinstall 不执行,软链/产物缺失。
修复:
# ① 重新安装并允许构建脚本
cd Web && pnpm install --force
# ② 单独重建受影响的包
pnpm rebuild vue-demi es5-ext core-js
# ③ CI 上确保未加 --ignore-scripts,且 allowBuilds 已包含这些包2. 内存溢出(JavaScript heap out of memory)
现象:FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory。
修复(package.json 已内置):
node --max-old-space-size=8192 ./node_modules/vite/bin/vite build临时加大:
# Windows PowerShell
$env:NODE_OPTIONS="--max-old-space-size=12288"; pnpm build
# Linux / Mac
NODE_OPTIONS="--max-old-space-size=12288" pnpm build若仍溢出,可关掉 visualizer(open: false 已关但插件仍会产出 stats.html),或把 build.sourcemap 保持 false。
3. SCSS 报错 / 弃用告警刷屏
现象:Deprecation Warning [legacy-js-api]、Sass @import rules are deprecated、global-builtin 之类。
修复(vite.config.ts 已配置):
css: {
preprocessorOptions: {
css: { charset: false },
scss: { silenceDeprecations: ['legacy-js-api', 'global-builtin', 'fs-importer-cwd', 'import'] },
},
},若是真报错(不是告警),常见两类:
- 引入顺序错:
theme/index.scss里element-plus/dist/index.css必须在最前。 - 用了不存在的变量:检查是否漏了
var(--next-*)的拼写,或变量只在[data-theme='dark']里定义。
4. window.__env__ undefined
现象:生产白屏,控制台 Cannot read properties of undefined (reading 'VITE_API_URL')。
原因:config.js 没加载(路径不对 / 被缓存 / 漏拷)。
修复:确认 dist/config.js 存在且 index.html 里 <script src="/config.js"></script> 在 main.ts 之前;nginx 对 config.js 设 no-store。
5. component 解析失败 / 菜单点开白屏
原因:dynamicImport() 前缀匹配 import.meta.glob('../views/**/*.{vue,tsx}'),命中 0 个或 >1 个。
修复:菜单 component 填 system/config/index(不带 views/、不带 .vue),并保证该路径前缀在 views/ 下唯一。
6. 开发环境接口 404 / 502
原因:.env.development 的 VITE_API_URL 与后端实际端口不一致,或后端未启动。
修复:改 .env.development 后重启 dev server(.env 变更不热更新);确认 vite proxy 三条规则覆盖到你的接口前缀。
7. 构建后页面空白但本地正常
按顺序排查:VITE_PUBLIC_PATH 是否与实际部署路径一致 → config.js 的 VITE_API_URL 是否正确 → 浏览器 Console 是否有 401 循环 → 是否有 assetsInlineLimit 导致的内联异常。
七、部署步骤清单
pnpm i(Node >= 22)→pnpm lintFix。- 确认
.env.production的VITE_API_URL(同源可留空)、.env的VITE_PUBLIC_PATH。 pnpm build→ 检查dist/与stats.html(包体积)。- 把
dist/内容拷到 nginx root(保留config.js在根)。 - 按需改
dist/config.js的VITE_API_URL。 - nginx 配
/api、/upload、/Upload、/sse反代;开gzip_static/brotli_static。 index.html与config.js设no-store,带 hash 资源设长缓存。- 刷新页面确认无缓存问题;更新版本后
webUpdateNotice会提示用户刷新。
⚠️ 注意事项
config.js是构建产物,不要提交手工修改;改环境请改.env*后重新构建。- 生产构建会
drop_console,别指望线上console.log排错;关键日志改用接口上报。 VITE_PUBLIC_PATH与 nginx 子目录必须一致,否则资源 404。- hash 路由下 nginx 不需要
try_files兜底;若改成 history 模式必须加。 - SSE 必须关
proxy_buffering。 - 更新后用户不刷新会用到旧 chunk ——
webUpdateNotice已处理(基于VITE_BUILD_TIMESTAMP)。 dist/里的.gz/.br是额外文件(deleteOriginalAssets: false),nginx 没开*_static时可以直接删掉省空间。
小结
- 环境变量分三层:
.env(通用)、.env.{mode}(环境)、VITE_BUILD_TIMESTAMP(构建注入)。 public/config.js让接口地址变成运行时配置,一套包多环境复用。pnpm build已带 8G 堆;分包、压缩(gzip + brotli)、drop_console都已内置。- nginx 只需三件事:静态托管 +
/api等反代 +gzip_static/brotli_static;config.js禁缓存。