Files
wukuang/perf/memory-leak-and-heavy-computation-audit-2026-04-27.md
T
2026-05-23 14:05:22 +08:00

56 KiB
Raw Blame History

内存泄漏与大内存/大计算风险审计

审计日期:2026-04-27

审计范围:apps/lcdp/src/**packages/**docs/**、根目录配置与现有 perf/** 文档。排查方式为静态代码扫描加重点文件复核,重点关注全局监听、路由守卫、事件总线、定时器、observer、第三方实例、Blob URL、长连接/流式请求、无限缓存、全量拉取、深拷贝、深度监听、递归和批量计算。

执行方式:主线程扫描与复核 + 4 个并行 agent 分区审计。分区覆盖 apps/lcdppackages/business-componentspackages/form-designerpackages/business-modulepackages/core-utils、构建脚本和既有 perf 性能文档。本文已合并 agent 回传结果。

风险等级说明:

等级 含义
较确定会在路由切换、组件卸载、微前端重复挂载后残留;或单次操作能产生明显大内存峰值。
中高 泄漏或大内存风险依赖数据量/交互路径,但影响面较大。
有残留或性能风险,需要补生命周期或限流;通常不会立即导致崩溃。
边界场景风险,建议随相关模块迭代修复。

总体结论

当前项目最需要优先治理的不是单一组件 bug,而是几类长期对象缺少统一释放机制:

  1. 微前端重复挂载相关:main.ts、路由守卫、平台 message handler、全局 bus、全局请求配置都缺少统一 disposer。
  2. 第三方实例相关:ERD Editor、Monaco Editor、自定义组件渲染、文档中心 SDK、Sortable 的实例生命周期不完整。
  3. 指令/组件监听相关:部分 DOM 监听、window 监听、watch、debounce、timeout 未在卸载时清理。
  4. 大数据路径相关:多处 size: 999/9999/99999、全量缓存、深拷贝、深度 watch、JSON.stringify 大对象会造成明显内存峰值和主线程阻塞。
  5. 构建脚本相关:多个 package 的 build 同时运行 vue-tsc --buildvite build,再叠加 vite-plugin-dts、sourcemap 或 postinstall 自动构建,会放大 CI 和本地 install/build 的峰值内存。

建议优先级:

  1. 先修高风险全局残留:路由守卫、postMessage、DataModelView、Monaco、自定义组件渲染、InputFormat 指令。
  2. 再修卸载后异步任务:search input、plugin select、FieldDialog、UseModelAuth、Preview debounce、DocLib。
  3. 最后做大数据治理:全量拉取改分页/虚拟列表,深拷贝和深度 watch 改增量 dirty 标记,AI prompt 和 ERD 数据做裁剪或 worker 化。
  4. 构建阶段峰值内存单独治理:先把并行 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 监听、setIntervalMutationObserver,未统一移除或 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 无 unregisteropenListen 注册匿名 message listener 无关闭能力。 全局 handler 表只能覆盖不能释放;message listener 生命周期不可控。 补 unregister 和 closeListen;监听函数用具名引用;仅在 handler/asyncMap 非空时保持监听。
packages/business-components/src/components/common/monaco-editor/MonacoEditor.vue:64 创建 editor 后没有 onBeforeUnmountpluginRuntime.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 使用匿名 listenerwatch([value, formatterValue], ...) 无 stop,且无 unmounted 指令卸载后 DOM 监听和 watcher 持有 input、value、formatterValue。 handler 和 stop 存到 elunmounted 中 remove + stop。
packages/form-designer/src/config/widget/CustomMobileNumber.vue:264 watch 中创建 ResizeObserver,没有保存,也没有 disconnect wrapper 变化或组件卸载后 observer 残留。 保存 observerwatch 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 初始化后,onBeforeUnmountdestroy() 被注释。 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 等上下文,事件页反复打开后监听叠加。 增加命名 handleronBeforeUnmounteventBus.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:20apps/lcdp/src/AppMobile.vue:15 中高 <KeepAlive>max/include/exclude runtime 页面、设计器、预览页长期保留组件实例、请求状态和第三方实例。 设置 max;只缓存明确需要的轻页面;重页面禁用缓存或实现淘汰策略。

中高风险:卸载后异步任务、全局注册和常驻 UI

位置 风险 证据 影响 建议
apps/lcdp/src/main.ts:139:150:174 中高 requestIdleCallbacksetTimeout 未保存 handle。 微前端卸载后仍可能执行组件注册、语言包加载,闭包持有 app。 保存 idle/timeout idunmount 时 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 idcleanup 时 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 keydownblur 时移除。 聚焦状态下组件销毁可能残留 window listener;最后一个 timeout 仍写状态/emit。 onBeforeUnmountclearTimeout(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 多次下载会积累 listenerlistener 闭包持有 controller。 使用 { once: true },或保存 handler 并在 scheduler resolve/finally 后 remove。
apps/lcdp/src/feature/modeling/components/model-details/entity-model/CurrentModeInfo.vue:116:145 lodash debounce 未 canceltooltip timeout 未清理。 pending debounce 可能在卸载后重新绑定 DOM 监听。 deCostFormMounted.cancel()clearTimeout(time),并加 disposed guard。
apps/lcdp/src/feature/page-preview/Preview.vue:71PreviewMobile.vue:94 lodash debounce 未 cancel;多个 deep watch 改 key 触发重挂载。 失活/卸载后 pending 路由回调写状态;深 watch 扩大重渲染面。 onDeactivated/onUnmounted cancelwatch 标量数组而非 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:222page-model/PageModelList.vue:182mixed-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 += valuecancelFn 到 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 currentSelectedDatatreeEvent.editNode(cloneDeep(...)) 字段属性编辑会频繁触发树节点重写和深拷贝。 只 watch 模型基础字段;字段表单变更不要驱动树;dirty/version 标记替代深度 watch。
apps/lcdp/src/feature/modeling/components/model-details/entity-model/hooks/UseModelAndFieldChange.ts:31Util.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:197Config/utils.ts:12:33 computed 中反复 flatten tree;节点上挂 parenttransform 写 parent/path。 大打印模型树会产生大量临时对象和父链引用。 数据变化时一次性构建 _path -> node Map;关闭弹窗清理;节点只存 parentId/path。
apps/lcdp/src/feature/modeling/components/list/business-model/BusinessModelList.vue:112page-model/PageModelList.vue:222mixed-list/MixedList.vue:518:545 批量引用详情用 Promise.all(cannotDelete*.map(...)) 大量不可删项会并发打满请求,并一次性保存全部详情。 限制并发;弹窗打开后分页/按需请求引用详情。
apps/lcdp/src/feature/modeling/components/list/mixed-list/MixedList.vue:136model-details/index.vue:419business-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:57grid.vue:240tree-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:188hooks/useReferenceDataSource.ts:85hooks/useFeatureDomainDataSource.ts:157hooks/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 中高 fieldOptionsCacheresolverWaits 为模块级 Map;成功和失败结果都写入缓存,未见清理 API。 访问大量 model 后字段选项长期驻留。 增加 clearFieldOptionsCache(modelId?)、TTL/LRU;页面卸载或切 app 时清理。
packages/form-designer/src/widget-mobile/utils/field-mapping-cache.ts:11:15 fieldMappingCachefieldSchemaCache 按 modelId 全局缓存完整字段映射和字段 schema。 移动端多模型页面切换后不会自动释放字段配置对象。 页面卸载或模型切换时调用 clearFieldMappingCache(modelId);加容量限制。
packages/form-designer/src/service/FieldKeyService.ts:20:40 fieldTreeParamsList/fetchLocal/fieldTreeTimermodel_app key 保留;请求 flush 后只清数组,不删除 key 和 timer 引用。 多 model/app 组合会留下大量空数组、空队列和 timer key。 请求结束后删除空 key;失败也清理;必要时加 clearFieldTreeBatchCache(tagKey?)
packages/business-components/src/service/common/BatchFetchLocalService.ts:11BatchParamsService.ts:14BatchParamsService.ts:111 模块级请求聚合缓存,返回前 deepCloneObj10s 后清理成功响应;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:6ImportScript.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 idstopEffect 时 clearDOM 操作前检查 disposed。
packages/form-designer/src/config/widget/list-vxe/preview/core-table/VxeTable.vue:449VxeTablePlus.vue:462 低到中 ResizeObserver 有 disconnect,但 debounce/内部 setTimeout 未 cancel。 卸载边界下仍可能执行一次 recalculate 相关回调。 卸载时 cancel debouncesetTimeout 前后判断 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:22packages/business-module/package.json:22 build 使用 run-p type-check "build-only" 并行运行 vue-tsc --buildvite 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: truedts() sourcemap 与类型生成在同一构建进程中增加内存占用。 发布构建才开 sourcemap;类型生成拆为独立步骤。
package.json:25 postinstall 自动执行 pnpm run build:core install 阶段触发重型 Vite 构建,CI 缓存失效或本地安装时容易出现不可预期峰值。 移出 postinstall,改为显式 bootstrap/build 步骤。

已检查但暂未列为主要问题

位置 结论
apps/lcdp/src/directives/TextHoverHandle.tsapps/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:先止住确定性泄漏

  1. 给微前端生命周期加统一 cleanup registry:路由守卫、idle/timeout、平台 message、AI 初始化、theme adapter、axios interceptors 都登记 disposer。
  2. 修复 DataModelView.vue:恢复 attachShadow、清理 editor listener/timer/observer,避免 200ms 轮询扫全 Shadow DOM。
  3. 修复 MonacoEditor.vue 和 plugin runtime:组件卸载时 dispose editor 和所有 provider/listener。
  4. 修复 EditorWorker.tsrevoke Blob URL,并提供 worker cache 的 terminate/clear。
  5. 修复自定义组件渲染:renderFramework/renderFrameworkESM/renderDebugFrameworkESM 返回统一 handle,调用方卸载时调用 destroy()
  6. 修复 InputFormatHandle.tsCustomMobileNumber.vueUseConfig.ts Sortable、UsePostMessage.tsfileVxe.tsScriptRightPanel.vueActionContent.vue 的生命周期缺口。

P1:控制异步与常驻 UI

  1. 所有请求型组件增加 stale request id 或 AbortController。
  2. lodash debounce/throttle 统一在卸载 cancel。
  3. showMessage({ duration: 0 }) 保存 handle,组件卸载时关闭。
  4. getAICodeService 改为可立即取消,并限制流式内容累计大小。
  5. 对通用 eventBus/mitt 封装开发期泄漏检测:同一组件重复注册、匿名 handler、on/off 引用不一致时输出告警。

P2:降低大数据内存和 CPU 峰值

  1. 清理 size: 999/9999/99999,改分页、懒加载、远程搜索或虚拟列表。
  2. 深拷贝和深度 watch 改为 dirty flag、版本号、局部浅拷贝。
  3. 大 prompt、大 ERD JSON、大导出 JSON 放到 worker 或后端生成。
  4. 模块级 Map 缓存增加 TTL/LRU 和显式 clear API。
  5. list-vxe 继续验证横向虚拟滚动、绑定模型字段纯文本渲染、useCacheComputed 和 merge-cell 预计算优化。

P3:降低构建阶段峰值内存

  1. form-designerbusiness-modulewidget-pc 的 build 改为 test/type-check/build 分阶段串行。
  2. dts rollup、sourcemap 和 Vite build 拆成发布专用步骤。
  3. 移除根 postinstall 的自动重构建,改显式 bootstrap。

验证建议

  1. 微前端重复挂载测试:同一页面 mount/unmount 20 次,检查路由守卫数量、message listener、axios interceptors 是否增长。
  2. Chrome Memory heap snapshot:进入/退出建模详情、ERD 视图、页面预览、Monaco 编辑器、自定义组件渲染页,比较 detached DOM、ResizeObserver、MutationObserver、Monaco model、Sortable 实例数量。
  3. 大数据压测:业务分类、字段选择、应用树、特征变量、打印模型树用 1k/5k/10k 数据量测试内存峰值和 long task。
  4. AI 流式请求测试:发起后立即切路由,确认请求被 abort,accumulatedContent 不再增长。
  5. 增加开发期诊断:包装 eventBus/router/axios/registerPostMessageHandle,开发模式输出当前 listener/interceptor/handler 计数。
  6. list-vxe 当前主路径已使用 VxeTablePlus,建议重新补跑 perf/list-vxe-performance-test.md 的基线数据,避免后续继续引用旧实现测试结果。

Agent 分工记录

分工 覆盖范围 结果
主线程 全仓静态扫描、重点文件复核、文档汇总 已合并所有确认项。
Agent A apps/lcdp/src 全局生命周期、页面预览、建模详情 发现路由守卫、postMessage、ERD editor、指令、全量请求等问题。
Agent B packages/business-componentspackages/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 缓存、构建峰值内存风险。