@lytjs/common-node-cache 6.5.0 → 6.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -122,7 +122,10 @@ var NodeCache = class {
122
122
  try {
123
123
  if (listener.el == null || typeof listener.el !== "object") {
124
124
  if (__DEV__) {
125
- console.warn("[lytjs/node-cache] Invalid event listener element, skipping:", listener.el);
125
+ console.warn(
126
+ "[lytjs/node-cache] Invalid event listener element, skipping:",
127
+ listener.el
128
+ );
126
129
  }
127
130
  continue;
128
131
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// @lytjs/common-node-cache\r\n// 节点缓存:管理 container → VNode 映射、组件实例 → 资源注册表映射、统一清理函数\r\n\r\ndeclare const __DEV__: boolean;\r\n\r\nimport type { RendererHost, HostEventHandler, HostEventOptions, HostEvent } from '@lytjs/host-contract';\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/**\r\n * 平台无关的 VNode 接口。\r\n *\r\n * L2 层不依赖具体的 VNode 实现,仅使用此最小接口。\r\n */\r\nexport interface VNode {\r\n /** 节点类型标识 */\r\n type: string | symbol | object;\r\n /** 节点属性 */\r\n props: Record<string, unknown> | null;\r\n /** 子节点 */\r\n children: VNode[] | string | null;\r\n /** 关联的组件实例 */\r\n component?: ComponentInstance | null;\r\n /** 宿主节点引用(渲染后赋值) */\r\n el?: unknown;\r\n /** 锚点引用 */\r\n anchor?: unknown;\r\n}\r\n\r\n/**\r\n * 组件内部实例(最小接口)。\r\n *\r\n * L2 层仅需要组件实例的标识能力,用于资源注册表映射。\r\n */\r\nexport interface ComponentInstance {\r\n /** 组件唯一标识 */\r\n uid: number;\r\n /** 组件类型 */\r\n type: object;\r\n /** 是否已卸载 */\r\n isUnmounted: boolean;\r\n /** VNode 引用 */\r\n vnode: VNode;\r\n /** 父组件实例 */\r\n parent: ComponentInstance | null;\r\n /** 子树 VNode */\r\n subTree: VNode;\r\n}\r\n\r\n/**\r\n * 事件监听器注册条目。\r\n */\r\nexport interface EventListenerEntry<HE> {\r\n /** 宿主元素 */\r\n el: HE;\r\n /** 事件名 */\r\n event: string;\r\n /** 处理函数 */\r\n handler: (event: HostEvent) => void;\r\n /** 事件选项 */\r\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\r\n}\r\n\r\n/**\r\n * 资源注册表条目。\r\n */\r\nexport interface ResourceEntry {\r\n /** 事件监听器列表 */\r\n eventListeners: EventListenerEntry<unknown>[];\r\n /** effect 订阅 dispose 列表 */\r\n effectDisposers: Array<() => void>;\r\n /** 通用清理回调列表 */\r\n cleanupHooks: Array<() => void>;\r\n}\r\n\r\n/**\r\n * 节点缓存配置项。\r\n */\r\nexport interface NodeCacheOptions {\r\n /** 是否启用 VNode 映射(默认 true) */\r\n enableVNodeMap?: boolean;\r\n /** 是否启用资源注册表(默认 true) */\r\n enableResourceRegistry?: boolean;\r\n}\r\n\r\n// ============================================================\r\n// 常量\r\n// ============================================================\r\n\r\n/** 默认配置 */\r\nconst DEFAULT_OPTIONS: Required<NodeCacheOptions> = {\r\n enableVNodeMap: true,\r\n enableResourceRegistry: true,\r\n};\r\n\r\n// ============================================================\r\n// NodeCache\r\n// ============================================================\r\n\r\n/**\r\n * 节点缓存。\r\n *\r\n * 管理 container → VNode 映射和组件实例 → 资源注册表映射,\r\n * 提供统一的资源清理函数。\r\n *\r\n * @template HN - 宿主节点类型\r\n * @template HE - 宿主元素类型\r\n */\r\nexport class NodeCache<HN extends object = object, HE extends HN = HN> {\r\n /** RendererHost 实例 */\r\n private host: RendererHost<HN, HE>;\r\n\r\n /** 配置项 */\r\n private options: Required<NodeCacheOptions>;\r\n\r\n /** container → VNode 映射 */\r\n private vnodeMap = new WeakMap<HN, VNode | null>();\r\n\r\n /** 组件实例 → 资源注册表映射 */\r\n private resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\r\n\r\n /**\r\n * 创建节点缓存实例。\r\n * @param host - RendererHost 实例\r\n * @param options - 可选的配置项\r\n */\r\n constructor(host: RendererHost<HN, HE>, options?: NodeCacheOptions) {\r\n this.host = host;\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n }\r\n\r\n // ==========================================================\r\n // VNode 映射\r\n // ==========================================================\r\n\r\n /**\r\n * 获取 container 对应的 VNode。\r\n * @param container - 宿主容器节点\r\n * @returns VNode 或 null\r\n */\r\n getVNode(container: HN): VNode | null {\r\n if (!this.options.enableVNodeMap) return null;\r\n return this.vnodeMap.get(container) ?? null;\r\n }\r\n\r\n /**\r\n * 设置 container → VNode 映射。\r\n * @param container - 宿主容器节点\r\n * @param vnode - VNode 实例\r\n */\r\n setVNode(container: HN, vnode: VNode | null): void {\r\n if (!this.options.enableVNodeMap) return;\r\n this.vnodeMap.set(container, vnode);\r\n }\r\n\r\n /**\r\n * 删除 container 的 VNode 映射。\r\n * @param container - 宿主容器节点\r\n */\r\n deleteVNode(container: HN): void {\r\n this.vnodeMap.delete(container);\r\n }\r\n\r\n // ==========================================================\r\n // 资源注册表\r\n // ==========================================================\r\n\r\n /**\r\n * 注册事件监听器到组件实例的资源注册表。\r\n *\r\n * @param instance - 组件实例\r\n * @param el - 宿主元素\r\n * @param event - 事件名\r\n * @param handler - 事件处理函数\r\n * @param options - 可选的事件选项\r\n */\r\n registerEventListener(\r\n instance: ComponentInstance,\r\n el: HE,\r\n event: string,\r\n handler: HostEventHandler,\r\n options?: HostEventOptions,\r\n ): void {\r\n if (!this.options.enableResourceRegistry) return;\r\n\r\n const entry = this.getOrCreateResourceEntry(instance);\r\n entry.eventListeners.push({ el, event, handler, options });\r\n }\r\n\r\n /**\r\n * 注册 effect 订阅到组件实例的资源注册表。\r\n *\r\n * @param instance - 组件实例\r\n * @param disposer - effect 的 dispose 回调函数\r\n */\r\n registerEffectSubscription(instance: ComponentInstance, disposer: () => void): void {\r\n if (!this.options.enableResourceRegistry) return;\r\n\r\n const entry = this.getOrCreateResourceEntry(instance);\r\n entry.effectDisposers.push(disposer);\r\n }\r\n\r\n /**\r\n * 注册通用清理钩子到组件实例的资源注册表。\r\n *\r\n * @param instance - 组件实例\r\n * @param cleanup - 清理回调函数\r\n */\r\n registerCleanup(instance: ComponentInstance, cleanup: () => void): void {\r\n if (!this.options.enableResourceRegistry) return;\r\n\r\n const entry = this.getOrCreateResourceEntry(instance);\r\n entry.cleanupHooks.push(cleanup);\r\n }\r\n\r\n // ==========================================================\r\n // 统一清理\r\n // ==========================================================\r\n\r\n /**\r\n * 统一清理组件实例的所有注册资源。\r\n *\r\n * 清理顺序:\r\n * 1. cleanup 钩子(可能依赖 effect 仍活跃)\r\n * 2. effect 订阅(停止响应式追踪)\r\n * 3. 事件监听器(DOM 操作,最后执行)\r\n *\r\n * 每个清理操作均通过 try-catch 保护,单个失败不影响其余流程。\r\n *\r\n * @param instance - 要清理资源的组件实例\r\n */\r\n cleanupComponentResources(instance: ComponentInstance): void {\r\n const entry = this.resourceRegistry.get(instance);\r\n if (!entry) return;\r\n\r\n // 1. 执行 cleanup 钩子\r\n for (const cleanup of entry.cleanupHooks) {\r\n try {\r\n cleanup();\r\n } catch (err) {\r\n if (__DEV__) console.warn('[lytjs/node-cache] Error during cleanup hook:', err);\r\n }\r\n }\r\n\r\n // 2. 清理 effect 订阅\r\n for (const disposer of entry.effectDisposers) {\r\n try {\r\n disposer();\r\n } catch (err) {\r\n if (__DEV__) console.warn('[lytjs/node-cache] Error during effect dispose:', err);\r\n }\r\n }\r\n\r\n // 3. 清理事件监听器\r\n for (const listener of entry.eventListeners) {\r\n try {\r\n // FIX: P2-v11-21 添加类型检查,确保 listener.el 是有效的宿主元素\r\n if (listener.el == null || typeof listener.el !== 'object') {\r\n if (__DEV__) {\r\n console.warn('[lytjs/node-cache] Invalid event listener element, skipping:', listener.el);\r\n }\r\n continue;\r\n }\r\n this.host.removeEventListener(\r\n // FIX: P2-batch2-13 添加运行时类型检查,确保 listener.el 符合 HE 类型约束\r\n listener.el as HE,\r\n listener.event,\r\n listener.handler,\r\n listener.options,\r\n );\r\n } catch (err) {\r\n if (__DEV__) console.warn('[lytjs/node-cache] Error during event listener cleanup:', err);\r\n }\r\n }\r\n\r\n // 删除注册表条目\r\n this.resourceRegistry.delete(instance);\r\n }\r\n\r\n /**\r\n * 清理指定 container 的 VNode 映射。\r\n * @param container - 宿主容器节点\r\n */\r\n cleanupContainer(container: HN): void {\r\n this.vnodeMap.delete(container);\r\n }\r\n\r\n /**\r\n * 销毁缓存,清理所有内部状态。\r\n *\r\n * 注意:不会自动清理所有组件资源,需提前手动调用 cleanupComponentResources。\r\n */\r\n dispose(): void {\r\n this.vnodeMap = new WeakMap<HN, VNode | null>();\r\n this.resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\r\n }\r\n\r\n // ==========================================================\r\n // 内部方法\r\n // ==========================================================\r\n\r\n /**\r\n * 获取或创建组件实例的资源注册表条目。\r\n */\r\n private getOrCreateResourceEntry(instance: ComponentInstance): ResourceEntry {\r\n let entry = this.resourceRegistry.get(instance);\r\n if (!entry) {\r\n entry = {\r\n eventListeners: [],\r\n effectDisposers: [],\r\n cleanupHooks: [],\r\n };\r\n this.resourceRegistry.set(instance, entry);\r\n }\r\n return entry;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// @lytjs/common-node-cache\n// 节点缓存:管理 container → VNode 映射、组件实例 → 资源注册表映射、统一清理函数\n\ndeclare const __DEV__: boolean;\n\nimport type {\n RendererHost,\n HostEventHandler,\n HostEventOptions,\n HostEvent,\n} from '@lytjs/host-contract';\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 平台无关的 VNode 接口。\n *\n * L2 层不依赖具体的 VNode 实现,仅使用此最小接口。\n */\nexport interface VNode {\n /** 节点类型标识 */\n type: string | symbol | object;\n /** 节点属性 */\n props: Record<string, unknown> | null;\n /** 子节点 */\n children: VNode[] | string | null;\n /** 关联的组件实例 */\n component?: ComponentInstance | null;\n /** 宿主节点引用(渲染后赋值) */\n el?: unknown;\n /** 锚点引用 */\n anchor?: unknown;\n}\n\n/**\n * 组件内部实例(最小接口)。\n *\n * L2 层仅需要组件实例的标识能力,用于资源注册表映射。\n */\nexport interface ComponentInstance {\n /** 组件唯一标识 */\n uid: number;\n /** 组件类型 */\n type: object;\n /** 是否已卸载 */\n isUnmounted: boolean;\n /** VNode 引用 */\n vnode: VNode;\n /** 父组件实例 */\n parent: ComponentInstance | null;\n /** 子树 VNode */\n subTree: VNode;\n}\n\n/**\n * 事件监听器注册条目。\n */\nexport interface EventListenerEntry<HE> {\n /** 宿主元素 */\n el: HE;\n /** 事件名 */\n event: string;\n /** 处理函数 */\n handler: (event: HostEvent) => void;\n /** 事件选项 */\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\n}\n\n/**\n * 资源注册表条目。\n */\nexport interface ResourceEntry {\n /** 事件监听器列表 */\n eventListeners: EventListenerEntry<unknown>[];\n /** effect 订阅 dispose 列表 */\n effectDisposers: Array<() => void>;\n /** 通用清理回调列表 */\n cleanupHooks: Array<() => void>;\n}\n\n/**\n * 节点缓存配置项。\n */\nexport interface NodeCacheOptions {\n /** 是否启用 VNode 映射(默认 true) */\n enableVNodeMap?: boolean;\n /** 是否启用资源注册表(默认 true) */\n enableResourceRegistry?: boolean;\n}\n\n// ============================================================\n// 常量\n// ============================================================\n\n/** 默认配置 */\nconst DEFAULT_OPTIONS: Required<NodeCacheOptions> = {\n enableVNodeMap: true,\n enableResourceRegistry: true,\n};\n\n// ============================================================\n// NodeCache\n// ============================================================\n\n/**\n * 节点缓存。\n *\n * 管理 container → VNode 映射和组件实例 → 资源注册表映射,\n * 提供统一的资源清理函数。\n *\n * @template HN - 宿主节点类型\n * @template HE - 宿主元素类型\n */\nexport class NodeCache<HN extends object = object, HE extends HN = HN> {\n /** RendererHost 实例 */\n private host: RendererHost<HN, HE>;\n\n /** 配置项 */\n private options: Required<NodeCacheOptions>;\n\n /** container → VNode 映射 */\n private vnodeMap = new WeakMap<HN, VNode | null>();\n\n /** 组件实例 → 资源注册表映射 */\n private resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\n\n /**\n * 创建节点缓存实例。\n * @param host - RendererHost 实例\n * @param options - 可选的配置项\n */\n constructor(host: RendererHost<HN, HE>, options?: NodeCacheOptions) {\n this.host = host;\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n // ==========================================================\n // VNode 映射\n // ==========================================================\n\n /**\n * 获取 container 对应的 VNode。\n * @param container - 宿主容器节点\n * @returns VNode 或 null\n */\n getVNode(container: HN): VNode | null {\n if (!this.options.enableVNodeMap) return null;\n return this.vnodeMap.get(container) ?? null;\n }\n\n /**\n * 设置 container → VNode 映射。\n * @param container - 宿主容器节点\n * @param vnode - VNode 实例\n */\n setVNode(container: HN, vnode: VNode | null): void {\n if (!this.options.enableVNodeMap) return;\n this.vnodeMap.set(container, vnode);\n }\n\n /**\n * 删除 container 的 VNode 映射。\n * @param container - 宿主容器节点\n */\n deleteVNode(container: HN): void {\n this.vnodeMap.delete(container);\n }\n\n // ==========================================================\n // 资源注册表\n // ==========================================================\n\n /**\n * 注册事件监听器到组件实例的资源注册表。\n *\n * @param instance - 组件实例\n * @param el - 宿主元素\n * @param event - 事件名\n * @param handler - 事件处理函数\n * @param options - 可选的事件选项\n */\n registerEventListener(\n instance: ComponentInstance,\n el: HE,\n event: string,\n handler: HostEventHandler,\n options?: HostEventOptions,\n ): void {\n if (!this.options.enableResourceRegistry) return;\n\n const entry = this.getOrCreateResourceEntry(instance);\n entry.eventListeners.push({ el, event, handler, options });\n }\n\n /**\n * 注册 effect 订阅到组件实例的资源注册表。\n *\n * @param instance - 组件实例\n * @param disposer - effect 的 dispose 回调函数\n */\n registerEffectSubscription(instance: ComponentInstance, disposer: () => void): void {\n if (!this.options.enableResourceRegistry) return;\n\n const entry = this.getOrCreateResourceEntry(instance);\n entry.effectDisposers.push(disposer);\n }\n\n /**\n * 注册通用清理钩子到组件实例的资源注册表。\n *\n * @param instance - 组件实例\n * @param cleanup - 清理回调函数\n */\n registerCleanup(instance: ComponentInstance, cleanup: () => void): void {\n if (!this.options.enableResourceRegistry) return;\n\n const entry = this.getOrCreateResourceEntry(instance);\n entry.cleanupHooks.push(cleanup);\n }\n\n // ==========================================================\n // 统一清理\n // ==========================================================\n\n /**\n * 统一清理组件实例的所有注册资源。\n *\n * 清理顺序:\n * 1. cleanup 钩子(可能依赖 effect 仍活跃)\n * 2. effect 订阅(停止响应式追踪)\n * 3. 事件监听器(DOM 操作,最后执行)\n *\n * 每个清理操作均通过 try-catch 保护,单个失败不影响其余流程。\n *\n * @param instance - 要清理资源的组件实例\n */\n cleanupComponentResources(instance: ComponentInstance): void {\n const entry = this.resourceRegistry.get(instance);\n if (!entry) return;\n\n // 1. 执行 cleanup 钩子\n for (const cleanup of entry.cleanupHooks) {\n try {\n cleanup();\n } catch (err) {\n if (__DEV__) console.warn('[lytjs/node-cache] Error during cleanup hook:', err);\n }\n }\n\n // 2. 清理 effect 订阅\n for (const disposer of entry.effectDisposers) {\n try {\n disposer();\n } catch (err) {\n if (__DEV__) console.warn('[lytjs/node-cache] Error during effect dispose:', err);\n }\n }\n\n // 3. 清理事件监听器\n for (const listener of entry.eventListeners) {\n try {\n // FIX: P2-v11-21 添加类型检查,确保 listener.el 是有效的宿主元素\n if (listener.el == null || typeof listener.el !== 'object') {\n if (__DEV__) {\n console.warn(\n '[lytjs/node-cache] Invalid event listener element, skipping:',\n listener.el,\n );\n }\n continue;\n }\n this.host.removeEventListener(\n // FIX: P2-batch2-13 添加运行时类型检查,确保 listener.el 符合 HE 类型约束\n listener.el as HE,\n listener.event,\n listener.handler,\n listener.options,\n );\n } catch (err) {\n if (__DEV__) console.warn('[lytjs/node-cache] Error during event listener cleanup:', err);\n }\n }\n\n // 删除注册表条目\n this.resourceRegistry.delete(instance);\n }\n\n /**\n * 清理指定 container 的 VNode 映射。\n * @param container - 宿主容器节点\n */\n cleanupContainer(container: HN): void {\n this.vnodeMap.delete(container);\n }\n\n /**\n * 销毁缓存,清理所有内部状态。\n *\n * 注意:不会自动清理所有组件资源,需提前手动调用 cleanupComponentResources。\n */\n dispose(): void {\n this.vnodeMap = new WeakMap<HN, VNode | null>();\n this.resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\n }\n\n // ==========================================================\n // 内部方法\n // ==========================================================\n\n /**\n * 获取或创建组件实例的资源注册表条目。\n */\n private getOrCreateResourceEntry(instance: ComponentInstance): ResourceEntry {\n let entry = this.resourceRegistry.get(instance);\n if (!entry) {\n entry = {\n eventListeners: [],\n effectDisposers: [],\n cleanupHooks: [],\n };\n this.resourceRegistry.set(instance, entry);\n }\n return entry;\n }\n}\n"]}
package/dist/index.mjs CHANGED
@@ -120,7 +120,10 @@ var NodeCache = class {
120
120
  try {
121
121
  if (listener.el == null || typeof listener.el !== "object") {
122
122
  if (__DEV__) {
123
- console.warn("[lytjs/node-cache] Invalid event listener element, skipping:", listener.el);
123
+ console.warn(
124
+ "[lytjs/node-cache] Invalid event listener element, skipping:",
125
+ listener.el
126
+ );
124
127
  }
125
128
  continue;
126
129
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @lytjs/common-node-cache\r\n// 节点缓存:管理 container → VNode 映射、组件实例 → 资源注册表映射、统一清理函数\r\n\r\ndeclare const __DEV__: boolean;\r\n\r\nimport type { RendererHost, HostEventHandler, HostEventOptions, HostEvent } from '@lytjs/host-contract';\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/**\r\n * 平台无关的 VNode 接口。\r\n *\r\n * L2 层不依赖具体的 VNode 实现,仅使用此最小接口。\r\n */\r\nexport interface VNode {\r\n /** 节点类型标识 */\r\n type: string | symbol | object;\r\n /** 节点属性 */\r\n props: Record<string, unknown> | null;\r\n /** 子节点 */\r\n children: VNode[] | string | null;\r\n /** 关联的组件实例 */\r\n component?: ComponentInstance | null;\r\n /** 宿主节点引用(渲染后赋值) */\r\n el?: unknown;\r\n /** 锚点引用 */\r\n anchor?: unknown;\r\n}\r\n\r\n/**\r\n * 组件内部实例(最小接口)。\r\n *\r\n * L2 层仅需要组件实例的标识能力,用于资源注册表映射。\r\n */\r\nexport interface ComponentInstance {\r\n /** 组件唯一标识 */\r\n uid: number;\r\n /** 组件类型 */\r\n type: object;\r\n /** 是否已卸载 */\r\n isUnmounted: boolean;\r\n /** VNode 引用 */\r\n vnode: VNode;\r\n /** 父组件实例 */\r\n parent: ComponentInstance | null;\r\n /** 子树 VNode */\r\n subTree: VNode;\r\n}\r\n\r\n/**\r\n * 事件监听器注册条目。\r\n */\r\nexport interface EventListenerEntry<HE> {\r\n /** 宿主元素 */\r\n el: HE;\r\n /** 事件名 */\r\n event: string;\r\n /** 处理函数 */\r\n handler: (event: HostEvent) => void;\r\n /** 事件选项 */\r\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\r\n}\r\n\r\n/**\r\n * 资源注册表条目。\r\n */\r\nexport interface ResourceEntry {\r\n /** 事件监听器列表 */\r\n eventListeners: EventListenerEntry<unknown>[];\r\n /** effect 订阅 dispose 列表 */\r\n effectDisposers: Array<() => void>;\r\n /** 通用清理回调列表 */\r\n cleanupHooks: Array<() => void>;\r\n}\r\n\r\n/**\r\n * 节点缓存配置项。\r\n */\r\nexport interface NodeCacheOptions {\r\n /** 是否启用 VNode 映射(默认 true) */\r\n enableVNodeMap?: boolean;\r\n /** 是否启用资源注册表(默认 true) */\r\n enableResourceRegistry?: boolean;\r\n}\r\n\r\n// ============================================================\r\n// 常量\r\n// ============================================================\r\n\r\n/** 默认配置 */\r\nconst DEFAULT_OPTIONS: Required<NodeCacheOptions> = {\r\n enableVNodeMap: true,\r\n enableResourceRegistry: true,\r\n};\r\n\r\n// ============================================================\r\n// NodeCache\r\n// ============================================================\r\n\r\n/**\r\n * 节点缓存。\r\n *\r\n * 管理 container → VNode 映射和组件实例 → 资源注册表映射,\r\n * 提供统一的资源清理函数。\r\n *\r\n * @template HN - 宿主节点类型\r\n * @template HE - 宿主元素类型\r\n */\r\nexport class NodeCache<HN extends object = object, HE extends HN = HN> {\r\n /** RendererHost 实例 */\r\n private host: RendererHost<HN, HE>;\r\n\r\n /** 配置项 */\r\n private options: Required<NodeCacheOptions>;\r\n\r\n /** container → VNode 映射 */\r\n private vnodeMap = new WeakMap<HN, VNode | null>();\r\n\r\n /** 组件实例 → 资源注册表映射 */\r\n private resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\r\n\r\n /**\r\n * 创建节点缓存实例。\r\n * @param host - RendererHost 实例\r\n * @param options - 可选的配置项\r\n */\r\n constructor(host: RendererHost<HN, HE>, options?: NodeCacheOptions) {\r\n this.host = host;\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n }\r\n\r\n // ==========================================================\r\n // VNode 映射\r\n // ==========================================================\r\n\r\n /**\r\n * 获取 container 对应的 VNode。\r\n * @param container - 宿主容器节点\r\n * @returns VNode 或 null\r\n */\r\n getVNode(container: HN): VNode | null {\r\n if (!this.options.enableVNodeMap) return null;\r\n return this.vnodeMap.get(container) ?? null;\r\n }\r\n\r\n /**\r\n * 设置 container → VNode 映射。\r\n * @param container - 宿主容器节点\r\n * @param vnode - VNode 实例\r\n */\r\n setVNode(container: HN, vnode: VNode | null): void {\r\n if (!this.options.enableVNodeMap) return;\r\n this.vnodeMap.set(container, vnode);\r\n }\r\n\r\n /**\r\n * 删除 container 的 VNode 映射。\r\n * @param container - 宿主容器节点\r\n */\r\n deleteVNode(container: HN): void {\r\n this.vnodeMap.delete(container);\r\n }\r\n\r\n // ==========================================================\r\n // 资源注册表\r\n // ==========================================================\r\n\r\n /**\r\n * 注册事件监听器到组件实例的资源注册表。\r\n *\r\n * @param instance - 组件实例\r\n * @param el - 宿主元素\r\n * @param event - 事件名\r\n * @param handler - 事件处理函数\r\n * @param options - 可选的事件选项\r\n */\r\n registerEventListener(\r\n instance: ComponentInstance,\r\n el: HE,\r\n event: string,\r\n handler: HostEventHandler,\r\n options?: HostEventOptions,\r\n ): void {\r\n if (!this.options.enableResourceRegistry) return;\r\n\r\n const entry = this.getOrCreateResourceEntry(instance);\r\n entry.eventListeners.push({ el, event, handler, options });\r\n }\r\n\r\n /**\r\n * 注册 effect 订阅到组件实例的资源注册表。\r\n *\r\n * @param instance - 组件实例\r\n * @param disposer - effect 的 dispose 回调函数\r\n */\r\n registerEffectSubscription(instance: ComponentInstance, disposer: () => void): void {\r\n if (!this.options.enableResourceRegistry) return;\r\n\r\n const entry = this.getOrCreateResourceEntry(instance);\r\n entry.effectDisposers.push(disposer);\r\n }\r\n\r\n /**\r\n * 注册通用清理钩子到组件实例的资源注册表。\r\n *\r\n * @param instance - 组件实例\r\n * @param cleanup - 清理回调函数\r\n */\r\n registerCleanup(instance: ComponentInstance, cleanup: () => void): void {\r\n if (!this.options.enableResourceRegistry) return;\r\n\r\n const entry = this.getOrCreateResourceEntry(instance);\r\n entry.cleanupHooks.push(cleanup);\r\n }\r\n\r\n // ==========================================================\r\n // 统一清理\r\n // ==========================================================\r\n\r\n /**\r\n * 统一清理组件实例的所有注册资源。\r\n *\r\n * 清理顺序:\r\n * 1. cleanup 钩子(可能依赖 effect 仍活跃)\r\n * 2. effect 订阅(停止响应式追踪)\r\n * 3. 事件监听器(DOM 操作,最后执行)\r\n *\r\n * 每个清理操作均通过 try-catch 保护,单个失败不影响其余流程。\r\n *\r\n * @param instance - 要清理资源的组件实例\r\n */\r\n cleanupComponentResources(instance: ComponentInstance): void {\r\n const entry = this.resourceRegistry.get(instance);\r\n if (!entry) return;\r\n\r\n // 1. 执行 cleanup 钩子\r\n for (const cleanup of entry.cleanupHooks) {\r\n try {\r\n cleanup();\r\n } catch (err) {\r\n if (__DEV__) console.warn('[lytjs/node-cache] Error during cleanup hook:', err);\r\n }\r\n }\r\n\r\n // 2. 清理 effect 订阅\r\n for (const disposer of entry.effectDisposers) {\r\n try {\r\n disposer();\r\n } catch (err) {\r\n if (__DEV__) console.warn('[lytjs/node-cache] Error during effect dispose:', err);\r\n }\r\n }\r\n\r\n // 3. 清理事件监听器\r\n for (const listener of entry.eventListeners) {\r\n try {\r\n // FIX: P2-v11-21 添加类型检查,确保 listener.el 是有效的宿主元素\r\n if (listener.el == null || typeof listener.el !== 'object') {\r\n if (__DEV__) {\r\n console.warn('[lytjs/node-cache] Invalid event listener element, skipping:', listener.el);\r\n }\r\n continue;\r\n }\r\n this.host.removeEventListener(\r\n // FIX: P2-batch2-13 添加运行时类型检查,确保 listener.el 符合 HE 类型约束\r\n listener.el as HE,\r\n listener.event,\r\n listener.handler,\r\n listener.options,\r\n );\r\n } catch (err) {\r\n if (__DEV__) console.warn('[lytjs/node-cache] Error during event listener cleanup:', err);\r\n }\r\n }\r\n\r\n // 删除注册表条目\r\n this.resourceRegistry.delete(instance);\r\n }\r\n\r\n /**\r\n * 清理指定 container 的 VNode 映射。\r\n * @param container - 宿主容器节点\r\n */\r\n cleanupContainer(container: HN): void {\r\n this.vnodeMap.delete(container);\r\n }\r\n\r\n /**\r\n * 销毁缓存,清理所有内部状态。\r\n *\r\n * 注意:不会自动清理所有组件资源,需提前手动调用 cleanupComponentResources。\r\n */\r\n dispose(): void {\r\n this.vnodeMap = new WeakMap<HN, VNode | null>();\r\n this.resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\r\n }\r\n\r\n // ==========================================================\r\n // 内部方法\r\n // ==========================================================\r\n\r\n /**\r\n * 获取或创建组件实例的资源注册表条目。\r\n */\r\n private getOrCreateResourceEntry(instance: ComponentInstance): ResourceEntry {\r\n let entry = this.resourceRegistry.get(instance);\r\n if (!entry) {\r\n entry = {\r\n eventListeners: [],\r\n effectDisposers: [],\r\n cleanupHooks: [],\r\n };\r\n this.resourceRegistry.set(instance, entry);\r\n }\r\n return entry;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @lytjs/common-node-cache\n// 节点缓存:管理 container → VNode 映射、组件实例 → 资源注册表映射、统一清理函数\n\ndeclare const __DEV__: boolean;\n\nimport type {\n RendererHost,\n HostEventHandler,\n HostEventOptions,\n HostEvent,\n} from '@lytjs/host-contract';\n\n// ============================================================\n// 类型定义\n// ============================================================\n\n/**\n * 平台无关的 VNode 接口。\n *\n * L2 层不依赖具体的 VNode 实现,仅使用此最小接口。\n */\nexport interface VNode {\n /** 节点类型标识 */\n type: string | symbol | object;\n /** 节点属性 */\n props: Record<string, unknown> | null;\n /** 子节点 */\n children: VNode[] | string | null;\n /** 关联的组件实例 */\n component?: ComponentInstance | null;\n /** 宿主节点引用(渲染后赋值) */\n el?: unknown;\n /** 锚点引用 */\n anchor?: unknown;\n}\n\n/**\n * 组件内部实例(最小接口)。\n *\n * L2 层仅需要组件实例的标识能力,用于资源注册表映射。\n */\nexport interface ComponentInstance {\n /** 组件唯一标识 */\n uid: number;\n /** 组件类型 */\n type: object;\n /** 是否已卸载 */\n isUnmounted: boolean;\n /** VNode 引用 */\n vnode: VNode;\n /** 父组件实例 */\n parent: ComponentInstance | null;\n /** 子树 VNode */\n subTree: VNode;\n}\n\n/**\n * 事件监听器注册条目。\n */\nexport interface EventListenerEntry<HE> {\n /** 宿主元素 */\n el: HE;\n /** 事件名 */\n event: string;\n /** 处理函数 */\n handler: (event: HostEvent) => void;\n /** 事件选项 */\n options?: { capture?: boolean; once?: boolean; passive?: boolean };\n}\n\n/**\n * 资源注册表条目。\n */\nexport interface ResourceEntry {\n /** 事件监听器列表 */\n eventListeners: EventListenerEntry<unknown>[];\n /** effect 订阅 dispose 列表 */\n effectDisposers: Array<() => void>;\n /** 通用清理回调列表 */\n cleanupHooks: Array<() => void>;\n}\n\n/**\n * 节点缓存配置项。\n */\nexport interface NodeCacheOptions {\n /** 是否启用 VNode 映射(默认 true) */\n enableVNodeMap?: boolean;\n /** 是否启用资源注册表(默认 true) */\n enableResourceRegistry?: boolean;\n}\n\n// ============================================================\n// 常量\n// ============================================================\n\n/** 默认配置 */\nconst DEFAULT_OPTIONS: Required<NodeCacheOptions> = {\n enableVNodeMap: true,\n enableResourceRegistry: true,\n};\n\n// ============================================================\n// NodeCache\n// ============================================================\n\n/**\n * 节点缓存。\n *\n * 管理 container → VNode 映射和组件实例 → 资源注册表映射,\n * 提供统一的资源清理函数。\n *\n * @template HN - 宿主节点类型\n * @template HE - 宿主元素类型\n */\nexport class NodeCache<HN extends object = object, HE extends HN = HN> {\n /** RendererHost 实例 */\n private host: RendererHost<HN, HE>;\n\n /** 配置项 */\n private options: Required<NodeCacheOptions>;\n\n /** container → VNode 映射 */\n private vnodeMap = new WeakMap<HN, VNode | null>();\n\n /** 组件实例 → 资源注册表映射 */\n private resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\n\n /**\n * 创建节点缓存实例。\n * @param host - RendererHost 实例\n * @param options - 可选的配置项\n */\n constructor(host: RendererHost<HN, HE>, options?: NodeCacheOptions) {\n this.host = host;\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n // ==========================================================\n // VNode 映射\n // ==========================================================\n\n /**\n * 获取 container 对应的 VNode。\n * @param container - 宿主容器节点\n * @returns VNode 或 null\n */\n getVNode(container: HN): VNode | null {\n if (!this.options.enableVNodeMap) return null;\n return this.vnodeMap.get(container) ?? null;\n }\n\n /**\n * 设置 container → VNode 映射。\n * @param container - 宿主容器节点\n * @param vnode - VNode 实例\n */\n setVNode(container: HN, vnode: VNode | null): void {\n if (!this.options.enableVNodeMap) return;\n this.vnodeMap.set(container, vnode);\n }\n\n /**\n * 删除 container 的 VNode 映射。\n * @param container - 宿主容器节点\n */\n deleteVNode(container: HN): void {\n this.vnodeMap.delete(container);\n }\n\n // ==========================================================\n // 资源注册表\n // ==========================================================\n\n /**\n * 注册事件监听器到组件实例的资源注册表。\n *\n * @param instance - 组件实例\n * @param el - 宿主元素\n * @param event - 事件名\n * @param handler - 事件处理函数\n * @param options - 可选的事件选项\n */\n registerEventListener(\n instance: ComponentInstance,\n el: HE,\n event: string,\n handler: HostEventHandler,\n options?: HostEventOptions,\n ): void {\n if (!this.options.enableResourceRegistry) return;\n\n const entry = this.getOrCreateResourceEntry(instance);\n entry.eventListeners.push({ el, event, handler, options });\n }\n\n /**\n * 注册 effect 订阅到组件实例的资源注册表。\n *\n * @param instance - 组件实例\n * @param disposer - effect 的 dispose 回调函数\n */\n registerEffectSubscription(instance: ComponentInstance, disposer: () => void): void {\n if (!this.options.enableResourceRegistry) return;\n\n const entry = this.getOrCreateResourceEntry(instance);\n entry.effectDisposers.push(disposer);\n }\n\n /**\n * 注册通用清理钩子到组件实例的资源注册表。\n *\n * @param instance - 组件实例\n * @param cleanup - 清理回调函数\n */\n registerCleanup(instance: ComponentInstance, cleanup: () => void): void {\n if (!this.options.enableResourceRegistry) return;\n\n const entry = this.getOrCreateResourceEntry(instance);\n entry.cleanupHooks.push(cleanup);\n }\n\n // ==========================================================\n // 统一清理\n // ==========================================================\n\n /**\n * 统一清理组件实例的所有注册资源。\n *\n * 清理顺序:\n * 1. cleanup 钩子(可能依赖 effect 仍活跃)\n * 2. effect 订阅(停止响应式追踪)\n * 3. 事件监听器(DOM 操作,最后执行)\n *\n * 每个清理操作均通过 try-catch 保护,单个失败不影响其余流程。\n *\n * @param instance - 要清理资源的组件实例\n */\n cleanupComponentResources(instance: ComponentInstance): void {\n const entry = this.resourceRegistry.get(instance);\n if (!entry) return;\n\n // 1. 执行 cleanup 钩子\n for (const cleanup of entry.cleanupHooks) {\n try {\n cleanup();\n } catch (err) {\n if (__DEV__) console.warn('[lytjs/node-cache] Error during cleanup hook:', err);\n }\n }\n\n // 2. 清理 effect 订阅\n for (const disposer of entry.effectDisposers) {\n try {\n disposer();\n } catch (err) {\n if (__DEV__) console.warn('[lytjs/node-cache] Error during effect dispose:', err);\n }\n }\n\n // 3. 清理事件监听器\n for (const listener of entry.eventListeners) {\n try {\n // FIX: P2-v11-21 添加类型检查,确保 listener.el 是有效的宿主元素\n if (listener.el == null || typeof listener.el !== 'object') {\n if (__DEV__) {\n console.warn(\n '[lytjs/node-cache] Invalid event listener element, skipping:',\n listener.el,\n );\n }\n continue;\n }\n this.host.removeEventListener(\n // FIX: P2-batch2-13 添加运行时类型检查,确保 listener.el 符合 HE 类型约束\n listener.el as HE,\n listener.event,\n listener.handler,\n listener.options,\n );\n } catch (err) {\n if (__DEV__) console.warn('[lytjs/node-cache] Error during event listener cleanup:', err);\n }\n }\n\n // 删除注册表条目\n this.resourceRegistry.delete(instance);\n }\n\n /**\n * 清理指定 container 的 VNode 映射。\n * @param container - 宿主容器节点\n */\n cleanupContainer(container: HN): void {\n this.vnodeMap.delete(container);\n }\n\n /**\n * 销毁缓存,清理所有内部状态。\n *\n * 注意:不会自动清理所有组件资源,需提前手动调用 cleanupComponentResources。\n */\n dispose(): void {\n this.vnodeMap = new WeakMap<HN, VNode | null>();\n this.resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();\n }\n\n // ==========================================================\n // 内部方法\n // ==========================================================\n\n /**\n * 获取或创建组件实例的资源注册表条目。\n */\n private getOrCreateResourceEntry(instance: ComponentInstance): ResourceEntry {\n let entry = this.resourceRegistry.get(instance);\n if (!entry) {\n entry = {\n eventListeners: [],\n effectDisposers: [],\n cleanupHooks: [],\n };\n this.resourceRegistry.set(instance, entry);\n }\n return entry;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lytjs/common-node-cache",
3
- "version": "6.5.0",
3
+ "version": "6.7.0",
4
4
  "description": "Node cache for managing container-VNode mappings and component resource registries in LytJS",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -27,7 +27,7 @@
27
27
  "sideEffects": false,
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
- "@lytjs/host-contract": "^6.0.0"
30
+ "@lytjs/host-contract": "^6.7.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "tsup": "^8.4.0",
package/src/index.ts CHANGED
@@ -1,319 +1,327 @@
1
- // @lytjs/common-node-cache
2
- // 节点缓存:管理 container → VNode 映射、组件实例 → 资源注册表映射、统一清理函数
3
-
4
- declare const __DEV__: boolean;
5
-
6
- import type { RendererHost, HostEventHandler, HostEventOptions, HostEvent } from '@lytjs/host-contract';
7
-
8
- // ============================================================
9
- // 类型定义
10
- // ============================================================
11
-
12
- /**
13
- * 平台无关的 VNode 接口。
14
- *
15
- * L2 层不依赖具体的 VNode 实现,仅使用此最小接口。
16
- */
17
- export interface VNode {
18
- /** 节点类型标识 */
19
- type: string | symbol | object;
20
- /** 节点属性 */
21
- props: Record<string, unknown> | null;
22
- /** 子节点 */
23
- children: VNode[] | string | null;
24
- /** 关联的组件实例 */
25
- component?: ComponentInstance | null;
26
- /** 宿主节点引用(渲染后赋值) */
27
- el?: unknown;
28
- /** 锚点引用 */
29
- anchor?: unknown;
30
- }
31
-
32
- /**
33
- * 组件内部实例(最小接口)。
34
- *
35
- * L2 层仅需要组件实例的标识能力,用于资源注册表映射。
36
- */
37
- export interface ComponentInstance {
38
- /** 组件唯一标识 */
39
- uid: number;
40
- /** 组件类型 */
41
- type: object;
42
- /** 是否已卸载 */
43
- isUnmounted: boolean;
44
- /** VNode 引用 */
45
- vnode: VNode;
46
- /** 父组件实例 */
47
- parent: ComponentInstance | null;
48
- /** 子树 VNode */
49
- subTree: VNode;
50
- }
51
-
52
- /**
53
- * 事件监听器注册条目。
54
- */
55
- export interface EventListenerEntry<HE> {
56
- /** 宿主元素 */
57
- el: HE;
58
- /** 事件名 */
59
- event: string;
60
- /** 处理函数 */
61
- handler: (event: HostEvent) => void;
62
- /** 事件选项 */
63
- options?: { capture?: boolean; once?: boolean; passive?: boolean };
64
- }
65
-
66
- /**
67
- * 资源注册表条目。
68
- */
69
- export interface ResourceEntry {
70
- /** 事件监听器列表 */
71
- eventListeners: EventListenerEntry<unknown>[];
72
- /** effect 订阅 dispose 列表 */
73
- effectDisposers: Array<() => void>;
74
- /** 通用清理回调列表 */
75
- cleanupHooks: Array<() => void>;
76
- }
77
-
78
- /**
79
- * 节点缓存配置项。
80
- */
81
- export interface NodeCacheOptions {
82
- /** 是否启用 VNode 映射(默认 true) */
83
- enableVNodeMap?: boolean;
84
- /** 是否启用资源注册表(默认 true) */
85
- enableResourceRegistry?: boolean;
86
- }
87
-
88
- // ============================================================
89
- // 常量
90
- // ============================================================
91
-
92
- /** 默认配置 */
93
- const DEFAULT_OPTIONS: Required<NodeCacheOptions> = {
94
- enableVNodeMap: true,
95
- enableResourceRegistry: true,
96
- };
97
-
98
- // ============================================================
99
- // NodeCache
100
- // ============================================================
101
-
102
- /**
103
- * 节点缓存。
104
- *
105
- * 管理 container → VNode 映射和组件实例 → 资源注册表映射,
106
- * 提供统一的资源清理函数。
107
- *
108
- * @template HN - 宿主节点类型
109
- * @template HE - 宿主元素类型
110
- */
111
- export class NodeCache<HN extends object = object, HE extends HN = HN> {
112
- /** RendererHost 实例 */
113
- private host: RendererHost<HN, HE>;
114
-
115
- /** 配置项 */
116
- private options: Required<NodeCacheOptions>;
117
-
118
- /** container VNode 映射 */
119
- private vnodeMap = new WeakMap<HN, VNode | null>();
120
-
121
- /** 组件实例 → 资源注册表映射 */
122
- private resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();
123
-
124
- /**
125
- * 创建节点缓存实例。
126
- * @param host - RendererHost 实例
127
- * @param options - 可选的配置项
128
- */
129
- constructor(host: RendererHost<HN, HE>, options?: NodeCacheOptions) {
130
- this.host = host;
131
- this.options = { ...DEFAULT_OPTIONS, ...options };
132
- }
133
-
134
- // ==========================================================
135
- // VNode 映射
136
- // ==========================================================
137
-
138
- /**
139
- * 获取 container 对应的 VNode。
140
- * @param container - 宿主容器节点
141
- * @returns VNode 或 null
142
- */
143
- getVNode(container: HN): VNode | null {
144
- if (!this.options.enableVNodeMap) return null;
145
- return this.vnodeMap.get(container) ?? null;
146
- }
147
-
148
- /**
149
- * 设置 container → VNode 映射。
150
- * @param container - 宿主容器节点
151
- * @param vnode - VNode 实例
152
- */
153
- setVNode(container: HN, vnode: VNode | null): void {
154
- if (!this.options.enableVNodeMap) return;
155
- this.vnodeMap.set(container, vnode);
156
- }
157
-
158
- /**
159
- * 删除 container 的 VNode 映射。
160
- * @param container - 宿主容器节点
161
- */
162
- deleteVNode(container: HN): void {
163
- this.vnodeMap.delete(container);
164
- }
165
-
166
- // ==========================================================
167
- // 资源注册表
168
- // ==========================================================
169
-
170
- /**
171
- * 注册事件监听器到组件实例的资源注册表。
172
- *
173
- * @param instance - 组件实例
174
- * @param el - 宿主元素
175
- * @param event - 事件名
176
- * @param handler - 事件处理函数
177
- * @param options - 可选的事件选项
178
- */
179
- registerEventListener(
180
- instance: ComponentInstance,
181
- el: HE,
182
- event: string,
183
- handler: HostEventHandler,
184
- options?: HostEventOptions,
185
- ): void {
186
- if (!this.options.enableResourceRegistry) return;
187
-
188
- const entry = this.getOrCreateResourceEntry(instance);
189
- entry.eventListeners.push({ el, event, handler, options });
190
- }
191
-
192
- /**
193
- * 注册 effect 订阅到组件实例的资源注册表。
194
- *
195
- * @param instance - 组件实例
196
- * @param disposer - effect 的 dispose 回调函数
197
- */
198
- registerEffectSubscription(instance: ComponentInstance, disposer: () => void): void {
199
- if (!this.options.enableResourceRegistry) return;
200
-
201
- const entry = this.getOrCreateResourceEntry(instance);
202
- entry.effectDisposers.push(disposer);
203
- }
204
-
205
- /**
206
- * 注册通用清理钩子到组件实例的资源注册表。
207
- *
208
- * @param instance - 组件实例
209
- * @param cleanup - 清理回调函数
210
- */
211
- registerCleanup(instance: ComponentInstance, cleanup: () => void): void {
212
- if (!this.options.enableResourceRegistry) return;
213
-
214
- const entry = this.getOrCreateResourceEntry(instance);
215
- entry.cleanupHooks.push(cleanup);
216
- }
217
-
218
- // ==========================================================
219
- // 统一清理
220
- // ==========================================================
221
-
222
- /**
223
- * 统一清理组件实例的所有注册资源。
224
- *
225
- * 清理顺序:
226
- * 1. cleanup 钩子(可能依赖 effect 仍活跃)
227
- * 2. effect 订阅(停止响应式追踪)
228
- * 3. 事件监听器(DOM 操作,最后执行)
229
- *
230
- * 每个清理操作均通过 try-catch 保护,单个失败不影响其余流程。
231
- *
232
- * @param instance - 要清理资源的组件实例
233
- */
234
- cleanupComponentResources(instance: ComponentInstance): void {
235
- const entry = this.resourceRegistry.get(instance);
236
- if (!entry) return;
237
-
238
- // 1. 执行 cleanup 钩子
239
- for (const cleanup of entry.cleanupHooks) {
240
- try {
241
- cleanup();
242
- } catch (err) {
243
- if (__DEV__) console.warn('[lytjs/node-cache] Error during cleanup hook:', err);
244
- }
245
- }
246
-
247
- // 2. 清理 effect 订阅
248
- for (const disposer of entry.effectDisposers) {
249
- try {
250
- disposer();
251
- } catch (err) {
252
- if (__DEV__) console.warn('[lytjs/node-cache] Error during effect dispose:', err);
253
- }
254
- }
255
-
256
- // 3. 清理事件监听器
257
- for (const listener of entry.eventListeners) {
258
- try {
259
- // FIX: P2-v11-21 添加类型检查,确保 listener.el 是有效的宿主元素
260
- if (listener.el == null || typeof listener.el !== 'object') {
261
- if (__DEV__) {
262
- console.warn('[lytjs/node-cache] Invalid event listener element, skipping:', listener.el);
263
- }
264
- continue;
265
- }
266
- this.host.removeEventListener(
267
- // FIX: P2-batch2-13 添加运行时类型检查,确保 listener.el 符合 HE 类型约束
268
- listener.el as HE,
269
- listener.event,
270
- listener.handler,
271
- listener.options,
272
- );
273
- } catch (err) {
274
- if (__DEV__) console.warn('[lytjs/node-cache] Error during event listener cleanup:', err);
275
- }
276
- }
277
-
278
- // 删除注册表条目
279
- this.resourceRegistry.delete(instance);
280
- }
281
-
282
- /**
283
- * 清理指定 container 的 VNode 映射。
284
- * @param container - 宿主容器节点
285
- */
286
- cleanupContainer(container: HN): void {
287
- this.vnodeMap.delete(container);
288
- }
289
-
290
- /**
291
- * 销毁缓存,清理所有内部状态。
292
- *
293
- * 注意:不会自动清理所有组件资源,需提前手动调用 cleanupComponentResources。
294
- */
295
- dispose(): void {
296
- this.vnodeMap = new WeakMap<HN, VNode | null>();
297
- this.resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();
298
- }
299
-
300
- // ==========================================================
301
- // 内部方法
302
- // ==========================================================
303
-
304
- /**
305
- * 获取或创建组件实例的资源注册表条目。
306
- */
307
- private getOrCreateResourceEntry(instance: ComponentInstance): ResourceEntry {
308
- let entry = this.resourceRegistry.get(instance);
309
- if (!entry) {
310
- entry = {
311
- eventListeners: [],
312
- effectDisposers: [],
313
- cleanupHooks: [],
314
- };
315
- this.resourceRegistry.set(instance, entry);
316
- }
317
- return entry;
318
- }
319
- }
1
+ // @lytjs/common-node-cache
2
+ // 节点缓存:管理 container → VNode 映射、组件实例 → 资源注册表映射、统一清理函数
3
+
4
+ declare const __DEV__: boolean;
5
+
6
+ import type {
7
+ RendererHost,
8
+ HostEventHandler,
9
+ HostEventOptions,
10
+ HostEvent,
11
+ } from '@lytjs/host-contract';
12
+
13
+ // ============================================================
14
+ // 类型定义
15
+ // ============================================================
16
+
17
+ /**
18
+ * 平台无关的 VNode 接口。
19
+ *
20
+ * L2 层不依赖具体的 VNode 实现,仅使用此最小接口。
21
+ */
22
+ export interface VNode {
23
+ /** 节点类型标识 */
24
+ type: string | symbol | object;
25
+ /** 节点属性 */
26
+ props: Record<string, unknown> | null;
27
+ /** 子节点 */
28
+ children: VNode[] | string | null;
29
+ /** 关联的组件实例 */
30
+ component?: ComponentInstance | null;
31
+ /** 宿主节点引用(渲染后赋值) */
32
+ el?: unknown;
33
+ /** 锚点引用 */
34
+ anchor?: unknown;
35
+ }
36
+
37
+ /**
38
+ * 组件内部实例(最小接口)。
39
+ *
40
+ * L2 层仅需要组件实例的标识能力,用于资源注册表映射。
41
+ */
42
+ export interface ComponentInstance {
43
+ /** 组件唯一标识 */
44
+ uid: number;
45
+ /** 组件类型 */
46
+ type: object;
47
+ /** 是否已卸载 */
48
+ isUnmounted: boolean;
49
+ /** VNode 引用 */
50
+ vnode: VNode;
51
+ /** 父组件实例 */
52
+ parent: ComponentInstance | null;
53
+ /** 子树 VNode */
54
+ subTree: VNode;
55
+ }
56
+
57
+ /**
58
+ * 事件监听器注册条目。
59
+ */
60
+ export interface EventListenerEntry<HE> {
61
+ /** 宿主元素 */
62
+ el: HE;
63
+ /** 事件名 */
64
+ event: string;
65
+ /** 处理函数 */
66
+ handler: (event: HostEvent) => void;
67
+ /** 事件选项 */
68
+ options?: { capture?: boolean; once?: boolean; passive?: boolean };
69
+ }
70
+
71
+ /**
72
+ * 资源注册表条目。
73
+ */
74
+ export interface ResourceEntry {
75
+ /** 事件监听器列表 */
76
+ eventListeners: EventListenerEntry<unknown>[];
77
+ /** effect 订阅 dispose 列表 */
78
+ effectDisposers: Array<() => void>;
79
+ /** 通用清理回调列表 */
80
+ cleanupHooks: Array<() => void>;
81
+ }
82
+
83
+ /**
84
+ * 节点缓存配置项。
85
+ */
86
+ export interface NodeCacheOptions {
87
+ /** 是否启用 VNode 映射(默认 true) */
88
+ enableVNodeMap?: boolean;
89
+ /** 是否启用资源注册表(默认 true) */
90
+ enableResourceRegistry?: boolean;
91
+ }
92
+
93
+ // ============================================================
94
+ // 常量
95
+ // ============================================================
96
+
97
+ /** 默认配置 */
98
+ const DEFAULT_OPTIONS: Required<NodeCacheOptions> = {
99
+ enableVNodeMap: true,
100
+ enableResourceRegistry: true,
101
+ };
102
+
103
+ // ============================================================
104
+ // NodeCache
105
+ // ============================================================
106
+
107
+ /**
108
+ * 节点缓存。
109
+ *
110
+ * 管理 container → VNode 映射和组件实例 → 资源注册表映射,
111
+ * 提供统一的资源清理函数。
112
+ *
113
+ * @template HN - 宿主节点类型
114
+ * @template HE - 宿主元素类型
115
+ */
116
+ export class NodeCache<HN extends object = object, HE extends HN = HN> {
117
+ /** RendererHost 实例 */
118
+ private host: RendererHost<HN, HE>;
119
+
120
+ /** 配置项 */
121
+ private options: Required<NodeCacheOptions>;
122
+
123
+ /** container → VNode 映射 */
124
+ private vnodeMap = new WeakMap<HN, VNode | null>();
125
+
126
+ /** 组件实例 资源注册表映射 */
127
+ private resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();
128
+
129
+ /**
130
+ * 创建节点缓存实例。
131
+ * @param host - RendererHost 实例
132
+ * @param options - 可选的配置项
133
+ */
134
+ constructor(host: RendererHost<HN, HE>, options?: NodeCacheOptions) {
135
+ this.host = host;
136
+ this.options = { ...DEFAULT_OPTIONS, ...options };
137
+ }
138
+
139
+ // ==========================================================
140
+ // VNode 映射
141
+ // ==========================================================
142
+
143
+ /**
144
+ * 获取 container 对应的 VNode。
145
+ * @param container - 宿主容器节点
146
+ * @returns VNode 或 null
147
+ */
148
+ getVNode(container: HN): VNode | null {
149
+ if (!this.options.enableVNodeMap) return null;
150
+ return this.vnodeMap.get(container) ?? null;
151
+ }
152
+
153
+ /**
154
+ * 设置 container → VNode 映射。
155
+ * @param container - 宿主容器节点
156
+ * @param vnode - VNode 实例
157
+ */
158
+ setVNode(container: HN, vnode: VNode | null): void {
159
+ if (!this.options.enableVNodeMap) return;
160
+ this.vnodeMap.set(container, vnode);
161
+ }
162
+
163
+ /**
164
+ * 删除 container 的 VNode 映射。
165
+ * @param container - 宿主容器节点
166
+ */
167
+ deleteVNode(container: HN): void {
168
+ this.vnodeMap.delete(container);
169
+ }
170
+
171
+ // ==========================================================
172
+ // 资源注册表
173
+ // ==========================================================
174
+
175
+ /**
176
+ * 注册事件监听器到组件实例的资源注册表。
177
+ *
178
+ * @param instance - 组件实例
179
+ * @param el - 宿主元素
180
+ * @param event - 事件名
181
+ * @param handler - 事件处理函数
182
+ * @param options - 可选的事件选项
183
+ */
184
+ registerEventListener(
185
+ instance: ComponentInstance,
186
+ el: HE,
187
+ event: string,
188
+ handler: HostEventHandler,
189
+ options?: HostEventOptions,
190
+ ): void {
191
+ if (!this.options.enableResourceRegistry) return;
192
+
193
+ const entry = this.getOrCreateResourceEntry(instance);
194
+ entry.eventListeners.push({ el, event, handler, options });
195
+ }
196
+
197
+ /**
198
+ * 注册 effect 订阅到组件实例的资源注册表。
199
+ *
200
+ * @param instance - 组件实例
201
+ * @param disposer - effect 的 dispose 回调函数
202
+ */
203
+ registerEffectSubscription(instance: ComponentInstance, disposer: () => void): void {
204
+ if (!this.options.enableResourceRegistry) return;
205
+
206
+ const entry = this.getOrCreateResourceEntry(instance);
207
+ entry.effectDisposers.push(disposer);
208
+ }
209
+
210
+ /**
211
+ * 注册通用清理钩子到组件实例的资源注册表。
212
+ *
213
+ * @param instance - 组件实例
214
+ * @param cleanup - 清理回调函数
215
+ */
216
+ registerCleanup(instance: ComponentInstance, cleanup: () => void): void {
217
+ if (!this.options.enableResourceRegistry) return;
218
+
219
+ const entry = this.getOrCreateResourceEntry(instance);
220
+ entry.cleanupHooks.push(cleanup);
221
+ }
222
+
223
+ // ==========================================================
224
+ // 统一清理
225
+ // ==========================================================
226
+
227
+ /**
228
+ * 统一清理组件实例的所有注册资源。
229
+ *
230
+ * 清理顺序:
231
+ * 1. cleanup 钩子(可能依赖 effect 仍活跃)
232
+ * 2. effect 订阅(停止响应式追踪)
233
+ * 3. 事件监听器(DOM 操作,最后执行)
234
+ *
235
+ * 每个清理操作均通过 try-catch 保护,单个失败不影响其余流程。
236
+ *
237
+ * @param instance - 要清理资源的组件实例
238
+ */
239
+ cleanupComponentResources(instance: ComponentInstance): void {
240
+ const entry = this.resourceRegistry.get(instance);
241
+ if (!entry) return;
242
+
243
+ // 1. 执行 cleanup 钩子
244
+ for (const cleanup of entry.cleanupHooks) {
245
+ try {
246
+ cleanup();
247
+ } catch (err) {
248
+ if (__DEV__) console.warn('[lytjs/node-cache] Error during cleanup hook:', err);
249
+ }
250
+ }
251
+
252
+ // 2. 清理 effect 订阅
253
+ for (const disposer of entry.effectDisposers) {
254
+ try {
255
+ disposer();
256
+ } catch (err) {
257
+ if (__DEV__) console.warn('[lytjs/node-cache] Error during effect dispose:', err);
258
+ }
259
+ }
260
+
261
+ // 3. 清理事件监听器
262
+ for (const listener of entry.eventListeners) {
263
+ try {
264
+ // FIX: P2-v11-21 添加类型检查,确保 listener.el 是有效的宿主元素
265
+ if (listener.el == null || typeof listener.el !== 'object') {
266
+ if (__DEV__) {
267
+ console.warn(
268
+ '[lytjs/node-cache] Invalid event listener element, skipping:',
269
+ listener.el,
270
+ );
271
+ }
272
+ continue;
273
+ }
274
+ this.host.removeEventListener(
275
+ // FIX: P2-batch2-13 添加运行时类型检查,确保 listener.el 符合 HE 类型约束
276
+ listener.el as HE,
277
+ listener.event,
278
+ listener.handler,
279
+ listener.options,
280
+ );
281
+ } catch (err) {
282
+ if (__DEV__) console.warn('[lytjs/node-cache] Error during event listener cleanup:', err);
283
+ }
284
+ }
285
+
286
+ // 删除注册表条目
287
+ this.resourceRegistry.delete(instance);
288
+ }
289
+
290
+ /**
291
+ * 清理指定 container 的 VNode 映射。
292
+ * @param container - 宿主容器节点
293
+ */
294
+ cleanupContainer(container: HN): void {
295
+ this.vnodeMap.delete(container);
296
+ }
297
+
298
+ /**
299
+ * 销毁缓存,清理所有内部状态。
300
+ *
301
+ * 注意:不会自动清理所有组件资源,需提前手动调用 cleanupComponentResources。
302
+ */
303
+ dispose(): void {
304
+ this.vnodeMap = new WeakMap<HN, VNode | null>();
305
+ this.resourceRegistry = new WeakMap<ComponentInstance, ResourceEntry>();
306
+ }
307
+
308
+ // ==========================================================
309
+ // 内部方法
310
+ // ==========================================================
311
+
312
+ /**
313
+ * 获取或创建组件实例的资源注册表条目。
314
+ */
315
+ private getOrCreateResourceEntry(instance: ComponentInstance): ResourceEntry {
316
+ let entry = this.resourceRegistry.get(instance);
317
+ if (!entry) {
318
+ entry = {
319
+ eventListeners: [],
320
+ effectDisposers: [],
321
+ cleanupHooks: [],
322
+ };
323
+ this.resourceRegistry.set(instance, entry);
324
+ }
325
+ return entry;
326
+ }
327
+ }