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) 全量注册)。 // 构建期静态扫描模板,只把「实际用到的组件」及其样式打进产物: // → 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)), }, }, })