56 KiB
56 KiB
内存泄漏与大内存/大计算风险审计
审计日期:2026-04-27
审计范围:apps/lcdp/src/**、packages/**、docs/**、根目录配置与现有 perf/** 文档。排查方式为静态代码扫描加重点文件复核,重点关注全局监听、路由守卫、事件总线、定时器、observer、第三方实例、Blob URL、长连接/流式请求、无限缓存、全量拉取、深拷贝、深度监听、递归和批量计算。
执行方式:主线程扫描与复核 + 4 个并行 agent 分区审计。分区覆盖 apps/lcdp、packages/business-components、packages/form-designer、packages/business-module、packages/core-utils、构建脚本和既有 perf 性能文档。本文已合并 agent 回传结果。
风险等级说明:
| 等级 | 含义 |
|---|---|
| 高 | 较确定会在路由切换、组件卸载、微前端重复挂载后残留;或单次操作能产生明显大内存峰值。 |
| 中高 | 泄漏或大内存风险依赖数据量/交互路径,但影响面较大。 |
| 中 | 有残留或性能风险,需要补生命周期或限流;通常不会立即导致崩溃。 |
| 低 | 边界场景风险,建议随相关模块迭代修复。 |
总体结论
当前项目最需要优先治理的不是单一组件 bug,而是几类长期对象缺少统一释放机制:
- 微前端重复挂载相关:
main.ts、路由守卫、平台 message handler、全局 bus、全局请求配置都缺少统一 disposer。 - 第三方实例相关:ERD Editor、Monaco Editor、自定义组件渲染、文档中心 SDK、Sortable 的实例生命周期不完整。
- 指令/组件监听相关:部分 DOM 监听、window 监听、watch、debounce、timeout 未在卸载时清理。
- 大数据路径相关:多处
size: 999/9999/99999、全量缓存、深拷贝、深度 watch、JSON.stringify大对象会造成明显内存峰值和主线程阻塞。 - 构建脚本相关:多个 package 的
build同时运行vue-tsc --build与vite build,再叠加vite-plugin-dts、sourcemap 或 postinstall 自动构建,会放大 CI 和本地 install/build 的峰值内存。
建议优先级:
- 先修高风险全局残留:路由守卫、postMessage、DataModelView、Monaco、自定义组件渲染、InputFormat 指令。
- 再修卸载后异步任务:search input、plugin select、FieldDialog、UseModelAuth、Preview debounce、DocLib。
- 最后做大数据治理:全量拉取改分页/虚拟列表,深拷贝和深度 watch 改增量 dirty 标记,AI prompt 和 ERD 数据做裁剪或 worker 化。
- 构建阶段峰值内存单独治理:先把并行 type-check/build 改为可配置串行,再处理 dts rollup、sourcemap 和 postinstall 自动构建。
高风险:确定性残留或高概率泄漏
| 位置 | 风险 | 证据 | 影响 | 建议 | |
|---|---|---|---|---|---|
apps/lcdp/src/router/navigation-guards/agentGuards.ts:4 |
高 | initAgentRouterGuards() 内注册 router.afterEach,未保存注销函数;apps/lcdp/src/main.ts:147 每次 render 都调用。 |
微前端反复挂载时导航守卫叠加,闭包保留 agent 注册逻辑。 | 模块级 installed guard;保存 const remove = router.afterEach(...);在子应用卸载时调用。 |
|
apps/lcdp/src/feature/modeling/components/model-details/entity-model/DataModelView.vue:13 |
高 | 覆盖 Element.prototype.attachShadow,未恢复。 |
路由切走后全局 monkey patch 继续影响所有组件和第三方库。 | 避免全局 patch;必须 patch 时幂等保存原函数,并在 onUnmounted 恢复。 |
|
apps/lcdp/src/feature/modeling/components/model-details/entity-model/DataModelView.vue:299 |
高 | 主/子 ERD editor 添加 contextmenu/click 监听、setInterval、MutationObserver,未统一移除或 disconnect。 |
observer/timer/listener 持有 editor、shadowRoot 和组件闭包,路由切换后残留。 | 保存 listener、timer、observer 句柄;返回主视图和卸载时统一 removeEventListener/clearInterval/clearTimeout/disconnect。 |
|
apps/lcdp/src/feature/page-preview/hook/UsePostMessage.ts:124 |
高 | 注册 registerPostMessageHandle 三个全局 handler,卸载时只清理临时数据。 |
handler 持有 previewRef/baseInfo,页面重进会重复响应。 |
registerPostMessageHandle 返回 unregister;或补 unregisterPostMessageHandle(type, handler),卸载时注销。 |
|
packages/core-utils/src/utils/PostMessageUtil.ts:42 |
高 | postMessageHandleMap[type] = handle 无 unregister;openListen 注册匿名 message listener 无关闭能力。 |
全局 handler 表只能覆盖不能释放;message listener 生命周期不可控。 | 补 unregister 和 closeListen;监听函数用具名引用;仅在 handler/asyncMap 非空时保持监听。 |
|
packages/business-components/src/components/common/monaco-editor/MonacoEditor.vue:64 |
高 | 创建 editor 后没有 onBeforeUnmount;pluginRuntime.dispose() 未调用。Plugins.ts:70 的 dispose 逻辑只有其内部 effectScope stop 后才会执行。 |
Monaco editor、model、监听、CodeLens provider、resize/key listeners 可能跨组件残留。 | onBeforeUnmount(() => pluginRuntime.dispose()),并在 runtime dispose 中 editor.dispose()、清理 editor listeners。 |
|
packages/business-components/src/components/common/monaco-editor/EditorWorker.ts:33 |
高 | 为 worker loader 创建 blobUrl 后直接缓存 worker,未 URL.revokeObjectURL(blobUrl),也未提供 worker terminate/clear API。 |
Monaco 多语言 worker 常驻是可接受的设计,但 Blob URL 与 worker 生命周期不可控;热更新、微前端重挂载或多版本加载会累积资源。 | worker 创建后 revoke blob URL;提供 disposeMonacoWorkers(),在子应用卸载或版本切换时 terminate 并清空 workerCache。 |
|
packages/form-designer/src/config/widget/CustomComponentRender.vue:119 |
高 | renderFrameworkESM 返回 destroy,调用方只保存 componentInstance;卸载时只尝试 $destroy。UMD Vue3/React 分支也没有 app/root unmount 返回。 |
自定义组件的 Vue/React 子应用、响应式 scope、DOM 和事件可能无法释放。 | 调用方保存 renderer handle,卸载调用 handle.destroy();renderFramework 的 Vue3/React/Vue2 分支统一返回 destroy。 |
|
apps/lcdp/src/directives/InputFormatHandle.ts:27 |
高 | focus/blur 使用匿名 listener,watch([value, formatterValue], ...) 无 stop,且无 unmounted。 |
指令卸载后 DOM 监听和 watcher 持有 input、value、formatterValue。 | handler 和 stop 存到 el,unmounted 中 remove + stop。 |
|
packages/form-designer/src/config/widget/CustomMobileNumber.vue:264 |
高 | watch 中创建 ResizeObserver,没有保存,也没有 disconnect。 |
wrapper 变化或组件卸载后 observer 残留。 | 保存 observer;watch cleanup 或 onBeforeUnmount 中 disconnect。 |
|
packages/business-components/src/components/common/table/hooks/UseConfig.ts:23 |
高 | new Sortable(el, ...) 未保存实例,未 destroy();tableKey 变化会重复初始化。 |
同一表格 tbody 上叠加拖拽监听和闭包。 | 用 `let sortable: Sortable | null;重新初始化前和卸载时sortable.destroy()`。 |
packages/form-designer/src/components/file-preview/doc-lib-preview/UseDocLib.ts:90 |
高 | DocumentCenterSdk 初始化后,onBeforeUnmount 中 destroy() 被注释。 |
SDK iframe/overlay/message listener/会话资源可能残留。 | 需要和 SDK 方确认 destroy bug;至少调用 destroyCurrentSession(),并清理 debounce executor。 |
|
packages/form-designer/src/config/widget/file-vxe/utils/fileVxe.ts:249、:272 |
高 | eventBus.on(BATCH_DELETE_ATTACHMENT, () => {}) 和 eventBus.on(BATCH_DOWNLOAD_ATTACHMENT, () => {}) 使用匿名函数,onBeforeUnmount 只清字段变更监听。 |
每次附件表格挂载都会保留闭包和 vxeGridRef/pageConfig/fieldtotal。 |
将两个 handler 提升为具名函数,并在 onBeforeUnmount 成对 eventBus.off(type, handler)。 |
|
packages/business-module/src/components/script-panel/ScriptRightPanel.vue:89 |
高 | 注册的是匿名包装函数,卸载时传入 handlerSwitch 执行 off。 |
on/off 函数引用不一致,监听无法移除。 | 保存 const handleSwitchRightHeader = (...) => { ... },注册和移除使用同一引用。 |
|
packages/form-designer/src/config/settings/event-list/components/ActionContent.vue:247 |
高 | eventBus.on(..., (id) => customTreeRef.value?.remove(id)) 无对应卸载清理。 |
会保留 customTreeRef/pageConfig/jsData 等上下文,事件页反复打开后监听叠加。 |
增加命名 handler;onBeforeUnmount 中 eventBus.off(type, handler),并清理 setTimeout。 |
|
apps/lcdp/src/feature/modeling/components/custom-component/group-tree-select/component/group-panel/store/group.ts:37 |
高 | 模块级 storeCache: Map<appId, Store> 只增不删。 |
切换多个 app 后 store 构造器和 group 数据长期驻留。 | 改单 store keyed state;或提供 clearGroupStore(appId)/LRU,并在离开应用时清理。 |
|
apps/lcdp/src/App.vue:20、apps/lcdp/src/AppMobile.vue:15 |
中高 | <KeepAlive> 无 max/include/exclude。 |
runtime 页面、设计器、预览页长期保留组件实例、请求状态和第三方实例。 | 设置 max;只缓存明确需要的轻页面;重页面禁用缓存或实现淘汰策略。 |
中高风险:卸载后异步任务、全局注册和常驻 UI
| 位置 | 风险 | 证据 | 影响 | 建议 |
|---|---|---|---|---|
apps/lcdp/src/main.ts:139、:150、:174 |
中高 | requestIdleCallback、setTimeout 未保存 handle。 |
微前端卸载后仍可能执行组件注册、语言包加载,闭包持有 app。 | 保存 idle/timeout id;unmount 时 cancel;回调中判断 app 是否仍有效。 |
apps/lcdp/src/utils/UsePlatformPostMessageHandler.ts:7 |
中高 | 底层 init() 注册 window message,未接收 disposer。 |
重复初始化可能叠加平台 message listener。 | 包装为幂等单例,暴露 destroy;确认底层是否支持 stop。 |
apps/lcdp/src/utils/MsgPackInterceptors.ts:5 |
中高 | 每次调用注册 axios interceptors,未保存 id,未 eject。当前 main 中注释,但启用后有风险。 | 重复挂载导致拦截器叠加。 | installed guard;保存 request/response id;cleanup 时 eject。 |
apps/lcdp/src/utils/mobileAuthUtils.ts:32 |
中 | execAutoRefreshToken(reIns.getAxiosIns()) 无 stop/幂等保护。 |
自动刷新定时器或拦截器可能重复注册。 | 确认底层返回值;增加单例 guard 和 stop。 |
apps/lcdp/src/components/common/search-input/index.vue:29、:59 |
中高 | debounce timeout 未卸载清理;focus 时 window keydown,blur 时移除。 | 聚焦状态下组件销毁可能残留 window listener;最后一个 timeout 仍写状态/emit。 | onBeforeUnmount 中 clearTimeout(timeout) 和 removeEventListener。 |
apps/lcdp/src/directives/ImageHandle.ts:32、:45 |
中 | 原生 img 与内部 img 使用匿名 error/load handler;卸载只 disconnect observer。 |
已绑定到真实 img 的 listener 不能精准移除。 | 记录 img 与 handler,卸载时 remove;移除临时 img 并清空字段。 |
apps/lcdp/src/components/business-common/plugin-select/hook/usePluginSelect.ts:91、:105、:197、:228、:265 |
中 | 请求完成后继续写 state;多处 timeout 未清理。 | 卸载后异步回调操作 DOM/ref;请求结果过期仍覆盖新状态。 | disposed flag 或 request id;保存 timeout id 数组并在卸载清理。 |
apps/lcdp/src/components/business-common/field-dialog/FieldDialog.vue:120 |
中 | 请求完成后写 list/total/status,关闭弹窗只重置 isInit。 |
弹窗关闭/卸载后旧请求仍写状态。 | 请求版本号或 AbortController;关闭/卸载时忽略旧响应。 |
packages/form-designer/src/components/btn-action/components/model-import-export/components/file/File.vue:22 |
中 | 上传态 setInterval 只在 state.type 变化时清理,没有卸载清理。 |
弹窗或组件在上传态关闭时 interval 留存,继续写 progress。 |
引入 onUnmounted(() => clearInterval(timer));重新进入 upload 前先清旧 interval。 |
packages/core-utils/src/hooks/UseBatchDownload.ts:248 |
中 | 每次调度都向外部 AbortSignal 添加 abort listener,未在调度结束后移除。 |
长生命周期 signal 多次下载会积累 listener;listener 闭包持有 controller。 | 使用 { once: true },或保存 handler 并在 scheduler resolve/finally 后 remove。 |
apps/lcdp/src/feature/modeling/components/model-details/entity-model/CurrentModeInfo.vue:116、:145 |
中 | lodash debounce 未 cancel;tooltip timeout 未清理。 | pending debounce 可能在卸载后重新绑定 DOM 监听。 | deCostFormMounted.cancel()、clearTimeout(time),并加 disposed guard。 |
apps/lcdp/src/feature/page-preview/Preview.vue:71、PreviewMobile.vue:94 |
中 | lodash debounce 未 cancel;多个 deep watch 改 key 触发重挂载。 |
失活/卸载后 pending 路由回调写状态;深 watch 扩大重渲染面。 | onDeactivated/onUnmounted cancel;watch 标量数组而非 deep object。 |
apps/lcdp/src/feature/modeling/components/model-details/auth-control/UseModelAuth.ts:34、:66 |
中 | debounced save/fetch 未 cancel;请求无 stale 判断。 | 权限页卸载后延迟保存/拉取继续持有 authInfo。 |
保存 debounced fetch 引用;卸载时 cancel/flush;请求加 stale token。 |
apps/lcdp/src/feature/modeling/components/list/business-model/BusinessModelList.vue:222、page-model/PageModelList.vue:182、mixed-list/MixedList.vue:191 |
中高 | showMessage({ duration: 0, append: h(... onClick ...) })。 |
全局常驻 message 闭包持有组件方法和列表数据。 | 保存 message handle,卸载或列表刷新时 close;设置有限 duration;点击时按 id 查询当前数据。 |
packages/form-designer/src/config/widget/list-vxe/composables/fetch.ts:196 |
中 | getTableData 是 lodash debounce;文件内未见 onUnmounted/onScopeDispose 中 .cancel()。 |
列表卸载后 pending debounce 仍可能触发请求或写入已失效的 ref;已有 abort 只在函数执行后才生效。 | 作用域销毁时 getTableData.cancel() 并 abortPendingRequest()。 |
packages/business-components/src/utils/VirtualTooltip.ts:5 |
中 | 全局 tooltip container/vnode/currentEl/timer 无销毁 API。 | 长生命周期单例可接受,但当前元素引用和 popper listener 可能滞留到下一次显示。 | 提供 destroyGlobalVirtualTooltip();隐藏时清空 virtualRef/currentEl 并清理 timer。 |
packages/business-components/src/composables/VirtualPopover.tsx:70、:222 |
低到中 | unmount 时 off 了 mitt,但 debounce open/close 未 cancel。 | pending debounce 可能在根组件卸载后改全局 state。 | onUnmounted 中 cancel debounceOpenPopover/debounceClosePopover。 |
大内存和大计算热点
| 位置 | 风险 | 证据 | 影响 | 建议 |
|---|---|---|---|---|
apps/lcdp/src/feature/modeling/components/model-details/entity-model/DataModelView.vue:87、:142、:377 |
高 | 每 200ms 递归 querySelectorAll('*') 扫 Shadow DOM 并重新注入样式;ERD 数据整体 JSON.stringify。 |
大 ER 图下 CPU 高、短期对象多,可能触发频繁 GC。 | 优先使用 ERD API/配置隐藏工具栏;observer 只监听目标节点;大数据转换和序列化移 worker 或限量。 |
apps/lcdp/src/service/BillTransferService.ts:67 |
高 | 流式 AI 响应 accumulatedContent += value;cancelFn 到 onDone 才随 resolve 返回。 |
调用方请求中途拿不到取消句柄;页面卸载后流继续累积大字符串。 | 立即返回 { promise, cancelFn } 或接收 AbortSignal;限制累计长度;取消/失败时清空缓冲。 |
apps/lcdp/src/feature/bill-transfer/hooks/useFieldMappingAI.ts:117、:121、:147、:201 |
中高 | flatTableData 有数据就不重建;cloneDeep(tableData);Map 持有行对象;JSON.stringify(pairs)。 |
表格变更后缓存不失效;大映射序列化和 Map 引用旧行。 | 按 source/target/tableData 版本重建;完成后清空 Map;AI 入参分批或裁剪字段。 |
apps/lcdp/src/service/ModelingService.ts:67 |
中高 | 导出模型 JSON.stringify(res, null, 2),再 Blob 和 URL.createObjectURL,未 revoke。 |
大模型导出有字符串+Blob 双份内存峰值;Blob URL 泄漏。 | 复用 downloadBlob 或点击后 URL.revokeObjectURL(url);大数据后端直接返回 blob/流。 |
apps/lcdp/src/service/ApplicationService.ts:95、:203 |
中高 | size: 99999;递归转换中反复复制 path 数组。 |
大租户业务/应用树会带来网络、内存和 CPU 压力。 | 后端分页/懒加载;限制 size;路径用 parentId 或共享数组迭代构建。 |
apps/lcdp/src/components/business-common/business-dropdown/index.vue:28 |
中 | size: 99999 拉全量,再前端 slice 展示。 |
大业务分类时前端持有全量列表。 | 分页/远程搜索;输入 debounce;不要保留全量。 |
apps/lcdp/src/components/business-common/field-dialog/FieldDialog.vue:125、:177 |
中 | 主列表和子节点都 size: 999,展开时 map/filter。 |
引用字段树懒加载可能多次拉大列表和复制数组。 | 后端分页/限制层级;按 referenceId 缓存;只加载必要字段。 |
apps/lcdp/src/components/common/search-select/index.vue:69 |
低到中 | watch(searchOptions, ..., { deep: true })。 |
选项多时深度遍历成本高。 | 改浅 watch 或赋值后直接同步 resShowOptions。 |
apps/lcdp/src/components/business-common/app-select/index.vue:34 |
低 | 打开弹窗 cloneDeep(list)。 |
list 较大时开窗产生完整副本。 | 只复制会被修改字段;用浅拷贝加按需 clone。 |
apps/lcdp/src/feature/modeling/components/model-details/entity-model/hooks/UseTreeLogic.ts:229、:263、:387、:632 |
中高 | 多处 cloneDeep 整树/对象;深度 watch currentSelectedData 后 treeEvent.editNode(cloneDeep(...))。 |
字段属性编辑会频繁触发树节点重写和深拷贝。 | 只 watch 模型基础字段;字段表单变更不要驱动树;dirty/version 标记替代深度 watch。 |
apps/lcdp/src/feature/modeling/components/model-details/entity-model/hooks/UseModelAndFieldChange.ts:31、Util.ts:7 |
中高 | requestDataIsChange 对整个 requestData 做 isEqual;复制模型路径多处 cloneDeep。 |
模型/子模型/字段操作列表越大,任意响应式变更都可能触发递归比较。 | 维护增量 dirty flag;按 operation list 判断变更;避免 computed 中全量深比较。 |
apps/lcdp/src/feature/modeling/components/model-details/feature-variable/hooks/useFeatureGroup.ts:225、:245、:304、:342 |
中高 | tableListGather 缓存全量数据;分页/搜索前 cloneDeep 全量列表; debounce 未 cancel。 |
数据多时内存翻倍,分页和搜索 CPU 高。 | 服务端分页/搜索;只克隆当前页;卸载清空缓存并 cancel debounce。 |
apps/lcdp/src/feature/print/hooks/print.ts:197、Config/utils.ts:12、:33 |
中 | computed 中反复 flatten tree;节点上挂 parent;transform 写 parent/path。 |
大打印模型树会产生大量临时对象和父链引用。 | 数据变化时一次性构建 _path -> node Map;关闭弹窗清理;节点只存 parentId/path。 |
apps/lcdp/src/feature/modeling/components/list/business-model/BusinessModelList.vue:112、page-model/PageModelList.vue:222、mixed-list/MixedList.vue:518、:545 |
中 | 批量引用详情用 Promise.all(cannotDelete*.map(...))。 |
大量不可删项会并发打满请求,并一次性保存全部详情。 | 限制并发;弹窗打开后分页/按需请求引用详情。 |
apps/lcdp/src/feature/modeling/components/list/mixed-list/MixedList.vue:136、model-details/index.vue:419、business-operation/FormEdit.vue:156 |
中 | 多处 size: 9999 拉页面/规则/操作列表。 |
一次性大列表进入 refs/options,路由切换无 abort。 | 改后端分页/搜索型 select;弹开再加载;请求加 AbortController 或 stale id。 |
apps/lcdp/src/feature/modeling/components/model-details/entity-model/hooks/UseAiBatchAdd.ts:304、:322 |
中 | AI 上下文 JSON.stringify(treeData.map(...)) 和全部字段列表;还有大对象 console。 |
大模型会生成大 prompt 字符串并阻塞主线程。 | 限制字段数量/深度,只传当前模型摘要;移除大对象 log;必要时 worker 构造 prompt。 |
apps/lcdp/src/feature/deployment-package/components/log/index.vue:23、:39 |
中低 | 一次性 logList.value = res,全量 v-for 渲染日志。 |
大安装日志占内存并卡渲染。 | 分页/虚拟列表;限制最大展示行数并提供完整日志下载。 |
apps/lcdp/src/views/demo/vxe-table/index.vue:57、grid.vue:240、tree-v2/index.vue:77 |
中 | demo 可生成 5w 行 × 120 列,树 demo 参数组合可生成极大节点数。 | 若路由可访问,会冻结主线程和内存暴涨。 | 加总量上限;大数据生成分片/worker;非虚拟表不要提供极端数据。 |
apps/lcdp/src/views/RuleRuntimeTest.vue:62 |
中低 | new RuntimeRuleEventCenter(...) 未见卸载销毁。 |
如果规则中心内部有事件或缓存,实例可能跨页面滞留。 | 确认类是否有 destroy/off/dispose;卸载调用。 |
packages/form-designer/src/config/widget/list-vxe/preview/core-table/VxeTablePlus.vue:239 |
中 | 纵向虚拟滚动已开启,但 virtualXConfig.enabled=false。 |
37+ 列宽表仍会让所有列参与渲染,横向大列数下 DOM/布局成本高。 | 在 fixed 列场景回归后开启横向虚拟滚动,或按列数阈值启用。 |
packages/form-designer/src/config/widget/table/FieldViewMap.ts:36 |
中高 | 绑定模型的选择字段仍走 Tag/TextCollapseTag/LightText 等组件渲染。 |
大列表中每单元格生成组件/VNode,已优化的纯文本路径不能覆盖该场景。 | 列表模式下为绑定模型选择字段提供纯文本 formatter,只有交互态再挂重组件。 |
packages/form-designer/src/config/widget/list-vxe/composables/useCacheComputed.ts:5 |
中 | stableStringify 对 watcher 值排序 key 并 JSON.stringify;结果还用 isEqual 比较。 |
大 schema/columns 变化时深遍历、字符串分配和深比较叠加。 | 用明确依赖的 shallow watch/computed 替代;逐步移除大对象 stringify。 |
packages/form-designer/src/config/widget/list-vxe/preview/core-table/useMergeCells.ts:117 |
中 | 每次为字段路径创建 values/truthy/sigs 数组,对对象值 safeStringify。 |
O(rows * paths) 预计算和字符串分配会在大表格下形成内存峰值。 | 先判断是否存在合并/分组列;只预计算可合并字段。 |
packages/form-designer/src/config/widget/list-vxe/preview/ListVxePreview.vue:632、:637 |
中 | 暴露的 getTableData/addTableData 对整页表格数据 deepCloneObj。 |
外部频繁读取/新增时复制整页对象图。 | 读接口返回只读引用或浅拷贝;只在外部可变写入边界 clone。 |
packages/form-designer/src/config/widget/list-vxe/index.ts:2 |
中 | 仍导出旧 VxeTable.vue,外部 import 可能绕过 VxeTablePlus。 |
旧实现的 useCacheComputed 与渲染路径仍可能被业务使用。 |
标记旧导出废弃或指向 Plus;补兼容测试。 |
packages/form-designer/src/components/settings-operation/index.vue:101 |
中 | 默认 size: 9999 获取所有操作。 |
大模型操作多时,一次性进入 optionList 并渲染。 |
改远程搜索/分页 select;仅弹窗打开时加载当前页。 |
packages/form-designer/src/hooks/useLoadPage.ts:149、:565 |
中 | 页面加载两处字段列表请求 size: 999。 |
多页面/多模型初始化时会把字段元数据批量拉入 store。 | 字段元数据按组件/引用路径懒加载;通用 field key 服务加缓存上限和按页清理。 |
packages/business-module/src/components/script-panel/Event.ts:60 |
中 | 脚本面板加载页面列表 size: 999,并在内存中构建树。 |
大模型多页面场景下脚本面板打开即产生大列表和树对象。 | 改按关键字/节点懒加载;树节点展开再请求。 |
packages/business-components/src/components/business/reference-data-select/FieldDialog.vue:188、hooks/useReferenceDataSource.ts:85、hooks/useFeatureDomainDataSource.ts:157、hooks/useFilterDataSource.ts:75 |
中 | 引用字段选择多处子节点加载 size: 999。 |
深引用模型下每次展开都可能拉完整字段列表,并映射生成路径字符串。 | 按层分页、限制最大展开数;相同 referenceId 加 TTL/LRU 缓存。 |
packages/form-designer/src/widget-mobile/composables/list-fetch/useMobileFetch.ts:249、:260 |
中 | deep watch 内用 JSON.stringify 比较 filter/order。 |
条件树变大时每次变更都会整树序列化,触发主线程阻塞和临时字符串内存。 | 在 store 内维护版本号/hash,或按字段浅比较。 |
packages 目录的长期缓存与工具风险
| 位置 | 风险 | 证据 | 影响 | 建议 |
|---|---|---|---|---|
packages/form-designer/src/service/FilterService.ts:27 |
中高 | fieldOptionsCache、resolverWaits 为模块级 Map;成功和失败结果都写入缓存,未见清理 API。 |
访问大量 model 后字段选项长期驻留。 | 增加 clearFieldOptionsCache(modelId?)、TTL/LRU;页面卸载或切 app 时清理。 |
packages/form-designer/src/widget-mobile/utils/field-mapping-cache.ts:11、:15 |
中 | fieldMappingCache、fieldSchemaCache 按 modelId 全局缓存完整字段映射和字段 schema。 |
移动端多模型页面切换后不会自动释放字段配置对象。 | 页面卸载或模型切换时调用 clearFieldMappingCache(modelId);加容量限制。 |
packages/form-designer/src/service/FieldKeyService.ts:20、:40 |
中 | fieldTreeParamsList/fetchLocal/fieldTreeTimer 按 model_app key 保留;请求 flush 后只清数组,不删除 key 和 timer 引用。 |
多 model/app 组合会留下大量空数组、空队列和 timer key。 | 请求结束后删除空 key;失败也清理;必要时加 clearFieldTreeBatchCache(tagKey?)。 |
packages/business-components/src/service/common/BatchFetchLocalService.ts:11、BatchParamsService.ts:14、BatchParamsService.ts:111 |
中 | 模块级请求聚合缓存,返回前 deepCloneObj;10s 后清理成功响应;ApiLocalMap[primaryKey] 不删除空容器。 |
短时间大量 key 会积压响应和 promise;不同 params 会留下大量空 Map;深拷贝放大 CPU/内存峰值。 | 增加最大 key 数、总大小限制;response/request 为空时删除 primaryKey;失败/超时清理 request;大响应避免深拷贝。 |
packages/business-module/src/stores/Page.ts:22 |
中 | pageStoreMap 只增不删,resetPageDataCode 只重置数据,不 dispose store。 |
脚本面板多 pageId 切换会保留 Pinia store 和响应式对象。 | 增加 deletePageStore(pageId):调用 $dispose() 并从 Map 删除;面板卸载时调用。 |
packages/business-components/src/utils/InterceptorUtils.ts:43 |
中 | CACHE_INFO_DATA localStorage 对象只增不删。 |
长期使用多个页面/模型后本地缓存膨胀。 | 给缓存项加版本/时间戳/LRU;应用切换时清理旧项。 |
packages/form-designer/src/utils/LoadUmd.ts:6、ImportScript.ts:19 |
中 | UMD load promise 全局缓存;script 成功后不移除;loadSourceWithCache 的 then 中没有 return。 |
动态组件越多,script/window 全局和 promise 缓存越多。 | 按组件版本设计 LRU;提供 unload 能力;修复 return get(window, umdPathKey)。 |
packages/form-designer/src/utils/LoadCustomResource.ts:172、:185 |
中 | cssLinkMap 全局缓存并向 head 追加 link,无移除。 |
动态加载大量组件样式后 CSS 常驻。 | 明确这是进程级缓存;或按组件引用计数清理。 |
packages/lowcode-create/src/logics/SandBox.ts:57、:140 |
中 | watcher 在 effectScope 中,CodeProtocol.destroy() 会 stop;但 setFieldStyle 的 3s timeout 未保存。 |
表单卸载后 timeout 仍可能查询/写 DOM。 | 保存 timeout id;stopEffect 时 clear;DOM 操作前检查 disposed。 |
packages/form-designer/src/config/widget/list-vxe/preview/core-table/VxeTable.vue:449、VxeTablePlus.vue:462 |
低到中 | ResizeObserver 有 disconnect,但 debounce/内部 setTimeout 未 cancel。 | 卸载边界下仍可能执行一次 recalculate 相关回调。 | 卸载时 cancel debounce;setTimeout 前后判断 mounted。 |
packages/business-components/src/components/business/upload/UseUploadModel.ts:14 |
低 | Blob URL 在 scope dispose 时统一 revoke。 | 删除单个 raw 文件时 URL 会保留到组件卸载。 | 文件移除时同步 revoke 对应 URL。 |
构建脚本内存峰值风险
| 位置 | 风险 | 证据 | 影响 | 建议 |
|---|---|---|---|---|
packages/form-designer/package.json:22、packages/business-module/package.json:22 |
高 | build 使用 run-p type-check "build-only" 并行运行 vue-tsc --build 和 vite build。 |
两个进程同时加载 TS/Vue/Rollup 依赖图,CI 或本地低内存机器峰值接近翻倍。 | CI 改串行阶段;或拆 job;必要时为 build/type-check 单独设置 NODE_OPTIONS=--max-old-space-size=...。 |
packages/widget-pc/package.json:13 |
高 | pnpm test && run-p type-check "build-only"。 |
测试结束后仍并行类型检查和构建,内存峰值高;失败重试成本也高。 | 测试、类型检查、构建分阶段执行;发布流水线再组合。 |
packages/widget-pc/vite.config.ts:20、:23 |
中 | vite-plugin-dts 启用 rollupTypes: true。 |
dts rollup 会构建类型图,和 Vite/Rollup 同进程叠加内存。 | 非发布构建关闭 rollupTypes;类型声明单独命令生成。 |
packages/lowcode-create/vite.config.ts:21、:55 |
中 | lib build 同时开启 sourcemap: true 和 dts()。 |
sourcemap 与类型生成在同一构建进程中增加内存占用。 | 发布构建才开 sourcemap;类型生成拆为独立步骤。 |
package.json:25 |
中 | postinstall 自动执行 pnpm run build:core。 |
install 阶段触发重型 Vite 构建,CI 缓存失效或本地安装时容易出现不可预期峰值。 | 移出 postinstall,改为显式 bootstrap/build 步骤。 |
已检查但暂未列为主要问题
| 位置 | 结论 |
|---|---|
apps/lcdp/src/directives/TextHoverHandle.ts、apps/lcdp/src/directives/vTooltip.ts |
DOM listener 有成对 remove,主要问题是全局 tooltip 单例本身缺少 destroy。 |
packages/form-designer/src/hooks/useResizable.ts |
mousemove/mouseup 和 ResizeObserver 在卸载时清理,当前实现基本完整。 |
packages/form-designer/src/config/widget/list-vxe/preview/core-table/* |
ResizeObserver 有 disconnect;残余风险在 debounce/setTimeout cancel。 |
packages/business-components/src/components/business/upload/UseUploadModel.ts |
已在 onScopeDispose revoke Object URL。 |
perf/list-vxe-performance-analysis.md |
已有 list-vxe 渲染卡顿分析,结论可继续沿用:大分页下主要瓶颈是 vxe-grid DOM 渲染,而非请求。 |
建议落地任务
P0:先止住确定性泄漏
- 给微前端生命周期加统一 cleanup registry:路由守卫、idle/timeout、平台 message、AI 初始化、theme adapter、axios interceptors 都登记 disposer。
- 修复
DataModelView.vue:恢复attachShadow、清理 editor listener/timer/observer,避免 200ms 轮询扫全 Shadow DOM。 - 修复
MonacoEditor.vue和 plugin runtime:组件卸载时 dispose editor 和所有 provider/listener。 - 修复
EditorWorker.ts:revoke Blob URL,并提供 worker cache 的 terminate/clear。 - 修复自定义组件渲染:
renderFramework/renderFrameworkESM/renderDebugFrameworkESM返回统一 handle,调用方卸载时调用destroy()。 - 修复
InputFormatHandle.ts、CustomMobileNumber.vue、UseConfig.tsSortable、UsePostMessage.ts、fileVxe.ts、ScriptRightPanel.vue、ActionContent.vue的生命周期缺口。
P1:控制异步与常驻 UI
- 所有请求型组件增加 stale request id 或 AbortController。
- lodash debounce/throttle 统一在卸载 cancel。
showMessage({ duration: 0 })保存 handle,组件卸载时关闭。getAICodeService改为可立即取消,并限制流式内容累计大小。- 对通用 eventBus/mitt 封装开发期泄漏检测:同一组件重复注册、匿名 handler、on/off 引用不一致时输出告警。
P2:降低大数据内存和 CPU 峰值
- 清理
size: 999/9999/99999,改分页、懒加载、远程搜索或虚拟列表。 - 深拷贝和深度 watch 改为 dirty flag、版本号、局部浅拷贝。
- 大 prompt、大 ERD JSON、大导出 JSON 放到 worker 或后端生成。
- 模块级 Map 缓存增加 TTL/LRU 和显式 clear API。
- list-vxe 继续验证横向虚拟滚动、绑定模型字段纯文本渲染、
useCacheComputed和 merge-cell 预计算优化。
P3:降低构建阶段峰值内存
form-designer、business-module、widget-pc的 build 改为 test/type-check/build 分阶段串行。- dts rollup、sourcemap 和 Vite build 拆成发布专用步骤。
- 移除根
postinstall的自动重构建,改显式 bootstrap。
验证建议
- 微前端重复挂载测试:同一页面 mount/unmount 20 次,检查路由守卫数量、message listener、axios interceptors 是否增长。
- Chrome Memory heap snapshot:进入/退出建模详情、ERD 视图、页面预览、Monaco 编辑器、自定义组件渲染页,比较 detached DOM、ResizeObserver、MutationObserver、Monaco model、Sortable 实例数量。
- 大数据压测:业务分类、字段选择、应用树、特征变量、打印模型树用 1k/5k/10k 数据量测试内存峰值和 long task。
- AI 流式请求测试:发起后立即切路由,确认请求被 abort,
accumulatedContent不再增长。 - 增加开发期诊断:包装 eventBus/router/axios/registerPostMessageHandle,开发模式输出当前 listener/interceptor/handler 计数。
- list-vxe 当前主路径已使用
VxeTablePlus,建议重新补跑perf/list-vxe-performance-test.md的基线数据,避免后续继续引用旧实现测试结果。
Agent 分工记录
| 分工 | 覆盖范围 | 结果 |
|---|---|---|
| 主线程 | 全仓静态扫描、重点文件复核、文档汇总 | 已合并所有确认项。 |
| Agent A | apps/lcdp/src 全局生命周期、页面预览、建模详情 |
发现路由守卫、postMessage、ERD editor、指令、全量请求等问题。 |
| Agent B | packages/business-components、packages/core-utils |
发现 Monaco、PostMessageUtil、Batch service、Tooltip、UseBatchDownload 等问题。 |
| Agent C | packages/form-designer 运行时、页面加载、动态组件 |
发现自定义组件渲染、DocLib、FilterService、LoadUmd、list/tree 字段加载等问题。 |
| Agent D | list-vxe、脚本面板、构建脚本 |
补充 list-vxe 热点、匿名 eventBus 泄漏、脚本面板 store 缓存、构建峰值内存风险。 |