From 06ada7a237456e443dbeb93ffc0df82019a18672 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Wed, 24 Dec 2025 04:04:59 +0100 Subject: [PATCH 1/2] docs: add Vue widgets documentation for custom nodes --- custom-nodes/js/javascript_overview.mdx | 6 +- custom-nodes/js/javascript_sidebar_tabs.mdx | 2 + custom-nodes/js/vue_widgets.mdx | 373 ++++++++++++++++++++ custom-nodes/overview.mdx | 10 +- docs.json | 1 + 5 files changed, 386 insertions(+), 6 deletions(-) create mode 100644 custom-nodes/js/vue_widgets.mdx diff --git a/custom-nodes/js/javascript_overview.mdx b/custom-nodes/js/javascript_overview.mdx index aaa561e4..65cf07f2 100644 --- a/custom-nodes/js/javascript_overview.mdx +++ b/custom-nodes/js/javascript_overview.mdx @@ -10,11 +10,13 @@ Comfy can be modified through an extensions mechanism. To add an extension you n - Place one or more `.js` files into that directory, - Use `app.registerExtension` to register your extension. -These three steps are below. Once you know how to add an extension, look -through the [hooks](/custom-nodes/js/javascript_hooks) available to get your code called, +These three steps are below. Once you know how to add an extension, look +through the [hooks](/custom-nodes/js/javascript_hooks) available to get your code called, a description of various [Comfy objects](/custom-nodes/js/javascript_objects_and_hijacking) you might need, or jump straight to some [example code snippets](/custom-nodes/js/javascript_examples). +For building custom node widgets with Vue components, see [Nodes 2.0 Widgets](/custom-nodes/js/vue_widgets). + ### Exporting `WEB_DIRECTORY` The Comfy web client can be extended by creating a subdirectory in your custom node directory, conventionally called `js`, and diff --git a/custom-nodes/js/javascript_sidebar_tabs.mdx b/custom-nodes/js/javascript_sidebar_tabs.mdx index a393df83..8b8fb06f 100644 --- a/custom-nodes/js/javascript_sidebar_tabs.mdx +++ b/custom-nodes/js/javascript_sidebar_tabs.mdx @@ -131,6 +131,8 @@ app.extensionManager.registerSidebarTab({ For a real-world example of a React application integrated as a sidebar tab, check out the [ComfyUI-Copilot project on GitHub](https://github.com/AIDC-AI/ComfyUI-Copilot). +For creating custom node widgets with Vue components, see [Nodes 2.0 Widgets](/custom-nodes/js/vue_widgets). + ## Dynamic Content Updates You can update sidebar content in response to graph changes: diff --git a/custom-nodes/js/vue_widgets.mdx b/custom-nodes/js/vue_widgets.mdx new file mode 100644 index 00000000..b1097382 --- /dev/null +++ b/custom-nodes/js/vue_widgets.mdx @@ -0,0 +1,373 @@ +--- +title: "Nodes 2.0 Widgets" +description: "Create custom node widgets using Vue Single File Components" +--- + +Nodes 2.0 widgets allow you to create rich, interactive node widgets using Vue 3 Single File Components (SFCs). This is the recommended approach for building custom widgets that need complex UI interactions, state management, or styling. + +## Overview + +You can create Vue-based widgets using the `getCustomVueWidgets()` hook. ComfyUI exposes Vue globally as `window.Vue`, so your extension uses the same Vue instance as the main app (smaller bundle size). + +## Project Structure + +``` +test_vue_widget_node/ +├── __init__.py # Python node definitions +└── web/ + ├── src/ + │ ├── extension.js # Entry point - registers extension + │ ├── styles.css # Tailwind directives + │ └── WidgetStarRating.vue + ├── dist/ + │ └── extension.js # Built output (loaded by ComfyUI) + ├── package.json + ├── vite.config.ts + ├── tailwind.config.js + └── postcss.config.js +``` + +## Complete Example + +### package.json + +```json +{ + "name": "test-vue-widget-node", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite build --watch" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "rollup-plugin-external-globals": "^0.13.0", + "tailwindcss": "^3.4.17", + "vite": "^6.0.0", + "vite-plugin-css-injected-by-js": "^3.5.2", + "vue": "^3.5.0" + } +} +``` + +### vite.config.ts + +```typescript +import vue from '@vitejs/plugin-vue' +import externalGlobals from 'rollup-plugin-external-globals' +import { defineConfig } from 'vite' +import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' + +export default defineConfig({ + plugins: [vue(), cssInjectedByJsPlugin()], + build: { + lib: { + entry: 'src/extension.js', + name: 'TestVueWidgets', + fileName: () => 'extension.js', + formats: ['es'] + }, + outDir: 'dist', + emptyOutDir: true, + cssCodeSplit: false, + rollupOptions: { + external: ['vue', /^\.\.\/.*\.js$/], + plugins: [externalGlobals({ vue: 'Vue' })] + } + } +}) +``` + +Key configuration points: + +| Option | Purpose | +|--------|---------| +| `cssInjectedByJsPlugin()` | Inlines CSS into the JS bundle | +| `external: ['vue', ...]` | Don't bundle Vue, use global | +| `externalGlobals({ vue: 'Vue' })` | Map Vue imports to `window.Vue` | + +### extension.js + +```javascript +/** + * Test Vue Widget Extension + * + * Demonstrates how to register custom Vue widgets for ComfyUI nodes. + * Widgets are built from .vue SFC files using Vite. + */ + +import './styles.css' +import { app } from '../../scripts/app.js' + +// Import Vue components +import WidgetStarRating from './WidgetStarRating.vue' + +// Register the extension +app.registerExtension({ + name: 'TestVueWidgets', + + getCustomVueWidgets() { + return { + star_rating: { + component: WidgetStarRating, + aliases: ['STAR_RATING'] + } + } + } +}) +``` + +### WidgetStarRating.vue + +```vue + + + +``` + +### __init__.py + +```python +""" +Test Vue Widget Node + +A test custom node that demonstrates the Vue widget registration feature. +This node uses a custom STAR_RATING widget type that is rendered by a Vue component. +""" + + +class TestVueWidgetNode: + """A test node with a custom Vue-rendered star rating widget.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "rating": ("INT", { + "default": 3, + "min": 0, + "max": 5, + "display": "star_rating", # Custom widget type hint + }), + "text_input": ("STRING", { + "default": "Hello Vue Widgets!", + "multiline": False, + }), + }, + } + + RETURN_TYPES = ("INT", "STRING") + RETURN_NAMES = ("rating_value", "text_value") + FUNCTION = "process" + CATEGORY = "Testing/Vue Widgets" + DESCRIPTION = "Test node for Vue widget registration feature" + + def process(self, rating: int, text_input: str): + print(f"[TestVueWidgetNode] Rating: {rating}, Text: {text_input}") + return (rating, text_input) + + +NODE_CLASS_MAPPINGS = { + "TestVueWidgetNode": TestVueWidgetNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "TestVueWidgetNode": "Test Vue Widget (Star Rating)", +} + +WEB_DIRECTORY = "./web/dist" +__all__ = ['NODE_CLASS_MAPPINGS', 'WEB_DIRECTORY'] +``` + +## Using Tailwind CSS + +### tailwind.config.js + +```javascript +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{vue,js,ts}'], + corePlugins: { + preflight: false // Disable base reset to avoid affecting other parts of the app + }, + theme: { + extend: {} + }, + plugins: [] +} +``` + +Always set `preflight: false` to prevent Tailwind's CSS reset from affecting ComfyUI's styles. + +### postcss.config.js + +```javascript +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} +``` + +### styles.css + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +### Handling Positioning + +Without Tailwind's preflight, some utility classes like `absolute`, `translate-x-1/2` may not work as expected. Use inline styles for critical positioning: + +```vue +
+ Centered content +
+``` + +## Widget Component API + +Your Vue component receives these props: + +| Prop | Type | Description | +|------|------|-------------| +| `widget` | `Object` | Widget configuration object | +| `widget.name` | `string` | Widget name from Python input | +| `widget.label` | `string` | Display label | +| `widget.options` | `Object` | Options from Python (`min`, `max`, `step`, etc.) | +| `widget.options.disabled` | `boolean` | Whether the widget is disabled | + +Use `defineModel()` for two-way value binding: + +```vue + +``` + +## Build and Test + +```bash +cd web +npm install +npm run build +``` + +Restart ComfyUI to load your extension. + +## Development Workflow + +Run the watcher during development: + +```bash +npm run dev +``` + +This rebuilds `dist/extension.js` on every file change. Refresh ComfyUI to see updates. + +## Common Pitfalls + +### Failed to resolve module specifier "vue" + +The browser can't resolve bare `"vue"` imports. + +**Solution:** Use `rollup-plugin-external-globals` to map Vue imports to `window.Vue`: + +```typescript +import externalGlobals from 'rollup-plugin-external-globals' + +rollupOptions: { + external: ['vue', /^\.\.\/.*\.js$/], + plugins: [externalGlobals({ vue: 'Vue' })] +} +``` + +### CSS Not Loading + +ComfyUI only loads JS files. CSS must be inlined into the bundle. + +**Solution:** Use `vite-plugin-css-injected-by-js`. + +### Styles Affecting Other UI + +Tailwind's preflight resets global styles, breaking ComfyUI. + +**Solution:** Set `preflight: false` in `tailwind.config.js`. + +### Accessing ComfyUI's App + +Import from the relative path to `scripts/app.js`: + +```javascript +import { app } from '../../scripts/app.js' +``` + +Configure Vite to preserve this import: + +```typescript +rollupOptions: { + external: [/^\.\.\/.*\.js$/] +} +``` + +## Examples + +- [ComfyUI_frontend_vue_basic](https://github.com/jtydhr88/ComfyUI_frontend_vue_basic) - Full Vue extension with PrimeVue, i18n, drawing board widget diff --git a/custom-nodes/overview.mdx b/custom-nodes/overview.mdx index 6f5d83b6..f4f6a7a0 100644 --- a/custom-nodes/overview.mdx +++ b/custom-nodes/overview.mdx @@ -11,10 +11,12 @@ simple node that takes an image and inverts it. ![Unique Images Node](/images/invert_image_node.png) -Custom node examples: -- [cookiecutter-comfy-extension](https://github.com/Comfy-Org/cookiecutter-comfy-extension) -- [ComfyUI-React-Extension-Template](https://github.com/Comfy-Org/ComfyUI-React-Extension-Template) -- [ComfyUI_frontend_vue_basic](https://github.com/jtydhr88/ComfyUI_frontend_vue_basic) +Custom node examples: +- [cookiecutter-comfy-extension](https://github.com/Comfy-Org/cookiecutter-comfy-extension) - Python-only template +- [ComfyUI-React-Extension-Template](https://github.com/Comfy-Org/ComfyUI-React-Extension-Template) - React UI extension +- [ComfyUI_frontend_vue_basic](https://github.com/jtydhr88/ComfyUI_frontend_vue_basic) - Vue UI extension + +For creating custom node widgets with Vue, see [Nodes 2.0 Widgets](/custom-nodes/js/vue_widgets). ## Client-Server Model diff --git a/docs.json b/docs.json index c8b78fc0..288e1f37 100644 --- a/docs.json +++ b/docs.json @@ -566,6 +566,7 @@ "custom-nodes/js/javascript_about_panel_badges", "custom-nodes/js/javascript_bottom_panel_tabs", "custom-nodes/js/javascript_sidebar_tabs", + "custom-nodes/js/vue_widgets", "custom-nodes/js/javascript_selection_toolbox", "custom-nodes/js/javascript_commands_keybindings", "custom-nodes/js/javascript_topbar_menu", From ec8fb252e8fb22a9523c0d1d49340053c1a142b6 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Wed, 24 Dec 2025 04:16:35 +0100 Subject: [PATCH 2/2] docs: add sample repo link to Vue widgets documentation --- custom-nodes/js/vue_widgets.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/custom-nodes/js/vue_widgets.mdx b/custom-nodes/js/vue_widgets.mdx index b1097382..490d9c5e 100644 --- a/custom-nodes/js/vue_widgets.mdx +++ b/custom-nodes/js/vue_widgets.mdx @@ -370,4 +370,5 @@ rollupOptions: { ## Examples +- [ComfyUI_vue_widget_example](https://github.com/Myestery/ComfyUI_vue_widget_example) - Minimal example with star rating widget (source for this documentation) - [ComfyUI_frontend_vue_basic](https://github.com/jtydhr88/ComfyUI_frontend_vue_basic) - Full Vue extension with PrimeVue, i18n, drawing board widget