fix: resolve 3D nodes missing after page refresh (#8711)

## Summary
Await all registerNodeDef calls in registerNodesFromDefs to prevent race
condition where lazy-loaded 3D node types (Load3D, Preview3D, SaveGLB)
are not registered in LiteGraph before workflow loading.

Replay lazily loaded extensions' beforeRegisterNodeDef hooks so that
input type modifications (e.g. Preview3D → PREVIEW_3D) are applied
correctly despite the extensions being registered mid-invocation.

Fixes the issue introduced by code splitting (#8542) where THREE.js lazy
import caused node registration to complete after workflow load.

## Screenshots (if applicable)
before

https://github.com/user-attachments/assets/370545dc-4081-4164-83ed-331a092fc690

after

https://github.com/user-attachments/assets/bf9dc887-0076-41fe-93ad-ab0bb984c5ce
This commit is contained in:
Terry Jia
2026-02-06 22:05:44 -05:00
committed by GitHub
parent d9ce4ff5e0
commit a2c8324c0a
2 changed files with 27 additions and 10 deletions

View File

@@ -8,26 +8,36 @@
*/
import { useExtensionService } from '@/services/extensionService'
import { app } from '@/scripts/app'
import { useExtensionStore } from '@/stores/extensionStore'
import type { ComfyExtension } from '@/types/comfy'
const LOAD3D_NODE_TYPES = new Set(['Load3D', 'Preview3D', 'SaveGLB'])
let load3dExtensionsLoaded = false
let load3dExtensionsLoading: Promise<void> | null = null
let load3dExtensionsLoading: Promise<ComfyExtension[]> | null = null
/**
* Dynamically load the 3D extensions (and THREE.js) on demand
* Dynamically load the 3D extensions (and THREE.js) on demand.
* Returns the list of newly registered extensions so the caller can
* replay hooks that they missed.
*/
async function loadLoad3dExtensions(): Promise<void> {
if (load3dExtensionsLoaded) return
async function loadLoad3dExtensions(): Promise<ComfyExtension[]> {
if (load3dExtensionsLoaded) return []
if (load3dExtensionsLoading) {
return load3dExtensionsLoading
}
load3dExtensionsLoading = (async () => {
const before = new Set(useExtensionStore().enabledExtensions)
// Import both extensions - they will self-register via useExtensionService()
await Promise.all([import('./load3d'), import('./saveMesh')])
load3dExtensionsLoaded = true
return useExtensionStore().enabledExtensions.filter(
(ext) => !before.has(ext)
)
})()
return load3dExtensionsLoading
@@ -44,10 +54,15 @@ function isLoad3dNodeType(nodeTypeName: string): boolean {
useExtensionService().registerExtension({
name: 'Comfy.Load3DLazy',
async beforeRegisterNodeDef(_nodeType, nodeData) {
// When a 3D node type is being registered, load the 3D extensions
async beforeRegisterNodeDef(nodeType, nodeData) {
if (isLoad3dNodeType(nodeData.name)) {
await loadLoad3dExtensions()
// Load the 3D extensions and replay their beforeRegisterNodeDef hooks,
// since invokeExtensionsAsync already captured the extensions snapshot
// before these new extensions were registered.
const newExtensions = await loadLoad3dExtensions()
for (const ext of newExtensions) {
await ext.beforeRegisterNodeDef?.(nodeType, nodeData, app)
}
}
}
})

View File

@@ -985,9 +985,11 @@ export class ComfyApp {
await useExtensionService().invokeExtensionsAsync('addCustomNodeDefs', defs)
// Register a node for each definition
for (const nodeId in defs) {
this.registerNodeDef(nodeId, defs[nodeId])
}
await Promise.all(
Object.keys(defs).map((nodeId) =>
this.registerNodeDef(nodeId, defs[nodeId])
)
)
}
loadTemplateData(templateData: {