Comfy-Org / Comfy-Org/ComfyUI_frontend
Extensions built with Vue/React templates can't participate in frontend reactivity (dual Vue runtime problem)
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 699
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 490
Description
### 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:
1. **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.
2. **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.
3. **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_
Contributor guide
Assessment
This issue has not been assessed yet.