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

@zabloo/renderer-web

v0.2.0

Published

zabloo web renderer: consumes IR envelopes and self-renders them to WebGL2 (own layout, own tessellation, own glyph atlas). Seed of the visual editor canvas.

Downloads

370

Readme

@zabloo/renderer-web

The zabloo self-renderer for the browser: give it an IR envelope and a <canvas>, and it draws the UI on WebGL2 — its own layout pass, its own tessellator, its own glyph atlas.

Part of zabloo/ui — build your game's UI once in React, ship a compact engine-agnostic IR, and let a lightweight SDK draw it inside Unity, Godot or Unreal.

The browser is just another engine target here: nothing is mapped to DOM elements, so what you see is what the in-engine SDKs paint. It powers the zabloo dev live preview and is the seed of the visual editor's canvas.

Install

npm install @zabloo/renderer-web

Mounting a view

import { mount } from "@zabloo/renderer-web";

const canvas = document.querySelector("canvas") as HTMLCanvasElement;
const envelope = await fetch("/zabloo.ir.json").then((r) => r.text());

const ui = mount(canvas, envelope, {
  view: "main-menu", // default: the envelope's first view
  background: "#11141d",
  onAction: (action, context) => {
    if (action === "play") startGame();
    if (context) console.log("fired from item", context.path);
  },
  onDataChanged: (path, value) => console.log("player wrote", path, "=", value),
  onDiagnostic: (d) => showInEditor(d.level, d.code, d.path, d.message),
});

await ui.ready; // the view has swapped in its own text rasterizer and repainted

ui.setData("player.gold", 1250); // bound Text/visible/checked react and re-lay out
ui.reload(nextEnvelope); // hot-update, same path a shipped SDK uses
ui.dispose();

mount throws an EnvelopeError if the payload is unusable — there is no previous UI to protect, and the caller has to hear that its payload never became a view. reload never throws: a refused hot-update is discarded and the view on screen stays exactly as it is.

The handle also drives the UI the way a player would (setOpen, setChecked, setValue, setText, setSelectedTab, setScroll), exposes the frame's measurements via snapshot() — rects, wrap points, baselines, clips, layer order, focus/hover/press, read a node at a time with findNode(snapshot, "buy-btn") — and its cost via stats().

viewIds is read from the envelope currently loaded, so a hot-update that adds, drops or renames views is reflected the next time you read it — a view picker should re-read it after every reload rather than keep the array it got at mount.

Seeing another screen, and what a frame costs

const ui = mount(canvas, envelope, {
  dpr: 1, // render as a 1× screen would, whatever this monitor is
  onFrame: ({ ms, drawCalls, vertices, repaintOnly }) => hud.update(ms, drawCalls),
});

dpr overrides the page's device pixel ratio everywhere the renderer turns logical pixels into device ones — the canvas backing store, the glyph atlas scale, the pixel grid glyph quads snap to — so the whole picture moves together instead of half of it. It is fixed for the life of the mount, because the atlases are rasterized at it: a host offering it as a control remounts. The logical size is unchanged by it, so the same UI is laid out the same way at any ratio.

onFrame fires once per frame actually painted, with FrameStats plus the ms spent tessellating and submitting it. stats() answers what did the last frame cost; this answers when, which polling cannot: the renderer paints on demand, so a still scene paints nothing at all and a caller's own requestAnimationFrame would be measuring the page rather than the renderer.

Authoring errors

onDiagnostic receives every diagnostic the loading contract produces, for both mount and reload: a warn was repaired and the envelope loaded without the broken part, a fatal means nothing loaded (and arrives just before mount throws). Each one carries a stable code and the path into the envelope it sits on, so an error overlay, a dev server or an editor can show it where the author is looking:

mount(canvas, envelope, {
  onDiagnostic: ({ level, code, path, message }) => {
    if (level === "fatal") overlay.show(message); // nothing loaded: the view is stale
    else console.info(`[${code}] ${path}`, message); // repaired: the view is fine
  },
});

Without it, warnings go to the console as [zabloo] lines — which is exactly what the CLI preview stopped relying on: a page cannot read its own console.

Script tag

The ./global subpath is an IIFE bundle that defines window.ZablooRenderer, for pages with no bundler (this is how the CLI's preview serves the renderer):

<script src="/node_modules/@zabloo/renderer-web/dist/index.global.js"></script>
<script>
  const ui = ZablooRenderer.mount(document.querySelector("canvas"), envelope);
</script>

Because it is loaded by path as often as by specifier, exports["./global"] is a plain string with no conditions — keep it resolvable from both ESM and CJS.

Documentation

License

MIT