Files
competition-website/vite.config.js

114 lines
4.8 KiB
JavaScript
Raw Normal View History

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath, URL } from 'node:url'
// Element Plus 按需样式模块清单(供下方 optimizeDeps.include 使用)。
// 为什么不能用 glob:Vite 的 include 需要「运行时请求的 specifier 形式」(无扩展名,
// 形如 element-plus/es/components/table/style/css),而 glob 匹配的是文件系统条目 ——
// `style/css` 在磁盘上是目录而非文件,glob 会静默匹配 0 项(实测 '*/style/css' → 0 项);
// 写成 '*/style/css.mjs' 虽能匹配 118 项,但其 key 与运行时 specifier 不一致,
// Vite 仍会把每个组件判定为「新依赖」⇒ 等于没配。
// 因此改为扫描目录动态生成:既不硬编码 118 项,也不会随 element-plus 升级失效。
const EP_COMPONENTS_DIR = fileURLToPath(
new URL('./node_modules/element-plus/es/components', import.meta.url)
)
const EP_STYLE_INCLUDES = (() => {
try {
return fs
.readdirSync(EP_COMPONENTS_DIR)
.filter((name) => fs.existsSync(path.join(EP_COMPONENTS_DIR, name, 'style', 'css.mjs')))
.map((name) => `element-plus/es/components/${name}/style/css`)
} catch {
return [] // 依赖尚未安装时降级为空(vite build 不依赖此项)
}
})()
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// Element Plus 按需引入(替代 main.js 中的 app.use(ElementPlus) 全量注册)。
// 构建期静态扫描模板,只把「实际用到的组件」及其样式打进产物:
// <el-table> → element-plus/es/components/table + table/style/css
// v-loading → ElLoadingDirective(resolver 的 directives 默认开启)
// 注意两处边界(改前已核对):
// ① 运行时字符串解析(h('el-button') / resolveComponent('el-icon'))不归它管
// —— 已改为显式组件引用,见 views/Upload.vue;
// ② 函数式 API(ElMessage/ElMessageBox/...)不会被注入样式,
// —— 已在 main.js 里显式引入这四个样式。
// dirs 置空 = 不额外扫描 src/components 目录,保持原有显式 import 行为不变,
// 只让本插件负责 Element Plus 一个职责(最小改动)。
Components({
dts: 'src/components.d.ts',
dirs: [],
resolvers: [ElementPlusResolver()],
}),
// 修复 Vite 内部 viteTransformMiddleware 调用 decodeURI 时遇到格式错误的 URL
// 导致 "URI malformed" 错误的问题。在 Vite 中间件之前拦截并安全处理。
{
name: 'fix-uri-malformed',
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url) {
try {
// 预检 URL 是否可被 decodeURI 安全解码
decodeURI(req.url)
} catch (_e) {
// URL 包含无效的百分号编码序列,直接返回 400,避免进入 Vite 内部中间件
console.warn(`[fix-uri-malformed] 拦截到格式错误的请求 URL,已返回 400`)
res.statusCode = 400
res.end()
return
}
}
next()
})
},
},
],
// 依赖预构建:显式声明「按需引入」注入的 Element Plus 样式模块。
// 必要性:unplugin-vue-components 是在 transform 阶段「运行时注入」style import 的,
// 而 Vite 启动期的依赖扫描基于 index.html,看不到这些被注入的模块 ⇒ 运行中才发现
// ⇒ 触发「二次预构建」(日志里的 new dependencies optimized + reloading)。
// 后果不只是「访问新页面时整页重载」;在带 safe-delete 钩子的受限沙箱里,
// 二次预构建清理临时目录会被钩子拦下,导致 dev server 进程直接退出。
// 显式声明后首次预构建即完整,dev 启动一次成型。
// ⚠️ 本项只作用于 dev(vite build 不做依赖预构建),不影响任何构建产物。
optimizeDeps: {
include: ['element-plus/es', ...EP_STYLE_INCLUDES],
},
// 开发服务器配置
server: {
host: '0.0.0.0',
port: 5173,
// 禁用 HMR 错误覆盖层,避免非致命错误(如 URI malformed)遮挡页面
hmr: {
overlay: false,
},
// 代理配置:将 /api 和 /public 请求转发到后端服务
proxy: {
'/api': {
target: 'http://127.0.0.1:7001',
changeOrigin: true,
},
'/public': {
target: 'http://127.0.0.1:7001',
changeOrigin: true,
},
},
},
// 路径别名配置
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})