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

buntop

v0.0.2

Published

BunTop a tiny desktop framework using Bun and WebView

Downloads

227

Readme

BunTop

Ask DeepWiki

A tiny desktop framework using Bun and WebView. Light, simple, and just works. If your app needs more than this, use Electron or similar instead.

No IPC. Communication between the frontend and backend happens over a local WebSocket.

NPM Packages:

  • https://www.npmjs.com/package/webview-bun (WebView)
  • https://www.npmjs.com/package/@trayjs/trayjs (Tray integration)
  • https://www.npmjs.com/package/node-notifier (Notification library)
  • https://www.npmjs.com/package/protocol-registry (Registry for custom protocol handlers)

All of the above support Windows, macOS, and Linux.

How it works

  • The main process runs a local Bun.serve() that serves your static files (HTML, CSS, JS, images, fonts, etc.) and a WebSocket endpoint for RPC and events.
  • The webview window runs in a separate child process that connects back over that WebSocket.
  • This keeps the server responsive even while the window's native event loop is running.

Getting started

bun add buntop
bunx buntop init   # interactive: scaffolds buntopconfig.json (and index.js if missing)
bunx buntop init --yes   # non-interactive: writes a default config (also used automatically when stdin isn't a TTY)
bunx buntop build dev

Usage

import { BunTop } from "buntop";

const app = new BunTop({ title: "My App", width: 800, height: 600 });

app.expose("getVersion", () => "1.0.0");

app.start({ path: "./ui" }); // directory containing index.html, or a single .html file

start() accepts one of:

  • path - an .html file or a directory containing index.html. Other local files referenced from it (.css, .js, images, fonts, etc.) are served alongside it.
  • html - an inline HTML string.
  • url - a remote URL to navigate to.

Pass sandbox: true to load less-trusted content (e.g. a remote url): this disables the buntop IPC bridge, the /__buntop_ws and /__buntop_focus endpoints, and devtools, so the page has no access to exposed functions or backend APIs.

Frontend API

const version = await buntop.call("getVersion");

window.addEventListener("buntop:tick", (e) => console.log(e.detail));

Backend API

  • app.expose(name, fn) - register a function callable from the frontend via buntop.call(name, ...args).
  • app.emit(event, data) - dispatch a buntop:<event> CustomEvent to the frontend.
  • app.notify(options) - show a native OS notification. (docs)
  • app.tray(options) - create a system tray icon and menu. (docs)
  • app.protocol.register/deregister/isRegistered/getDefaultApp - manage custom URL protocol handlers. (docs)
  • app.dialog.openFile/saveFile/openFolder/message/confirm - native file and message dialogs. (docs)
  • app.updater.check/download/apply - check for, download, and install updates. (docs)
  • app.open(target) - open a URL or file/folder path in the OS default browser/app.
  • app.paths.home/appData/config/cache/temp/logs/documents/downloads/desktop/pictures/music/videos - standard OS directories. (docs)
  • app.paths.userData/userCache/userLogs - per-app, per-channel directories ({appData|cache|logs}/{identifier}/{channel}, channel from BUNTOP_CHANNEL, default "dev"). identifier comes from "identifier" in buntopconfig.json for compiled builds, falling back to package.json's "name". (docs)
  • app.moveToTrash(path) - move a file or folder to the OS trash/recycle bin.
  • app.log.debug/info/warn/error(...) - rolling, newline-delimited JSON file logger (see "Logging").
  • app.close() - close the window and exit cleanly.

When singleInstance is enabled, launching a second copy of the app focuses the existing window (via window.focus()) and emits buntop:second-instance with { argv } (the new process's argv.slice(2)) in the running instance, then exits the new process. (docs)

Window options

new BunTop({
  title: "My App",
  width: 800,
  height: 600,
  sizeHint: "none", // "none" | "min" | "max" | "fixed"
  debug: false,     // enable webview devtools (always off in beta/release builds)
  shareEnv: false,  // include process.env in buntop.info on the frontend
  singleInstance: false, // only allow one running instance per app + channel
  log: {}, // logger options, or `false` to disable logging
});

Examples

  • examples/minimal.js - the smallest possible app.
  • examples/basic.js - exposed functions, events, and the frontend buntop API.
  • examples/tray.js - system tray icon, menu, and notifications.
  • examples/notes-app/ - a small but complete app: static site serving, paths, dialogs, tray, notifications, the trash, and single-instance handling.

Exit handling

The window closes and the process exits cleanly on:

  • the window being closed by the user,
  • Ctrl+C (SIGINT) or SIGTERM,
  • an uncaught error,
  • the backend process exiting for any other reason.

Icons

Set "icon" in buntopconfig.json to a single source .png (ideally 1024x1024). The build script (bun run build:<channel>) uses icon-gen to generate, into {outDir}/icons/:

  • icon.ico - used for the compiled exe (--windows-icon)
  • icon.icns - for macOS app bundles
  • favicon.ico - served automatically as /favicon.ico if your site/HTML doesn't have its own
  • icon16.png ... icon1024.png - for tray icons, notifications, and Linux desktop files
{
  "name": "my-app",
  "title": "My App",
  "icon": "./assets/icon.png",
  "entry": "index.js"
}

Then wire the generated files up:

  • Compiled exe icon / title: set automatically from "icon" and "title" for Windows targets.
  • Window / titlebar icon: not set by BunTop. Use an HTML <link rel="icon"> tag, or a <head> favicon, in your page.
  • Tray icon: app.tray({ icon: { png: "./dist/icons/icon32.png", ico: "./dist/icons/icon.ico" } }).
  • Notification icon: app.notify({ icon: "./dist/icons/icon128.png", ... }).

You can also set "icon" directly to a pre-made .ico file to skip generation.

Logging

app.log.info("ready", { port: app.server.port });
app.log.error("something broke", err);

console.log("log file:", app.log.path()); // {logs}/{identifier}/{channel}/app.log
  • Writes newline-delimited JSON ({ time, level, message }) to app.paths.userLogs().
  • Rotates to app.log.1, app.log.2, ... once the active file exceeds maxSize (default 5MB), keeping maxFiles rotated files (default 5).
  • Uncaught exceptions, unhandled rejections, server/socket errors, and webview crashes are logged automatically.
  • app.log.files() returns the active log file plus any rotated ones, useful for attaching to a bug report or crash uploader.
  • Pass log: false to disable logging entirely.

Updater

const update = await app.updater.check({
  currentVersion: app.info().version,
  source: { type: "github", repo: "owner/repo", asset: process.platform === "win32" ? /\.exe$/ : /linux/ },
  // or: { type: "manifest", url: "https://example.com/update.json" }
  // or: { type: "file", path: "./update.json" }
});

if (update.hasUpdate) {
  const file = await app.updater.download(update, "./update-download");
  app.updater.apply(file); // replaces the running binary and restarts (compiled builds only)
}

Manifest/file sources are JSON: { "version": "1.2.3", "url": "https://.../app-linux", "notes": "..." }. url may be http(s)://, a local path, or file://.

Building

buntopconfig.json defines how bunx buntop build <channel> (dev, alpha, beta, release) compiles your app with bun build --compile. Run bunx buntop init to generate a starter config.

{
  "name": "buntop-app",
  "entry": "examples/basic.js",
  "outDir": "dist",
  "buildBin": "build-bin",
  "icon": "./examples/icon.png",
  "license": "./LICENSE",
  "identifier": "online.sectly.buntop-app",
  "title": "BunTop App",
  "description": "A standalone BunTop application",
  "publisher": "Sectly",
  "version": "0.0.1",
  "copyright": "Copyright 2026 Sectly",
  "channels": {
    "dev": { "targets": ["current"], "minify": false, "sourcemap": true, "bytecode": false },
    "release": { "targets": ["bun-windows-x64", "bun-linux-x64", "bun-linux-arm64", "bun-darwin-x64", "bun-darwin-arm64"], "minify": true, "sourcemap": false, "bytecode": false }
  }
}
  • title, description, publisher, version, copyright are embedded as Windows executable metadata (--windows-title/-description/-publisher/-version/-copyright).
  • icon is embedded as the .exe icon (--windows-icon) and used to generate the app/window/taskbar icons described above.
  • identifier (e.g. online.sectly.buntop-app) is the app's bundle/package ID. It's embedded into the binary (used for app.paths.userData/userCache/userLogs) and used as the macOS CFBundleIdentifier and Linux .desktop filename. Defaults to com.<publisher>.<name>.
  • The selected channel is embedded via __BUNTOP_CHANNEL__ and exposed at runtime (BUNTOP_CHANNEL); all Windows builds use --windows-hide-console (console output still works when run from a terminal); beta and release builds also have devtools disabled.
  • The build script prints per-target progress, timing, and output file sizes, and exits non-zero if any target fails to build.

Installers

beta and release builds also produce an installer/bundle for each target alongside the portable executable (set "installer": false/true on a channel to override). The compiled exe still works standalone if you'd rather ship that.

  • Windows: an NSIS *-setup.exe with Start Menu + Desktop shortcuts and an uninstaller. makensis is downloaded automatically and extracted into buildBin (default ./build-bin, configurable in buntopconfig.json) on first use, so no manual NSIS install is needed and this works from any host OS. Safe to delete; it'll be re-downloaded on the next build.
    • The setup wizard lets the user choose a per-user install (%LOCALAPPDATA%, no prompt) or an all-users install (Program Files, triggers a UAC prompt only in that case).
    • If a previous version is detected (via the registry), the installer offers to silently uninstall it first.
    • If the app is currently running during install or uninstall, the user is prompted to close it before continuing.
    • Installs/uninstalls a standard Add/Remove Programs entry.
    • If "license" is set, it's shown as a license-acceptance page.
  • macOS: a *.app bundle (with Info.plist and .icns icon, plus the license file in Resources if "license" is set). If hdiutil is available (i.e. building on macOS), also produces a *.dmg.
  • Linux: an *.AppDir with a .desktop file, icon, AppRun launcher, and the license file if "license" is set, packaged as a *.tar.gz.

If a step can't be completed (e.g. .dmg generation off macOS), the build logs a warning and skips just that part - the portable executable/bundle is still built normally.

Error handling

If BunTop can't start (unsupported OS, missing Bun runtime, missing system webview dependencies, or a webview crash), it prints an error with install hints and writes the failure to the log file (so it's visible even in --windows-hide-console builds), then exits with a non-zero code instead of leaving a silent/blank window.

Planned features

  • SQLite (Bun's built-in SQLite)