perf(frontend): RichEditor 异步化拆分 Admin chunk(1021 kB → 232 kB)(BACKLOG C6)

定性:Admin-*.js 达单 chunk 1,021 kB(gzip 340 kB),而其中约 800 kB 是**进入管理端
根本用不到**的富文本编辑器。
- Admin.vue 静态 import RichEditor.vue,后者静态 import @wangeditor/editor
  (dist/index.esm.js = 818,777 B)⇒ 编辑器全量并进 Admin chunk。
- 实测三处使用点(446 大题题干 / 1384 新闻 / 1476 轮播)**全部位于 el-dialog 内**,
  而 el-dialog 初始 rendered=false 不渲染内容 ⇒ 首屏不该付这份成本。
- 全仓反证:RichEditor 只被 Admin.vue 引用;@wangeditor 特征串在入口 chunk 0 命中。

处置:改为 defineAsyncComponent 按需加载(最小改动 16+/2−,只动 Admin.vue)
- src/views/Admin.vue: import RichEditor → defineAsyncComponent({ loader: () => import(...) })
- 加 loadingComponent:用 **render 函数**而非 template 字符串 —— 本工程为 runtime-only
  构建,字符串模板需要运行时编译器,会直接报错。
- vue import 列表补 defineAsyncComponent 与 h。
- 无需 manualChunks:唯一引用点 + 动态 import ⇒ Vite 自动分片。

实测(真 chromium + 静态服务 + /api 反代真实后端):
  Admin-*.js        1,045,614 B → 231,715 B(−77.8%)
  RichEditor-*.js   新增 810,862 B(按需加载;gzip 281,958 B)
  首屏 JS 合计      1,684,778 B → 872,736 B(−48.1%)
  首屏最大单 chunk  1,045,614 B → 283,833 B(−72.9%)

验证:
- 新增 perf-r34/c6-chunk-split-check.mjs:21/21 全绿(首屏不含 wangeditor;打开新闻与
  轮播两个弹窗后编辑器 chunk 按需请求且渲染成功;0 pageerror / 0 Failed to resolve)
- 🔴 判别力(变异测试):对**拆分前**的 dist 跑同一脚本,恰好 5 项判红(A4 首屏含
  wangeditor / A5a 1,684,778 B 超预算 / A5b 1,045,614 B 超 500 kB / B2·B3 无按需请求)
  —— 证明断言真的在守,而非「守卫根本没跑」。
- 回归:perf-r34/dist-admin-check.mjs 9/9(el-table 20 行 / 分页中文 / ElMessageBox
  中文按钮 / 零 Failed to resolve)⇒ 管理端功能零回归。
- TDZ 扫描 37 文件 PASS · vite build exit 0 · .gz 预压缩 70 个(含 RichEditor.js.gz)
This commit is contained in:
2026-09-29 12:49:58 +08:00
parent ecf8d39f03
commit 3f8316da47

View File

@@ -1533,7 +1533,7 @@
* 7. 新闻管理 - CRUD + 置顶/分组 * 7. 新闻管理 - CRUD + 置顶/分组
* 8. 轮播管理 - 添加/删除/排序/启用 * 8. 轮播管理 - 添加/删除/排序/启用
*/ */
import { ref, reactive, onMounted, watch, computed, nextTick } from 'vue' import { ref, reactive, onMounted, watch, computed, nextTick, defineAsyncComponent, h } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { import {
DataAnalysis, User, EditPen, FolderOpened, DataAnalysis, User, EditPen, FolderOpened,
@@ -1546,7 +1546,21 @@ import * as carouselApi from '@/api/carousel'
import { formatFileSize, normalizeImageUrl } from '@/utils/upload' import { formatFileSize, normalizeImageUrl } from '@/utils/upload'
import { safeDownload, fetchBlobGuarded } from '@/utils/download' import { safeDownload, fetchBlobGuarded } from '@/utils/download'
import { sanitizeHtml, isRichTextEmpty } from '@/utils/richtext' import { sanitizeHtml, isRichTextEmpty } from '@/utils/richtext'
import RichEditor from '@/components/RichEditor.vue' // 🔴 富文本编辑器**异步加载**(2026-09-29 R41 · BACKLOG C6):
// RichEditor.vue 静态 import 了 @wangeditor/editor(dist 主文件 818,777 B ≈ 800 kB),
// 此前经静态 import 全量并进 Admin chunk ⇒ Admin-*.js 达 1021 kB(gzip 340 kB)。
// 实测三处使用点(446 大题题干 / 1384 新闻 / 1476 轮播)**全部在 el-dialog 内**,
// 而 el-dialog 初始 rendered=false 不渲染内容 ⇒ 进入管理端时本编辑器根本用不到。
// 改为异步后 wangeditor 被剥离为独立 chunk,仅在首次打开这三个弹窗时才按需下载。
// 注意:loadingComponent 用 render 函数而非 template 字符串 —— 本工程为 runtime-only
// 构建,字符串模板需要运行时编译器,会直接报错。
const RichEditor = defineAsyncComponent({
loader: () => import('@/components/RichEditor.vue'),
loadingComponent: {
render: () => h('div', { style: 'padding:24px;color:#909399;font-size:13px;text-align:center' }, '富文本编辑器加载中…'),
},
delay: 120,
})
const authStore = useAuthStore() const authStore = useAuthStore()