npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nynxz/zenkit-nodekit

v0.2.0

Published

Everything a ComfyUI node pack's frontend needs: node declaration, widget mounting, the Vite build preset, and the ambient @comfy/* types.

Downloads

146

Readme

@nynxz/zenkit-nodekit

Everything a ComfyUI custom-node pack's frontend needs: the build, the ambient host types, and the machinery to put a Vue component inside a node body.

It knows nothing about ZenKit being installed. Panels and the viewer light up when the ZenKit runtime is present and no-op when it isn't, so a pack built on nodekit works on a plain ComfyUI.

Install

pnpm add -D @nynxz/zenkit-nodekit
pnpm add @nynxz/zenkit-ui       # optional: the themed component set

The three files

1. pack.json — your pack's identity, read by both halves so they can't drift.

{ "namespace": "nynxz", "displayName": "Nynxz's Nodes", "category": "Nynxz" }

2. src/framework.ts — bind nodekit once; every other file imports from here.

import { createNodekit, defineNode } from '@nynxz/zenkit-nodekit'
import manifest from '../pack.json'

export const { nodeId, typeId, route, registerNodes, discoverWidgets, widgetTypes } =
  createNodekit(manifest)
export { defineNode }

3. src/main.ts — the whole registration story.

const widgets = import.meta.glob('./widgets/*.vue', { eager: true })
registerNodes(discoverWidgets(widgets))

vite.config.mts is then three lines:

import { zenPluginConfig } from '@nynxz/zenkit-nodekit/vite'
export default zenPluginConfig({ name: 'comfyui-yourpack', configUrl: import.meta.url })

and tsconfig.json extends @nynxz/zenkit-nodekit/tsconfig.pack.json, adding node_modules/@nynxz/zenkit-nodekit/comfy.d.ts to include for the ambient @comfy/* types.

Adding a widget is adding a file

Drop src/widgets/LoraStack.vue in. It registers the io type NYNXZ_LORA_STACK — namespace uppercased, plus the component name in SCREAMING_SNAKE.

Your python schema derives the same string from the same component name, so neither side writes it out:

LoraStackType = widget_type("LoraStack", list)      # -> NYNXZ_LORA_STACK
...
inputs=[LoraStackType.Input("stack", default=[])]

The component receives { widget, node }. widget.value is what serializes into the prompt:

<script setup lang="ts">
import type { DOMWidget } from '@nynxz/zenkit-nodekit'
const props = defineProps<{ widget: DOMWidget; node: unknown }>()
const { widget } = props // not through `props` — see below
</script>

Two constraints:

  • Take widget out of props before writing to it — assigning through props.widget trips vue/no-mutating-props.
  • widget.value is not reactive. Hold state in a ref and write through on change.

The widget's name and default come from what ComfyUI passes the constructor, so both live only in define_schema.

When a widget needs more

Export widgetOptions for widget-level tweaks, or nodeDef for node-level behaviour — colocated with the component instead of in a separate registry:

<script lang="ts">
import type { WidgetOptions } from '@nynxz/zenkit-nodekit'
export const widgetOptions: WidgetOptions = { minHeight: 90, fill: true }
</script>
// node-level: needs `is`, because these act on a node instance
export const nodeDef = {
  is: ['nynxz.Lora.Loader', 'nynxz.Lora.LoaderCLIP'],
  minSize: [380, 130] as [number, number],
  output: { widget: 'stack', from: (o) => o.result?.[0] },
}

To declare nodes explicitly instead of by convention, defineNode + discoverNodes take a glob of node.ts files. Both paths end in registerNodes.

What else is in here

| | | | ------------------------------- | -------------------------------------------------------------------------- | | mountWidget | mount a Vue app in a node body — pointer, sizing and value traps handled | | useDragSurface | drag-to-reorder inside a node body without the canvas stealing the gesture | | addNodeHeaderButton | a control in the node's title bar | | openZenPanel / hasZenPanels | pop a component into a real dockable panel when ZenKit is installed | | openViewer / viewUrl | the shared media viewer | | registerSlotLink | middle-click a slot → spawn and wire a companion node |