Comfy-Org / Comfy-Org/ComfyUI_frontend
RFC: Standardize how custom node extensions declare and load JS library dependencies
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 705
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 512
Description
## Problem
Custom node extensions that need third-party JS libraries (markdown parsers, visualization libraries, code editors) cannot place them in `WEB_DIRECTORY` because the frontend unconditionally `import()`s every `.js` file found there. This forces node packs to use workarounds like registering custom aiohttp static routes (`kjweb_async`, `mtb_async`), which break on Cloud.
### Root Cause
1. Backend (`server.py`): The `/extensions` endpoint uses `glob("**/*.js")` to find every JS file in all `WEB_DIRECTORY` paths
2. Frontend (`extensionService.ts`): `loadExtensions()` calls `import()` on every URL returned — no filtering, no manifest, no opt-in
3. Vendored libraries (e.g., `marked.min.js`) get executed as ES modules on every page load, causing errors and global pollution
### Current Workarounds
| Pack | Workaround | How |
|------|-----------|-----|
| KJNodes | `kjweb_async/` folder | Custom `web.static("/kjweb_async", ...)` aiohttp route |
| comfy_mtb | `web_async/` folder | Custom `web.static("/mtb_async", ...)` aiohttp route |
Both use a `loadScript()` helper to inject `` tags on demand. Both break on Cloud.
## Proposed Layered Solution
### Short-term (S, in progress)
**Expose commonly-needed libraries from the frontend bundle.**
`marked` and `DOMPurify` are already bundled as direct dependencies. PR #10700 exposes `renderMarkdownToHtml()` on `ExtensionManager`, eliminating the need for custom nodes to bundle their own copies.
```js
// Custom node extension can now do:
const html = app.extensionManager.renderMarkdownToHtml(nodeData.description)
```
### Medium-term (S-M)
**Static subdirectory convention**: Backend treats a subdirectory (e.g., `web/libs/` or `web/static/`) as served-but-not-imported.
```python
# Backend change (~5 lines):
# When building the /extensions response, exclude files under a `libs/` subdirectory
# Files are still served via the existing static route, just not included in the glob
```
This formalizes what `kjweb_async` does manually, without requiring custom `add_routes()` calls. Cloud-compatible because files are served from the standard extension static path.
### Long-term (M)
**Extension manifest in `pyproject.toml`**: Custom nodes already declare metadata in `[tool.comfy]`. Extend this to declare which files are extensions vs. static assets:
```toml
[tool.comfy]
PublisherId = "kijai"
DisplayName = "KJNodes"
[tool.comfy.web]
extensions = ["web/js/*.js"] # Files to import() as extensions
static = ["web/libs/*"] # Files to serve but not auto-import
```
This gives the backend and frontend a proper manifest for filtering, without breaking any existing packs (missing manifest = current behavior).
## Design Considerations
- **Backward compatibility**: All solutions should be additive — existing `WEB_DIRECTORY` behavior unchanged
- **Cloud compatibility**: Solutions must work without custom aiohttp routes
- **Deduplication**: If multiple packs bundle the same library, consider shared asset serving
- **Security**: Exposed utilities should be safe by default (e.g., `renderMarkdownToHtml` includes DOMPurify sanitization)
## References
- KJNodes `kjweb_async` pattern: [__init__.py](https://github.com/kijai/ComfyUI-KJNodes) — `web.static("/kjweb_async", ...)`
- comfy_mtb `web_async` pattern: [comfy_mtb](https://github.com/melmass/comfy_mtb)
- Frontend extension loading: `src/services/extensionService.ts` L31-53
- Backend extension serving: `server.py` `/extensions` endpoint
┆Issue is synchronized with this [Notion page](https://www.notion.so/Issue-10701-RFC-Standardize-how-custom-node-extensions-declare-and-load-JS-library-dependencies-3326d73d365081f9a266f24419f54584) by [Unito](https://www.unito.io)
Contributor guide
Research direction
Start by reading src/services/extensionService.ts L31-53 and the /extensions endpoint in server.py to trace how JavaScript files are discovered, served, and imported. Compare the short-, medium-, and long-term proposals with the backward-compatibility and Cloud requirements; done requires an agreed standard and an implementation path, but this RFC does not select one.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, python, typescript
- Domain
- backend, cloud, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100