2026-08-17 12:25:42 +08:00
|
|
|
|
import { defineConfig } from 'vite'
|
|
|
|
|
|
import vue from '@vitejs/plugin-vue'
|
2026-09-29 11:44:15 +08:00
|
|
|
|
import Components from 'unplugin-vue-components/vite'
|
|
|
|
|
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|
|
|
|
|
import fs from 'node:fs'
|
|
|
|
|
|
import path from 'node:path'
|
2026-08-17 12:25:42 +08:00
|
|
|
|
import { fileURLToPath, URL } from 'node:url'
|
|
|
|
|
|
|
2026-09-29 11:44:15 +08:00
|
|
|
|
// 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 不依赖此项)
|
|
|
|
|
|
}
|
|
|
|
|
|
})()
|
|
|
|
|
|
|
2026-08-17 12:25:42 +08:00
|
|
|
|
// https://vitejs.dev/config/
|
|
|
|
|
|
export default defineConfig({
|
|
|
|
|
|
plugins: [
|
|
|
|
|
|
vue(),
|
2026-09-29 11:44:15 +08:00
|
|
|
|
// 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()],
|
|
|
|
|
|
}),
|
2026-08-17 12:25:42 +08:00
|
|
|
|
// 修复 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()
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
|
2026-09-29 11:44:15 +08:00
|
|
|
|
// 依赖预构建:显式声明「按需引入」注入的 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],
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-08-17 12:25:42 +08:00
|
|
|
|
// 开发服务器配置
|
|
|
|
|
|
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)),
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|