Skip to content

## Circular Dependency Issue with verbatimModuleSyntax #5545

@snomiao

Description

@snomiao

Circular Dependency Issue with verbatimModuleSyntax

We've encountered a blocking issue when enabling TypeScript's verbatimModuleSyntax option. The strict import/export requirements expose circular dependencies that were previously hidden:

The Problem

With verbatimModuleSyntax enabled:

  1. Type imports must use import type { ... } syntax
  2. Values and types cannot be mixed in the same import statement
  3. This stricter separation reveals circular dependencies between modules

Actual Runtime Failure

When running tests with Vitest, we encounter runtime errors that demonstrate the circular dependency problem:

FAIL  tests-ui/tests/store/bottomPanelStore.test.ts
TypeError: Class extends value undefined is not a constructor or null
 ❯ src/lib/litegraph/src/subgraph/EmptySubgraphInput.ts:14:41
     12|  * A virtual slot that simply creates a new input slot when connected …
     13|  */
     14| export class EmptySubgraphInput extends SubgraphInput {
       |                                         ^
     15|   declare parent: SubgraphInputNode
     16| 
 ❯ src/lib/litegraph/src/canvas/LinkConnector.ts:4:31

This error occurs because SubgraphInput is undefined at the time EmptySubgraphInput tries to extend it, indicating a circular dependency in the module initialization order.

The Circular Import Chain

Starting from EmptySubgraphInput.ts:

  1. EmptySubgraphInput.ts

    • Line 8: import { SubgraphInput } from './SubgraphInput'
    • Line 14: export class EmptySubgraphInput extends SubgraphInput
  2. SubgraphInput.ts

    • Line 16: import type { SubgraphInputNode } from './SubgraphInputNode'
    • Line 18: import { SubgraphSlot } from './SubgraphSlotBase'
    • Line 32: export class SubgraphInput extends SubgraphSlot
  3. SubgraphInputNode.ts

    • Line 5: import type { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
    • Line 19: import { EmptySubgraphInput } from './EmptySubgraphInput'
    • Line 30: Creates EmptySubgraphInput instance
  4. LinkConnector.ts

    • Line 20: import { EmptySubgraphInput } from '@/lib/litegraph/src/subgraph/EmptySubgraphInput'
    • Line 24: import { SubgraphInputNode } from '@/lib/litegraph/src/subgraph/SubgraphInputNode'

The Cycle:

EmptySubgraphInput → SubgraphInput → SubgraphInputNode → EmptySubgraphInput
                                    ↓                    ↑
                                LinkConnector ──────────┘

When modules are loaded:

  1. LinkConnector imports EmptySubgraphInput
  2. EmptySubgraphInput tries to extend SubgraphInput
  3. SubgraphInput needs SubgraphInputNode (for type)
  4. SubgraphInputNode needs EmptySubgraphInput (creates instance)
  5. But EmptySubgraphInput is still being defined, causing SubgraphInput to be undefined

Additional Circular Dependencies Found

  1. LiteGraph Internal Structure

    • Subgraph class extends LGraph (in LGraph.ts)
    • Multiple files import both Subgraph and components that depend on it
    • Temporary workaround exists: Subgraph.ts re-exports from LGraph.ts
  2. ComfyApp ↔ LiteGraph

    • src/scripts/app.ts exports ComfyApp class
    • Various files import from both @comfyorg/litegraph and ComfyApp
    • LiteGraph extensions depend on ComfyApp types
    • ComfyApp depends on LiteGraph types
  3. Component Registry Circular References

    • Component index files export types
    • Components import from their own index for type definitions
    • Creates self-referential loops

Why This Matters

  • Build failures: TypeScript cannot resolve the import order
  • Runtime crashes: Even if TypeScript compiles, the circular dependencies cause runtime initialization failures
  • Test failures: Vitest cannot properly initialize modules with circular dependencies
  • Maintenance burden: The codebase architecture needs refactoring to properly separate concerns

Recommended Solution

Before enabling verbatimModuleSyntax, we should:

  1. Break the SubgraphInput cycle:

    • Option A: Extract EmptySubgraphInput to not extend SubgraphInput directly
    • Option B: Use lazy initialization for the emptySlot in SubgraphInputNode
    • Option C: Restructure the inheritance hierarchy to avoid the cycle
  2. Fix LiteGraph internal structure:

    • Resolve circular dependencies within the LiteGraph library
    • Ensure proper initialization order for class hierarchies
    • Separate type definitions from implementations
  3. Refactor module structure to eliminate circular dependencies:

    • Extract shared types to separate type-only modules
    • Use dependency injection patterns where appropriate
    • Consider facade patterns for complex module interactions
  4. Create abstraction layers:

    • Define interfaces in separate files from implementations
    • Use type-only exports for shared contracts
    • Implement proper layering (core → features → UI)
  5. Gradual migration:

    • Fix circular dependencies module by module
    • Enable verbatimModuleSyntax per module once clean
    • Eventually enable project-wide

This refactoring would improve the codebase architecture beyond just TypeScript compliance - it would make the code more maintainable and testable.

Current Status

The branch has partial fixes for type imports, but the circular dependency issue blocks full implementation. The runtime errors in tests confirm this is not just a TypeScript compilation issue but a fundamental module structure problem that needs architectural refactoring.

Originally posted by @snomiao in #5533 (comment)

┆Issue is synchronized with this Notion page by Unito

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions