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

debloatui

v0.1.0

Published

Fast, debloated native UI toolkit (raylib + raygui) with JavaScript app logic

Readme

debloatui

Native UI for JavaScript/TypeScript — without embedding a browser.

About

debloatui is a desktop UI toolkit for Node.js aimed at low-end machines and simple tools. It pairs a thin native renderer (raylib + raygui) with a JavaScript library for reactive UI and layout, so application logic stays in JS/TS while drawing stays native and lightweight.

Built on raylib and raygui

  • raylib is a lightweight, modern, open-source C library for games and graphics. It is best known for a simple OpenGL pipeline; depending on platform and configuration, the raylib ecosystem also covers other backends (including paths used for Vulkan and software rendering). debloatui currently builds the standard desktop raylib backend (OpenGL).
  • raygui sits on top of raylib and provides an immediate-mode library for native user interfaces — buttons, panels, sliders, scroll views, and more — without a retained widget tree or a full browser engine.

debloatui wraps that stack in an N-API native addon so Node can own app logic while the OS window and controls stay native.

JavaScript toolkit

On top of the native addon, debloatui provides a JavaScript/TypeScript library so you do not have to wire every control by hand each frame:

  • Reactive UI (require('debloatui/reactive')) — single-window apps with a global state object, a pure view(state) that returns control nodes, and dispatch(action) for updates. Control events (onclick, oninput, onchange, and so on) drive state transitions, while raygui still draws natively each frame.
  • Layout tools (require('debloatui/layout')) — helpers such as gridLayoutContainer and gridSize that assign x / y / w / h to children (columns, rows, spans, margins, padding) so you can build structured screens without manual pixel arithmetic.

You can still use the low-level API (run(options, frameCallback) and ui.button(...)) when you want full control. The reactive and layout packages are the recommended path for application UI.

Why not a WebView?

WebView-based shells work well for many products, but they are often overkill: large install size, high memory use, and heavy CPU/GPU cost for interfaces that are basically forms and tools. Shipping an entire browser to draw a desktop settings window is a poor fit when you only need native controls.

See Performance measurement for measured RSS/CPU on sample apps and a comparison with WebView-style stacks.

debloatui is an alternative when you want:

  • Native drawing (raylib + raygui), not HTML/CSS/DOM
  • Low bloat — no Chromium
  • Reactive JS UI and layout helpers — ship screens quickly without a DOM
  • JS/TS app logic for iteration speed
  • Desktop as the primary target today (Linux, Windows, macOS)

Android support is planned and coming soon.


Architecture

┌──────────────────────────────────────────────┐
│  JavaScript / TypeScript                     │
│  state, IO, business logic                   │
│  frame / view → controls                     │
└─────────────────────┬────────────────────────┘
                      │ N-API (in-process)
┌─────────────────────▼────────────────────────┐
│  Native addon — owns window + render loop    │
│  raylib (window / OpenGL) + raygui (UI)      │
└──────────────────────────────────────────────┘

| Layer | Responsibility | |-------|----------------| | Native | Window, graphics backend, frame timing, raygui controls, textures | | JavaScript | App state, events, files/network, product logic | | Bridge | One JS entry per frame; UI methods are thin N-API calls |

The native side owns the main loop; JS runs inside each frame. This is not FFI over a free-running Node event loop.


Quick start

Dependencies

Linux (Debian/Ubuntu):

sudo apt install build-essential cmake libgl1-mesa-dev \
  libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev

macOS: Xcode CLT + brew install cmake
Windows: Visual Studio Build Tools (C++) + CMake

Node: 18+

Install and run examples

npm install                 # builds the native addon (cmake-js + raylib/raygui)

npm run example:basic       # low-level frame callback
npm run example:counter
npm run example:reactive    # reactive state + view
npm run example:scroll      # vertical GuiScrollPanel
npm run example:image       # PNG textures
npm run example:layout      # grid layout helper
npm run example:layout-ts   # same UI in TypeScript (requires Bun)
#   or: bun examples/ts/layout.ts
#   or: npx --yes tsx examples/ts/layout.ts   (see examples/ts/README.md)

Usage

Low-level (immediate-mode frame callback)

const { run } = require('debloatui');

let show = false;

run({ title: 'My App', width: 400, height: 200 }, (ui) => {
  if (ui.button(24, 24, 120, 30, 'Hello')) show = true;

  if (show) {
    const btn = ui.messageBox(85, 70, 250, 100, 'Hi', 'From JS state', 'OK;Cancel');
    if (btn >= 0) show = false;
  }
});

Reactive layer (recommended)

Single window, global state, pure view(state), and dispatch(action):

const {
  app, state, h, button, label,
} = require('debloatui/reactive');

const Increment = (s) => ({ ...s, count: s.count + 1 });

app({
  init: { count: 0 },
  window: { title: 'My App', width: 400, height: 200 },
  view: (s) => h(
    label({ x: 24, y: 24, w: 200, h: 24, text: `count = ${s.count}` }),
    button({ x: 24, y: 60, w: 120, h: 30, text: 'Inc', onclick: Increment }),
    // also readable globally: state.count
  ),
});
state ──view(state)──► nodes ──render──► raygui
  ▲                       │
  └──── dispatch(action)◄─┘  (onclick / oninput / onchange)

See examples/reactive-app.js (npm run example:reactive).

Vertical scroll

Uses raygui GuiScrollPanel plus scissor clipping:

scrollPanel({
  x: 24, y: 72, w: 400, h: 360,
  contentHeight: 1200,   // taller than panel → vertical scrollbar
  scrollY: state.scrollY,
  onscroll: (s, { scrollY }) => ({ ...s, scrollY }),
  children: [
    // coords relative to content top-left (0, 0)
    label({ x: 12, y: 12, w: 200, h: 20, text: 'Row 1' }),
  ],
})

Run npm run example:scroll.

Images (raylib textures)

const path = require('path');

image({
  x: 24, y: 40, w: 200, h: 200,
  src: path.join(__dirname, 'assets/logo.png'),
  fit: 'contain',           // fill | contain | cover | none
  // optional sprite / atlas region in original PNG pixels:
  rectX: 0, rectY: 0, rectW: 64, rectH: 64,
})

Run npm run example:image.

Icons (raygui)

raygui embeds a pack of 16×16 icons. Prefix control labels with #id# or use helpers:

const { Icons, iconText } = require('debloatui/icons');

button({
  x: 20, y: 40, w: 120, h: 30,
  text: iconText(Icons.FILE_OPEN, 'Open'), // "#5#Open"
  onclick: Inc,
});

Run npm run example:icons npm run example:icon-grid # full icon pack in a scroll grid.

Multi-line text

textBlock({
  x: 24, y: 48, w: 400, h: 200,
  wrap: 'word', // 'none' | 'char' | 'word'
  text: 'Hello.\n\nA longer paragraph that wraps inside the box.',
})

Run npm run example:multiline npm run example:table npm run example:components npm run example:icons # raygui built-in icons # function components (ActionCard) # Spain squad data table + scroll.

Layout (grid)

Pure JS helper — assigns x/y/w/h to reactive children:

const { gridLayoutContainer, gridSize } = require('debloatui/layout');

const grid = {
  x: 24, y: 56, width: 432,
  cols: 3, rows: 4,
  cellHeight: 48, cellMargin: 10,
};

const nodes = gridLayoutContainer({
  grid,
  children: [
    button({ text: 'A', onclick: Inc }),
    button({ text: 'Wide', col: 0, row: 1, colSpan: 2, onclick: Inc }),
  ],
});
// ...h(...nodes)

Run npm run example:layout.

Keyboard events

On the window options object (both run({ ... }) and reactive app({ window })):

const Keys = require('debloatui/keys');

run({
  title: 'App',
  width: 400,
  height: 200,
  keydown: (e) => {
    if (e.key === Keys.ESCAPE) { /* ... */ }
  },
  keyup: (e) => { /* ... */ },
}, (ui) => { /* draw */ });

Event shape: { type, key, repeat, ctrl, shift, alt, meta }.
key is a raylib key code; use debloatui/keys for named constants (Keys.A, Keys.ENTER, …).

Run npm run example:keys.

Mouse events

On the same window options object:

const Mouse = require('debloatui/mouse');

run({
  title: 'App',
  mousemove: (e) => { /* e.x, e.y, e.dx, e.dy */ },
  mousedown: (e) => { /* e.button, e.x, e.y */ },
  mouseup: (e) => {},
  click: (e) => { /* left button release */ },
  rightclick: (e) => { /* right button release */ },
}, (ui) => { /* draw */ });

Event shape: { type, button, x, y, dx, dy, ctrl, shift, alt, meta } (dx/dy set on mousemove).
Button codes: debloatui/mouse (Mouse.LEFT, Mouse.RIGHT, …).

Run npm run example:mouse.


Project layout

debloatui/
├── package.json
├── CMakeLists.txt              # native addon build
├── native/src/                 # N-API + raylib/raygui
│   ├── addon.cpp
│   ├── app.cpp / app.h
│   └── ui_context.cpp / .h
├── lib/
│   ├── index.js / index.d.ts   # low-level API
│   ├── reactive.js / .d.ts     # state + view + dispatch
│   └── layout.js / .d.ts       # grid layout helper
├── examples/
│   ├── basic.js
│   ├── counter.js
│   ├── reactive-app.js
│   ├── reactive-scroll.js
│   ├── reactive-image.js
│   ├── reactive-layout.js
│   └── assets/
├── raygui/                     # standalone C sample (no Node)
└── temp/                       # experiments

API (MVP)

run(options, frameCallback) — blocks until the window closes.

Ui methods (inside frameCallback):

| Method | Returns | |--------|---------| | button(x,y,w,h,text) | boolean clicked | | label(x,y,w,h,text) | void | | checkBox(x,y,w,h,text,checked) | new boolean | | slider / sliderBar / progressBar | new number | | textBox(id,x,y,w,h,text,maxLen?) | { text, active } | | valueBox / spinner | new number | | comboBox(x,y,w,h,"A;B",active) | index | | toggle(...) | new boolean | | messageBox(..., "Yes;No") | button index or -1 | | scrollPanel(...) | { scrollX, scrollY, view } | | image(x,y,w,h,src,options?) | { ok, width, height, rect } | | beginScissor / endScissor | void | | statusBar / groupBox / panel / line / dummy | void | | getScreenSize() | { width, height } | | getFrameTime() | seconds | | close() | request quit |

See lib/index.d.ts and lib/reactive.d.ts for full typings.


Design notes

  • Debloat: no Chromium, no retained DOM — native raylib/raygui drawing.
  • Fast shipping: product logic in JS/TS without rebuilding C++ for every change.
  • Performance: one JS entry per frame; control calls stay in-process (N-API).
  • run() blocks the Node event loop by design (native main loop). Use workers or deferred I/O if you need concurrency.
  • Desktop-first: Linux, Windows, macOS today; Android planned.

Performance measurement

Sample CPU and memory (RSS) while an example runs (no extra npm deps):

# default: 10s sample window after 1s warmup
npm run measure -- examples/reactive-layout.js

# custom duration / interval
node scripts/measure-example.js examples/reactive-multiline.js -d 15 -i 250

# JSON summary
npm run measure -- examples/reactive-app.js --json

The script spawns the example, samples periodically (Linux: /proc), then sends SIGTERM.

Test environment

Results below were captured on this host:

| Spec | Value | |------|--------| | OS | Ubuntu 22.04.5 LTS (Jammy) | | Kernel | Linux 6.8.0-134-generic x86_64 | | CPU | 13th Gen Intel Core i5-1334U (12 logical CPUs) | | RAM | ~24 GiB system memory | | GPU | Intel integrated graphics (Mesa OpenGL) | | Display | X11 (DISPLAY=:1) | | Node | v24.18.0 | | Toolkit | debloatui native addon + raylib 5.5 / raygui (desktop OpenGL) |

This is a developer workstation, not a constrained low-end device. Absolute MiB will differ on smaller machines; the point of the comparison is the single-process, no-Chromium cost class.

Sample results (debloatui)

Settings: warmup 1 s, duration 10 s, interval 200 ms, window open and idle (no heavy interaction).
CPU is approximate % of one core from process tick deltas.

| Example | RSS min (MiB) | RSS avg (MiB) | RSS max (MiB) | CPU avg (%) | CPU max (%) | |---------|---------------|---------------|---------------|-------------|-------------| | examples/basic.js | 113.6 | 113.6 | 113.8 | 5.6 | 10 | | examples/reactive-layout.js | 115.1 | 117.6 | 118.4 | 6.2 | 10 | | examples/reactive-multiline.js | 113.9 | 115.4 | 116.5 | 6.8 | 15 |

Takeaways from this run:

  • Small tools land around ~114–118 MiB RSS for a live OpenGL window + Node + native UI.
  • Idle CPU stays low (single-digit average % of one core at 60 FPS target with a simple scene).
  • Cost is one process (Node loads debloatui.node); there is no separate browser renderer/GPU process tree.

Re-run on your machine to refresh the table:

npm run measure -- examples/reactive-layout.js -d 10 -i 200 --json

Comparison with traditional WebView UI

A typical “WebView desktop UI” means Electron, CEF, or an OS WebView (WebView2, WKWebView, WebKitGTK) hosting HTML/CSS/JS. Those stacks attach a full browser engine. They are a great fit for complex web UIs, but idle cost and install size scale differently from an immediate-mode native UI.

| Stack | Typical memory for a simple / idle UI | Notes | |-------|----------------------------------------|--------| | debloatui (measured above) | ~114–118 MiB RSS | Single process: Node + raylib/raygui | | Electron (empty / “hello” window) | Often ~150–300+ MiB across processes | Chromium multi-process model (main + GPU + renderer); community and vendor samples commonly land in this range | | WebView2 / WebKitGTK thin host | Often ~50–150+ MiB, highly variable | Still a browser engine; grows with DOM/CSS/JS complexity | | Empty Chrome/Chromium tab | Often ~50–100+ MiB per tab process | Reference only — not a complete app shell |

How to read this fairly:

  • WebView/Electron figures are order-of-magnitude ranges from public docs and community measurements, not a same-app A/B bench inside this repository.
  • Absolute numbers change with OS, GPU driver, scaling, and whether the window is focused.
  • For forms, tools, and simple control panels, debloatui stays in a compact single-process band without shipping Chromium.
  • Prefer a WebView when you need full HTML/CSS/DOM, the web widget ecosystem, or an existing web front-end; prefer debloatui when you want native drawing, low bloat, and JS/TS logic.

Install / runtime size (qualitative): Electron apps often ship ~100–200+ MB of runtime binaries. debloatui ships a small native addon (plus system OpenGL/X11 or OS equivalents), with Node provided by the environment rather than embedded Chromium.

License

debloatui is licensed under the GNU GPL v3 or later — see LICENSE.md.

Dependencies raylib and raygui use zlib-style licenses (GPL-compatible) — see their repositories.