What problem are you experiencing?
The problem
There's a fundamental catch-22 with the current extension architecture for Nodes 2.0.
The reason you'd want to build an extension with Vue (using the official extension templates) is to get reactivity — reactive state driving UI, computed properties, watchers, all of it. If you just need to render some HTML with click handlers, plain JS and DOM widgets already do that without any build step.
But when you actually use the Vue extension template and compile with Vite, your extension bundles its own copy of Vue's reactivity system. The frontend also bundles its own Vue internally -- it's not externalized or exposed anywhere. So now there are two completely independent Vue runtimes in the browser. Your extension's ref() and computed() are tracked by your Vue. The frontend's toValue() and computed() operate on a completely separate reactivity system. They can't see each other.
Which means the main reason to use Vue in an extension -- reactivity that integrates with the frontend -- is exactly the thing that doesn't work.
What this looks like in practice
I hit this with node.badges. The badges API accepts getter functions, and usePartitionedBadges.ts evaluates them via toValue() inside a computed. If I create a ref in my extension and return a getter that reads it, updating that ref does nothing -- the frontend's computed never re-evaluates because the dependency was registered with my Vue, not theirs. The badges only visually update when something else forces the frontend to tear down and recreate the node component entirely (like entering/exiting a subgraph), at which point toValue() gets called fresh and happens to read the current value. But that's a one-shot read, not a live reactive subscription.
This would affect any extension feature that tries to feed reactive data into the frontend -- not just badges, but anything where the frontend uses toValue(), watch(), or computed() on data that an extension provides.
The scaling concern
Right now most custom nodes ship plain JS without a build step, so this hasn't been widely felt yet. But the official extension templates exist to encourage Vue/React-based extensions going forward. If the ecosystem moves in that direction, every node pack that uses a build step and imports vue will bundle another copy of Vue's reactivity core. Ten extension packs = ten additional Vue runtimes, none of which can participate in the frontend's dependency tracking or each other's.
The bundle size per copy isn't huge (~15-20KB), but the semantic problem is the real issue -- you end up with an ecosystem of extensions that are all "reactive" internally but completely opaque to the frontend that's supposed to render them.
What the templates are actually useful for right now
As things stand, the Vue extension template is only useful for building self-contained UI that mounts into its own container (a DOM widget, a sidebar panel, a bottom tab) and talks to the backend via fetch/websocket. That's a valid use case, but it's essentially just "render your own island." It's not "integrate with the frontend's reactive graph," which is what most extension authors would expect when they're told to build with Vue.
What I've tried
- Looked for a way to import Vue from the frontend's instance -- it's not on
window, not on window.comfyAPI, and not in any import map.
- Checked
comfyAPIPlugin.ts -- it shims exported functions/classes from src/scripts/ and src/extensions/core/ onto window.comfyAPI, but Vue's own exports (ref, computed, toValue, etc.) are just internal imports and never get shimmed.
- Array mutation on
node.badges (splice/replace) -- this can trigger the spread in usePartitionedBadges.ts but it's hacky and fragile, not a real solution.
- Falling back to plain JS without a build step works for simple cases, but that just means giving up on Vue entirely, which circles back to "why do the templates exist."
Possible solutions
Some way for extensions to access the frontend's Vue reactivity primitives so everything shares a single reactive system:
-
Expose Vue's core reactivity exports on window.comfyAPI -- something like window.comfyAPI.vue = { ref, computed, toValue, watch, reactive, ... }. Extensions would import from there instead of bundling their own Vue, and everything would share a single reactivity system.
-
Externalize vue in the frontend's Vite config -- mark vue as an external and expose the frontend's Vue instance as a global (e.g., window.Vue). Extensions would configure their own Vite builds to treat vue as external too, and both sides would use the same runtime. This is the standard pattern for plugin architectures that need shared dependencies.
-
Provide a reactivity bridge utility on window.comfyAPI -- something like createReactiveGetter(updateFn) that returns a getter the frontend can track, without extensions needing direct Vue access. More conservative, but might cover the common cases.
Option 2 is probably the cleanest -- it's the standard approach for this kind of plugin architecture and wouldn't require extensions to change how they write Vue code, just how they configure their builds. Option 1 is similar but more manual. Option 3 is a smaller surface area but might not cover all the ways extensions need to integrate.
Context
I'm converting several custom nodes to support Nodes 2.0 alongside legacy LiteGraph. The extension templates are great for building the UI components themselves, but as soon as the extension needs to provide data that the frontend consumes reactively, things break silently. No errors, no warnings -- stuff just doesn't update, and you have to dig into the frontend source to figure out why. Figured it was worth flagging now before more extension authors run into this as Nodes 2.0 adoption grows.
When does this problem occur?
n/a
How often do you encounter this problem?
Multiple times per day
How much does this problem affect your workflow?
Blocks me from completing tasks
Current workarounds
No response
Ideas for solutions (Optional)
No response
Additional context
No response
What problem are you experiencing?
The problem
There's a fundamental catch-22 with the current extension architecture for Nodes 2.0.
The reason you'd want to build an extension with Vue (using the official extension templates) is to get reactivity — reactive state driving UI, computed properties, watchers, all of it. If you just need to render some HTML with click handlers, plain JS and DOM widgets already do that without any build step.
But when you actually use the Vue extension template and compile with Vite, your extension bundles its own copy of Vue's reactivity system. The frontend also bundles its own Vue internally -- it's not externalized or exposed anywhere. So now there are two completely independent Vue runtimes in the browser. Your extension's
ref()andcomputed()are tracked by your Vue. The frontend'stoValue()andcomputed()operate on a completely separate reactivity system. They can't see each other.Which means the main reason to use Vue in an extension -- reactivity that integrates with the frontend -- is exactly the thing that doesn't work.
What this looks like in practice
I hit this with
node.badges. The badges API accepts getter functions, andusePartitionedBadges.tsevaluates them viatoValue()inside acomputed. If I create arefin my extension and return a getter that reads it, updating that ref does nothing -- the frontend'scomputednever re-evaluates because the dependency was registered with my Vue, not theirs. The badges only visually update when something else forces the frontend to tear down and recreate the node component entirely (like entering/exiting a subgraph), at which pointtoValue()gets called fresh and happens to read the current value. But that's a one-shot read, not a live reactive subscription.This would affect any extension feature that tries to feed reactive data into the frontend -- not just badges, but anything where the frontend uses
toValue(),watch(), orcomputed()on data that an extension provides.The scaling concern
Right now most custom nodes ship plain JS without a build step, so this hasn't been widely felt yet. But the official extension templates exist to encourage Vue/React-based extensions going forward. If the ecosystem moves in that direction, every node pack that uses a build step and imports
vuewill bundle another copy of Vue's reactivity core. Ten extension packs = ten additional Vue runtimes, none of which can participate in the frontend's dependency tracking or each other's.The bundle size per copy isn't huge (~15-20KB), but the semantic problem is the real issue -- you end up with an ecosystem of extensions that are all "reactive" internally but completely opaque to the frontend that's supposed to render them.
What the templates are actually useful for right now
As things stand, the Vue extension template is only useful for building self-contained UI that mounts into its own container (a DOM widget, a sidebar panel, a bottom tab) and talks to the backend via fetch/websocket. That's a valid use case, but it's essentially just "render your own island." It's not "integrate with the frontend's reactive graph," which is what most extension authors would expect when they're told to build with Vue.
What I've tried
window, not onwindow.comfyAPI, and not in any import map.comfyAPIPlugin.ts-- it shims exported functions/classes fromsrc/scripts/andsrc/extensions/core/ontowindow.comfyAPI, but Vue's own exports (ref,computed,toValue, etc.) are just internal imports and never get shimmed.node.badges(splice/replace) -- this can trigger the spread inusePartitionedBadges.tsbut it's hacky and fragile, not a real solution.Possible solutions
Some way for extensions to access the frontend's Vue reactivity primitives so everything shares a single reactive system:
Expose Vue's core reactivity exports on
window.comfyAPI-- something likewindow.comfyAPI.vue = { ref, computed, toValue, watch, reactive, ... }. Extensions would import from there instead of bundling their own Vue, and everything would share a single reactivity system.Externalize
vuein the frontend's Vite config -- markvueas an external and expose the frontend's Vue instance as a global (e.g.,window.Vue). Extensions would configure their own Vite builds to treatvueas external too, and both sides would use the same runtime. This is the standard pattern for plugin architectures that need shared dependencies.Provide a reactivity bridge utility on
window.comfyAPI-- something likecreateReactiveGetter(updateFn)that returns a getter the frontend can track, without extensions needing direct Vue access. More conservative, but might cover the common cases.Option 2 is probably the cleanest -- it's the standard approach for this kind of plugin architecture and wouldn't require extensions to change how they write Vue code, just how they configure their builds. Option 1 is similar but more manual. Option 3 is a smaller surface area but might not cover all the ways extensions need to integrate.
Context
I'm converting several custom nodes to support Nodes 2.0 alongside legacy LiteGraph. The extension templates are great for building the UI components themselves, but as soon as the extension needs to provide data that the frontend consumes reactively, things break silently. No errors, no warnings -- stuff just doesn't update, and you have to dig into the frontend source to figure out why. Figured it was worth flagging now before more extension authors run into this as Nodes 2.0 adoption grows.
When does this problem occur?
n/a
How often do you encounter this problem?
Multiple times per day
How much does this problem affect your workflow?
Blocks me from completing tasks
Current workarounds
No response
Ideas for solutions (Optional)
No response
Additional context
No response