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

@grafloria/qwik

v0.13.0

Published

Qwik bindings for Grafloria Diagrams and Grafloria Dashboards — an MIT diagram and dashboard engine: routing, auto-layout, undo, collaboration and dashboard layouts, server-rendered and resumable in Qwik.

Readme

@grafloria/qwik

Qwik bindings for Grafloria Diagrams and Grafloria Dashboards. Grafloria is an MIT diagram and dashboard engine for JavaScript: one headless core, native Angular, React, Vue and Qwik bindings, one document format and one undo stack.

Grafloria Diagrams is an MIT JavaScript diagram library for flowcharts, workflow editors, UML and ER diagrams, with obstacle-avoiding routing, auto-layout, undo and real-time collaboration built in. In Qwik: server-rendered and resumable — the diagram is on the page, correct, before any component JavaScript runs.

npm install @grafloria/qwik @grafloria/element @grafloria/renderer @grafloria/engine
import { component$, useSignal, $ } from '@builder.io/qwik';
import { GrafloriaFlow, type NodeSpec, type EdgeSpec } from '@grafloria/qwik';

export default component$(() => {
  const nodes = useSignal<NodeSpec[]>([
    { id: 'a', type: 'job', position: { x: 0, y: 0 }, size: { width: 180, height: 80 }, data: { title: 'Extract' } },
    { id: 'b', position: { x: 260, y: 0 }, label: 'Load' },
  ]);
  const edges = useSignal<EdgeSpec[]>([{ source: 'a', target: 'b' }]);

  return (
    <GrafloriaFlow
      nodes={nodes.value}
      edges={edges.value}
      layout="elk"
      nodeTypes={{ job: JobCard }}
      onNodesChange$={$((next) => (nodes.value = next))}
      style={{ height: '400px' }}
    />
  );
});
  • nodes / edges — controlled data; adds and removes made inside the diagram come back through onNodesChange$ / onEdgesChange$. defaultNodes / defaultEdges for uncontrolled use.
  • nodeTypes — custom nodes as Qwik components, keyed by node type. Declaring the type is the whole opt-in. (Read the container caveat below.)
  • layout — 'elk' | 'dagre' | 'force' | 'tree' | 'grid' | 'auto' | … or { name, options }; re-runs on value change, never on data change; onLayoutDone$ fires after. ELK loads lazily (~1.4 MB you don't ship unless a layout runs).
  • highlighterConfig — the outline layer: outlines around the hovered node, the selected node, nodes with a validation issue, and valid connection targets. Off by default; true turns every kind on, an object picks kinds ({ showValidation: false } keeps hover and selection only). Follows the prop live.
  • highlightConnected — select a node and its lines come forward in ink while the rest fade: true, or { depth, stroke, outgoing, dimOpacity } (depth: Infinity traces every path in and out). Off by default; follows the prop live.
  • groups / defaultGroups — zones around some nodes: a spec's groups, or the live GroupModels of a loaded document (fromDocument()), reconciled like nodes.
  • <GrafloriaDiagram spec> — hosts any kit spec. A CHANGED spec (or options) replaces the diagram; an equal one built again on a re-render does not, so spec={erDiagram({ … })} written inline is fine.
  • Callbacks are QRLs — onInit$, onSelectionChange$, onConnect$, onNodeClick$, onEdgeClick$. The $ suffix is what lets Qwik's optimizer split each handler into its own lazy chunk.
  • Hooks — useGrafloria(), useSelection(), useOnSelectionChange$(), useViewport(), and <GrafloriaProvider> to reach the instance from sibling toolbars and inspectors.

MIT © Grafloria

Vite setup — nothing to add

qwikVite() is the whole Vite config (plus qwikCity() in a Qwik City app) — in the dev server, with server rendering, and in production builds:

// vite.config.ts
import { defineConfig } from 'vite';
import { qwikVite } from '@builder.io/qwik/optimizer';

export default defineConfig({ plugins: [qwikVite()] });

This package is a Qwik library (its qwik field makes your optimizer compile it), so Vite's dev server does not pre-bundle it, nor the engine, renderer and element it imports: they are served as plain ES modules. Everything a browser loads from them is plain ESM — the engine ships ES module copies of its CommonJS dependencies (eventemitter3, dagre, lemonadejs, and elkjs, which still loads only when an ELK layout runs) — and each of the three carries a qwik field too, so qwikVite() serves one copy of each however your app imports them: instanceof NodeModel holds between your code and the component's.

grafloriaQwik() from @grafloria/qwik/vite, which @grafloria/qwik 0.10.6 required, still works and is now optional: it pre-bundles the three packages for the dev server, so the first page load makes a few dozen requests instead of ~600. Production builds are the same with or without it.

Server rendering, and why Qwik gets the best version of it

Every DOM touch in this wrapper happens inside useVisibleTask$, which never runs on the server. So the component renders server-side with no window anywhere — and you can go further and put the real diagram in that HTML:

import { renderToStaticSVG, GrafloriaFlow } from '@grafloria/qwik';

// On the server (a routeLoader$, a server$ call, or a build step):
const ssr = renderToStaticSVG({ nodes, edges, width: 800, height: 600 });

// In the component:
<GrafloriaFlow nodes={nodes} edges={edges} ssr={ssr} />;

renderToStaticSVG() runs the real engine and the real renderer in Node, with no DOM, and returns the exact markup createDiagram() would have mounted plus a hydration snapshot. The component emits that markup verbatim, and on the client the visible task adopts the existing DOM via createDiagram({ hydrate }) rather than rebuilding it: no flash, no re-layout.

The React binding in this repo does the same, but React still walks the tree again to hydrate. Qwik resumes: there is no hydration pass. The diagram's script starts as soon as the page is ready — deliberately, so onInit$ fires and useGrafloria() returns the instance before anyone touches it — and it adopts the server's DOM instead of redrawing it.

Put ssr.css in your document head; the client re-injects identical content under the same ids, so nothing repaints.

Custom nodes are not server-rendered — they are framework components and the server has no framework. They mount on the client inside the (empty, correctly transformed) HTML layer the SSR markup already carries.

Every gallery demo, in Qwik: grafloria.com/demos-qwik — the Qwik version of each of the 100+ gallery pages, source shown; on any gallery page, the Qwik button runs it in place.

Server rendering, end to end. apps/demos-qwik in the repository is a Qwik SSR app over this package; its ssr-resumable route is the end-to-end proof (view source and the laid-out diagram is already in the HTML). Clone, then:

npx vite --config apps/demos-qwik/vite.config.ts --mode ssr

--mode ssr is required — Qwik's Vite plugin only starts its dev SSR server in that mode.

Four Qwik-specific rules

1. Live objects must be noSerialize()d. Qwik serializes the state a component closes over so the page can resume. A transport holds sockets; a CommentStore holds subscribers; neither survives JSON. Mark them:

import { noSerialize, useSignal } from '@builder.io/qwik';

const collab = useSignal(
  noSerialize({ transport: new WebSocketTransport(url), actor: 'ada', presence: true })
);

<GrafloriaFlow collab={collab.value} … />;

The DiagramInstance the wrapper hands you is already noSerialized; it comes back undefined after a resume, which is correct, because a resumed page builds a fresh instance.

2. Custom nodes are their own Qwik container. Qwik has no portal primitive, so nodeTypes components are mounted with Qwik's render() into the host element the core creates. That creates a separate container, which means a custom node cannot read contexts provided by the surrounding app — including router contexts. This is intended behaviour for render() upstream, not a bug in this wrapper. Keep custom nodes self-contained and feed them through node.data. The same applies to widgetTypes on <GrafloriaDashboard>.

3. A client-only app must load Qwik's event loader. Qwik dispatches DOM events (onClick$, onInput$, …) through a tiny loader script. A server-rendered page carries it automatically; an app that only calls render() in the browser does not, and every handler is then silently dead — buttons and inputs do nothing, with no error. Add it once before rendering:

import { render } from '@builder.io/qwik';
import { QWIK_LOADER } from '@builder.io/qwik/loader';

const loader = document.createElement('script');
loader.textContent = QWIK_LOADER;
document.head.appendChild(loader);
render(document.getElementById('app')!, <App />);

4. Kit specs go through spec$. erDiagram(), umlDiagram() and dashboard() return specs that carry functions, and Qwik cannot serialize a function into server-rendered HTML. Hand <GrafloriaDiagram> a QRL that builds the spec in the browser:

<GrafloriaDiagram key={schemaVersion} spec$={() => erDiagram({ entities, relationships })} />

A $ prop is fixed for the life of a Qwik component, so the spec is built once, at mount — change key to rebuild it from new data. Plain-data specs ({ nodes, edges }) can still go in spec.

Qwik version

Targets Qwik 1.x (@builder.io/qwik). Qwik 2 is published as @qwik.dev/core and is still in beta, which is not something a published library should pin a peer range to. A Qwik 2 app can still consume this package: the v2 optimizer rewrites @builder.io/qwik imports to @qwik.dev/core. You may need the aliases described in the Qwik v2 migration notes for TypeScript to resolve types.

Bundle size — what actually ships

Don't judge this library by npm's unpacked size stat — that is uncompressed ESM source plus full TypeScript declarations. None of it reaches your users as-is; what matters is what your bundler emits, and with Qwik the answer is unusually good, because the optimizer splits every $ boundary into its own chunk and the preloader only fetches what an interaction actually needs.

Reproduce a worst-case eager bundle in two minutes:

npm i -D esbuild @grafloria/qwik
echo "export * from '@grafloria/qwik';" > entry.mjs
npx esbuild entry.mjs --bundle --minify --format=esm --splitting --outdir=out --external:@builder.io/qwik
gzip -k9 out/entry.js && wc -c out/entry.js out/entry.js.gz

--splitting matters: without it esbuild inlines the lazily-imported ELK chunk and inflates the number by ~1.4 MB. Real app bundlers split by default. Since engine 0.3.0 / renderer 0.4.0 / element 0.4.0 the packages are pure ESM — every bundler tree-shakes them, and Node ≥ 20.19 can require() them too.