- vite.config.js: 新增 unplugin-vue-components + ElementPlusResolver;
optimizeDeps.include 动态生成 118 项无扩展名 specifier(glob/.mjs 皆静默失效)
- main.js: 删 app.use(ElementPlus) 与全量 CSS;改 provideGlobalConfig(locale, app, true);
显式引入 4 个函数式组件样式(message/message-box/notification/loading)
- views/Upload.vue: 3 处 h(resolveComponent('el-xxx')) 改显式组件引用;清 2 处死导入
- devDep: unplugin-vue-components@^32.1.0
- .gitignore: 忽略 vite.config.js.timestamp-*.mjs
验证:dist 渲染 9/9 · dist 登录后 9/9 · dev 通道致命错误 0/告警 0
瘦身:首屏 425.1->158.8KB(-62.6%);主包 JS 1112.9->281.09KB
114 lines
4.8 KiB
JavaScript
114 lines
4.8 KiB
JavaScript
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)),
|
||
},
|
||
},
|
||
})
|