feat(frontend): Element Plus 改按需引入,首屏 425.1->158.8KB(R36)

- 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
This commit is contained in:
2026-09-29 11:44:15 +08:00
parent 042e0a8e1f
commit ea350fb2e0
7 changed files with 471 additions and 16 deletions

View File

@@ -1,11 +1,52 @@
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 中间件之前拦截并安全处理。
{
@@ -30,6 +71,18 @@ export default defineConfig({
},
],
// 依赖预构建:显式声明「按需引入」注入的 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',