fix(cli): 删节点/组件后清理孤儿元素的 cc.Asset 引用,避免 build 404

remove-node / remove-component 保留孤儿元素(保持其他 __id__ 稳定)的策略下,
残留的 cc.Asset uuid 引用会被 bundle build 扫整个 data 数组撞到、算入依赖图,
运行时拉不存在的资源触发 404。新增 clearOrphanAssetRefs,删完后清理。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
furao
2026-06-07 17:58:15 +08:00
parent bee9f89c98
commit b8185434af
3 changed files with 71 additions and 2 deletions
+52
View File
@@ -119,6 +119,57 @@ function disconnectSubtree(elements, nodeId) {
}
}
// ─── 孤儿元素 cc.Asset 引用清理 ──────────────────────────────
/**
* 清除元素里所有 cc.Asset 引用字段(含 `{__uuid__, __expectedType__}` 的对象,
* 或全部由这类对象组成的数组),把对象置 null、数组置 []。
*
* 用于 remove-node / remove-componentcli 保留孤儿元素(保持其他 __id__ 稳定)的
* 策略本身正确,但孤儿元素里残留的 cc.Asset uuid 引用会被 bundle build 扫整个
* data 数组时撞到、算入依赖图,运行时拉不存在的资源触发 404
* (典型现象:`GET /assets/<bundle>/import/<uuid>.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,
+7 -1
View File
@@ -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;
}
+12 -1
View File
@@ -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);