refactor: Element Plus 样式预构建改回硬编码组件清单,分页默认条数调整为 [10,20,50,100]

This commit is contained in:
Ray.Hao
2026-08-13 12:39:19 +08:00
parent 5671190b8e
commit 384696affc
2 changed files with 109 additions and 108 deletions

View File

@@ -4,14 +4,12 @@ import { type ConfigEnv, type UserConfig, loadEnv, defineConfig } from "vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import type { ComponentResolverObject } from "unplugin-vue-components";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import { mockDevServerPlugin } from "vite-plugin-mock-dev-server";
import UnoCSS from "unocss/vite";
import { resolve } from "path";
import fs from "node:fs";
import { name, version } from "./package.json" with { type: "json" };
// 平台名称、版本信息
@@ -24,10 +22,8 @@ const __APP_INFO__ = {
const pathSrc = resolve(import.meta.dirname, "src");
// Vite配置 https://cn.vitejs.dev/config
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => {
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
const env = loadEnv(mode, process.cwd());
// 生成 Element Plus 组件样式预构建清单resolver 驱动)
const elementPlusStyleImports = await collectElementPlusStyleImports();
return {
resolve: {
@@ -117,9 +113,84 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
"element-plus/es",
"element-plus/es/locale/lang/en",
"element-plus/es/locale/lang/zh-cn",
// Element Plus 组件样式预构建resolver 驱动):扫描 src 实际用到的组件/指令
// 解析出 base + 组件样式路径,首启即预加载,避免首次使用某组件时重优化导致页面刷新
...elementPlusStyleImports,
// Element Plus 组件样式预构建:按 src 实际用到的组件清单硬编码,首启即预加载
// 避免首次使用某组件时触发依赖重优化导致页面刷新
...[
"alert",
"avatar",
"backtop",
"badge",
"base",
"breadcrumb",
"breadcrumb-item",
"button",
"card",
"cascader",
"checkbox",
"checkbox-group",
"checkbox-button",
"col",
"color-picker",
"config-provider",
"collapse-transition",
"date-picker",
"descriptions",
"descriptions-item",
"dialog",
"divider",
"drawer",
"dropdown",
"dropdown-item",
"dropdown-menu",
"empty",
"form",
"form-item",
"icon",
"image",
"image-viewer",
"input",
"input-number",
"input-tag",
"link",
"loading",
"menu",
"menu-item",
"message",
"message-box",
"notification",
"option",
"pagination",
"popover",
"progress",
"radio",
"radio-button",
"radio-group",
"row",
"scrollbar",
"select",
"skeleton",
"skeleton-item",
"space",
"step",
"steps",
"sub-menu",
"switch",
"tab-pane",
"table",
"table-column",
"tabs",
"tag",
"text",
"time-picker",
"time-select",
"timeline",
"timeline-item",
"tooltip",
"tree",
"tree-select",
"upload",
"watermark",
].map((c) => `element-plus/es/components/${c}/style/index`),
],
},
// 构建配置Vite 8 使用 Rolldown + Oxc
@@ -164,65 +235,3 @@ export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> =>
},
};
});
// ── 工具函数 ──────────────────────────────────────────────────────────────
// Element Plus 按需样式解析器(与上方 AutoImport/Components 同配置,保证解析结果一致)
const elementPlusComponentResolver = ElementPlusResolver({
importStyle: "sass",
})[0] as ComponentResolverObject;
// 扫描 src 实际用到的 Element Plus 组件/指令,经 resolver 解析出 base + 组件样式路径,
// 供 optimizeDeps 首启预构建,避免首次使用某组件时重优化导致页面刷新。
// 指令统一转成组件名解析v-loading → ElLoading与组件共用同一 resolver。
async function collectElementPlusStyleImports(): Promise<string[]> {
const files: string[] = [];
const walk = (dir: string) => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory()) {
walk(resolve(dir, entry.name));
} else if (/\.(vue|ts|tsx|js|jsx)$/.test(entry.name)) {
files.push(resolve(dir, entry.name));
}
}
};
walk(pathSrc);
// kebab-case 名转 El 前缀 PascalCaseel-button-group → ElButtonGroup
const toPascalName = (kebab: string) =>
`El${kebab
.split("-")
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join("")}`;
const names = new Set<string>();
for (const file of files) {
const source = fs.readFileSync(file, "utf-8");
for (const match of source.matchAll(/<el-([a-z0-9][a-z0-9-]*)/g)) {
names.add(toPascalName(match[1])); // <el-button> / <el-form-item>
}
for (const match of source.matchAll(/<([A-Z][A-Za-z0-9]*)/g)) {
if (match[1].startsWith("El")) names.add(match[1]); // <ElTable> / <ElButton>
}
for (const match of source.matchAll(/\bEl[A-Z][A-Za-z0-9]*\b/g)) {
names.add(match[0]); // ElMessage / ElMessageBox 等脚本标识符
}
for (const match of source.matchAll(/\bv-(loading|popover|infinite-scroll)\b/g)) {
names.add(toPascalName(match[1])); // v-loading → ElLoading
}
}
// 每个名字经 resolver 解析出的副作用即所需样式路径sideEffects 支持 string / ImportInfo / 数组)
const styleImports = new Set<string>();
for (const name of names) {
const resolved = await elementPlusComponentResolver.resolve(name);
if (!resolved || typeof resolved === "string") continue;
const { sideEffects } = resolved;
if (!sideEffects) continue;
for (const effect of Array.isArray(sideEffects) ? sideEffects : [sideEffects]) {
styleImports.add(typeof effect === "string" ? effect : effect.from);
}
}
return [...styleImports];
}