From b8185434af3b7530c11b00ffc4ff59fe6eca0ebf Mon Sep 17 00:00:00 2001 From: furao Date: Sun, 7 Jun 2026 17:58:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(cli):=20=E5=88=A0=E8=8A=82=E7=82=B9/?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=90=8E=E6=B8=85=E7=90=86=E5=AD=A4=E5=84=BF?= =?UTF-8?q?=E5=85=83=E7=B4=A0=E7=9A=84=20cc.Asset=20=E5=BC=95=E7=94=A8?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=20build=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit remove-node / remove-component 保留孤儿元素(保持其他 __id__ 稳定)的策略下, 残留的 cc.Asset uuid 引用会被 bundle build 扫整个 data 数组撞到、算入依赖图, 运行时拉不存在的资源触发 404。新增 clearOrphanAssetRefs,删完后清理。 Co-Authored-By: Claude Opus 4.8 (1M context) --- cli/src/editor/id-utils.js | 52 ++++++++++++++++++++++++++ cli/src/editor/ops/remove-component.js | 8 +++- cli/src/editor/ops/remove-node.js | 13 ++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/cli/src/editor/id-utils.js b/cli/src/editor/id-utils.js index 2116d43..fefdca7 100644 --- a/cli/src/editor/id-utils.js +++ b/cli/src/editor/id-utils.js @@ -119,6 +119,57 @@ function disconnectSubtree(elements, nodeId) { } } +// ─── 孤儿元素 cc.Asset 引用清理 ────────────────────────────── + +/** + * 清除元素里所有 cc.Asset 引用字段(含 `{__uuid__, __expectedType__}` 的对象, + * 或全部由这类对象组成的数组),把对象置 null、数组置 []。 + * + * 用于 remove-node / remove-component:cli 保留孤儿元素(保持其他 __id__ 稳定)的 + * 策略本身正确,但孤儿元素里残留的 cc.Asset uuid 引用会被 bundle build 扫整个 + * data 数组时撞到、算入依赖图,运行时拉不存在的资源触发 404 + * (典型现象:`GET /assets//import/.json 404`)。 + * + * 只处理顶层字段——cc.Asset 引用一般在第一层(_spriteFrame / _defaultClip / _clips / + * _font / _skeletonData / asset 等);深层递归不做,避免误清非 asset 的嵌套对象。 + * + * 跳过结构字段(__type__/_parent/_prefab/_components/_children/node/__editorExtras__ 等)—— + * 这些走 disconnectSubtree / 父引用清理。 + */ +function clearOrphanAssetRefs(element) { + if (!element || typeof element !== 'object') return; + for (const key of Object.keys(element)) { + if ( + key.startsWith('__') || + key === 'node' || + key === '_parent' || + key === '_prefab' || + key === '_components' || + key === '_children' + ) { + continue; + } + const val = element[key]; + if (!val || typeof val !== 'object') continue; + + // 单个 cc.Asset 引用 {__uuid__: string, __expectedType__: string} + if (typeof val.__uuid__ === 'string' && typeof val.__expectedType__ === 'string') { + element[key] = null; + continue; + } + + // 全部由 cc.Asset 引用组成的数组(如 cc.Animation._clips) + if (Array.isArray(val) && val.length > 0) { + const allAssetRefs = val.every( + (v) => v && typeof v === 'object' && typeof v.__uuid__ === 'string' && typeof v.__expectedType__ === 'string' + ); + if (allAssetRefs) { + element[key] = []; + } + } + } +} + // ─── elements 重排:__id__ 引用映射 / 收缩 ─────────────────── // // 用于 dedupe-component:合并删除组件后,所有 __id__ 指向被删/被合并对象的 @@ -281,6 +332,7 @@ module.exports = { collectExistingFileIds, uniqueFileId, disconnectSubtree, + clearOrphanAssetRefs, countPropertyRefs, isReservedCompField, filterCompRefsInElements, diff --git a/cli/src/editor/ops/remove-component.js b/cli/src/editor/ops/remove-component.js index c17a4de..674120c 100644 --- a/cli/src/editor/ops/remove-component.js +++ b/cli/src/editor/ops/remove-component.js @@ -15,7 +15,7 @@ 'use strict'; const { normalizeComponentType, isStub, resolveNode } = require('../helpers.js'); -const { cleanupRootTargetOverrides } = require('../id-utils.js'); +const { cleanupRootTargetOverrides, clearOrphanAssetRefs } = require('../id-utils.js'); function execRemoveComponent(prefabData, op) { const { elements, rootId } = prefabData; @@ -72,6 +72,12 @@ function execRemoveComponent(prefabData, op) { } cleanupRootTargetOverrides(elements, rootId, removedIds); + // 清孤儿组件里的 cc.Asset 引用字段(_spriteFrame / _defaultClip / _clips / _font 等)。 + // 不清会被 bundle build 扫整个 data 数组撞到、算入依赖图,运行时拉不存在的资源 404。 + for (const id of removedIds) { + clearOrphanAssetRefs(elements[id]); + } + return nodeId; } diff --git a/cli/src/editor/ops/remove-node.js b/cli/src/editor/ops/remove-node.js index 344ad39..e371870 100644 --- a/cli/src/editor/ops/remove-node.js +++ b/cli/src/editor/ops/remove-node.js @@ -6,7 +6,12 @@ 'use strict'; const { isStub, resolveNode } = require('../helpers.js'); -const { disconnectSubtree, cleanupRootTargetOverrides, syncNestedRoots } = require('../id-utils.js'); +const { + disconnectSubtree, + clearOrphanAssetRefs, + cleanupRootTargetOverrides, + syncNestedRoots, +} = require('../id-utils.js'); function execRemoveNode(prefabData, op) { const { elements, rootId } = prefabData; @@ -48,6 +53,12 @@ function execRemoveNode(prefabData, op) { disconnectSubtree(elements, targetId); + // 清孤儿元素里的 cc.Asset 引用字段(asset / _spriteFrame / _defaultClip / _clips 等)。 + // 不清会被 bundle build 扫整个 data 数组撞到、算入依赖图,运行时拉不存在的资源 404。 + for (const id of subtreeIds) { + clearOrphanAssetRefs(elements[id]); + } + // 软删后同步外层 PrefabInfo.nestedPrefabInstanceRoots,清掉孤儿 stub 引用 syncNestedRoots(elements, rootId);