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

@webviewjs/webview

v0.4.0

Published

Robust cross-platform webview library for Node.js written in Rust. It also works with deno and bun.

Readme

@webviewjs/webview

https://github.com/webviewjs/webview/actions

Robust cross-platform webview library for Node.js written in Rust. It is a native binding to tao and wry allowing you to easily manage cross platform windowing and webview.

Highlights

  • Promise-based application readiness with optional automatic event pumping.
  • Non-blocking application pumping, so ordinary Node timers and I/O continue running.
  • Browser windows, menus, dialogs, cookies, DevTools, and window controls.
  • Typed EventEmitter APIs for applications, windows, webviews, and system tray icons.
  • Shared browser contexts for profile, cookie, cache, and storage isolation.
  • Cross-platform system tray icons with menus and runtime updates.
  • Native desktop notifications with a browser-familiar API.
  • Native Windows, macOS, X11, Wayland, iOS, and Android window extensions.
  • IPC through window.ipc.postMessage(), with an optional alias such as window.bindings.
  • Fetch-compatible asynchronous custom protocols, including Hono routing without an HTTP server.
  • Promise-based webview.expose() namespaces for page-to-Node calls.

preview

[!NOTE] This library is meant to be a lightweight system webview binding for JavaScript. It does not aim to be a full-featured framework like Electron or Tauri. Please report any issues you find, and consider contributing to the project if you need additional features.

See the full documentation for API references, guides, platform notes, and runnable examples.

Documentation

Getting started

| | | | ---------------------------------------------- | ------------------------------- | | Installation | System requirements and setup | | Quick Start | Your first window in minutes | | Event Loop | How the non-blocking pump works |

API reference

| | | | ------------------------------------- | ------------------------------------------------------ | | Application | Root object — event loop, windows, menus | | BrowserWindow | OS window, size, position, cursor, decorations | | Webview | Embedded browser — navigation, cookies, script, bounds | | WebContext | Shared browser data, profiles, and automation | | System Tray | Tray icons, menus, updates, and pointer events | | Notification | Native desktop notifications and lifecycle events | | Menu | Native menu bar construction | | Types | Shared interfaces and enums |

Guides

| | | | ----------------------------------------------------- | ----------------------------------------------- | | Building Executables | Compile to .exe / binary with node, deno, bun | | IPC Messaging | Page ↔ Node communication | | Menus | Building menu bars with roles and accelerators | | Multiple Windows | Managing several windows | | Cookies & Storage | Reading, writing, and clearing cookies | | Custom Protocols | Serving local content to the webview |

Platform notes

| | | | ----------------------------- | ----------------------------------------- | | Windows | WebView2, taskbar, DPI | | macOS | WebKit, main-thread requirement, app menu | | Linux | WebKitGTK, Wayland/X11, menu limitations | | iOS | Orientation, status bar, and gestures | | Android | Content rectangle and configuration |

Installation

npm install @webviewjs/webview

Supported platforms

| Platform | OS | Arch | Supported | | ----------------------------- | ------- | ----- | ----------------- | | x86_64-pc-windows-msvc | Windows | x64 | ✅ | | i686-pc-windows-msvc | Windows | x86 | ✅ | | aarch64-pc-windows-msvc | Windows | arm64 | ✅ | | x86_64-apple-darwin | macOS | x64 | ✅ | | aarch64-apple-darwin | macOS | arm64 | ✅ | | x86_64-unknown-linux-gnu | Linux | x64 | ✅ | | aarch64-unknown-linux-gnu | Linux | arm64 | ✅ | | armv7-unknown-linux-gnueabihf | Linux | armv7 | ✅ | | i686-unknown-linux-gnu | Linux | x86 | ⚠️ (no CI) | | aarch64-linux-android | Android | arm64 | ⚠️ (experimental) | | armv7-linux-androideabi | Android | armv7 | ⚠️ (experimental) | | x86_64-unknown-freebsd | FreeBSD | x64 | ⚠️ (no CI) |

Examples

Load external url

import { Application } from '@webviewjs/webview';
// or
const { Application } = require('@webviewjs/webview');

const app = new Application();
let mainWindow = null;
let mainWebview = null;

app.whenReady().then(() => {
  mainWindow = app.createBrowserWindow();
  mainWebview = mainWindow.createWebview({ url: 'https://nodejs.org' });
});

Event pumping

app.whenReady() starts the non-blocking event pump by default:

await app.whenReady({ interval: 16, ref: true });

For manual startup, disable auto-run:

const ready = app.whenReady({ autoRun: false });
app.run({ interval: 16, ref: true });
await ready;

interval defaults to 16 milliseconds and ref defaults to true. Use app.pumpEvents() for manual pumping.

System tray

Keep a strong JavaScript reference when you need to call tray methods or keep its listeners reachable:

let tray = null;

app.whenReady().then(() => {
  tray = app.createTrayIcon({
    id: 'main',
    icon: { data: rgba, width: 16, height: 16 },
    tooltip: 'My application',
    menu: { items: [{ id: 'quit', label: 'Quit' }] },
  });

  tray.on('click', (event) => console.log(event));
});

See the system tray reference and runnable tray example.

Notifications

import { Notification } from '@webviewjs/webview';

const notification = new Notification('Build complete', {
  body: 'The release executable is ready.',
});

notification.on('click', () => console.log('notification clicked'));
notification.on('error', ({ error }) => console.error(error));

Notification permission is always "granted" for native applications. See the notification reference and runnable notification example.

IPC and exposed functions

The webview page can send messages to Node through window.ipc.postMessage():

const webview = window.createWebview({ ipcName: 'bindings' });
webview.onIpcMessage((message) => console.log(message.body.toString()));

ipcName adds an alias, so the page can use window.bindings.postMessage(...); window.ipc remains available.

For typed request/response style calls, expose a namespace:

webview.expose('native', {
  version: '0.1.4',
  readConfig: async () => JSON.parse(await readFile('./config.json', 'utf8')),
});

In the page:

console.log(window.native.version);
const config = await window.native.readConfig();

Every exposed function returns a Promise in the page. Values, arguments, and results must be JSON-serializable. Violations use SerializationError.

Asynchronous custom protocols

Register a protocol before creating its webview:

window.registerProtocol('app', async (request) => {
  const filePath = join(process.cwd(), 'dist', new URL(request.url).pathname);
  try {
    return new Response(await readFile(filePath), {
      headers: { 'Content-Type': 'text/html; charset=utf-8' },
    });
  } catch {
    return new Response('Not found', {
      status: 404,
      headers: { 'Content-Type': 'text/plain; charset=utf-8' },
    });
  }
});

window.createWebview({ url: 'app://localhost/index.html' });

See Custom Protocols, IPC, and the runnable custom protocol and expose examples.

Menu System

WebviewJS provides a cross-platform menu system that works on macOS, Windows, and Linux.

Basic Menu Setup

import { Application } from '@webviewjs/webview';

const app = new Application();

// Set global application menu
app.setMenu({
  items: [
    {
      label: 'File',
      submenu: {
        items: [
          { id: 'new', label: 'New', accelerator: 'CmdOrCtrl+N' },
          { id: 'open', label: 'Open', accelerator: 'CmdOrCtrl+O' },
          { role: 'separator' },
          { id: 'quit', label: 'Quit', accelerator: 'CmdOrCtrl+Q' },
        ],
      },
    },
    {
      label: 'Edit',
      submenu: {
        items: [{ role: 'copy' }, { role: 'paste' }, { role: 'cut' }, { role: 'selectall' }],
      },
    },
  ],
});

const window = app.createBrowserWindow();
const webview = window.createWebview({ url: 'https://nodejs.org' });

app.run();

Menu Event Handling

import { Application } from '@webviewjs/webview';

const app = new Application();

// Handle menu events
app.on('custom-menu-click', ({ customMenuEvent: menuEvent }) => {
  console.log(`Menu item clicked: ${menuEvent.id}`);
  console.log(`From window: ${menuEvent.windowId}`);

  // Handle specific menu items
  switch (menuEvent.id) {
    case 'new':
      console.log('Creating new document...');
      break;
    case 'open':
      console.log('Opening file...');
      break;
    case 'quit':
      app.exit();
      break;
  }
});

// Set up menu...
app.setMenu({
  /* ... */
});

Window-Specific Menus

const app = new Application();

// Create window with custom menu
const window = app.createBrowserWindow({
  title: 'Custom Window',
  menu: {
    items: [
      {
        id: 'window-action',
        label: 'Window Action',
        accelerator: 'Ctrl+W',
      },
    ],
  },
});

// Or check if window has a menu
if (window.hasMenu()) {
  console.log('This window has a menu');
}

Menu Item Options

  • id: Unique identifier for the menu item (used in events)
  • label: Display text for the menu item
  • enabled: Whether the item is clickable (default: true)
  • accelerator: Keyboard shortcut (e.g., "CmdOrCtrl+N", "Alt+F4")
  • submenu: Nested menu items
  • role: Predefined menu items with built-in behavior

Predefined Menu Roles

  • "copy": Standard copy action
  • "paste": Standard paste action
  • "cut": Standard cut action
  • "selectall": Select all text action
  • "separator": Visual separator line

IPC

const app = new Application();
const window = app.createBrowserWindow();

const webview = window.createWebview({
  html: `<!DOCTYPE html>
    <html>
        <head>
            <title>Webview</title>
        </head>
        <body>
            <h1 id="output">Hello world!</h1>
            <button id="btn">Click me!</button>
            <script>
                btn.onclick = function send() {
                    window.ipc.postMessage('Hello from webview');
                }
            </script>
        </body>
    </html>
    `,
  preload: `window.onIpcMessage = function(data) {
        const output = document.getElementById('output');
        output.innerText = \`Server Sent A Message: \${data}\`;
    }`,
});

if (!webview.isDevtoolsOpen()) webview.openDevtools();

webview.onIpcMessage((data) => {
  const reply = `You sent ${data.body.toString('utf-8')}`;
  webview.evaluateScript(`onIpcMessage("${reply}")`);
});

app.run();

Closing the Application

You can close the application, windows, and webviews gracefully to ensure all resources (including temporary folders) are cleaned up properly.

const app = new Application();
const window = app.createBrowserWindow();
const webview = window.createWebview({ url: 'https://nodejs.org' });

app.on('application-close-requested', () => {
  console.log('Application is closing, cleaning up resources...');
});

app.on('window-close-requested', () => {
  console.log('Window close requested');
});

// Close the application gracefully (cleans up temp folders)
app.exit();

// Or hide/show the window
window.hide(); // Hide the window
window.show(); // Show the window again

// Or reload the webview
webview.reload();

For more details on closing applications and cleaning up resources, see the Closing Guide.

Keep strong references

Retain BrowserWindow, Webview, WebContext, and TrayIcon wrappers for as long as you need to call their methods or retain their JavaScript listeners. Avoid discarded temporary handles:

const windows = [];

app.whenReady().then(() => {
  const window = app.createBrowserWindow();
  const webview = window.createWebview({ url: 'https://example.com' });
  windows.push({ window, webview });
});

The root Application owns native resources created through it. app.exit(), app[Symbol.dispose](), and application garbage collection dispose those resources in shutdown order. Retained wrappers then report isDisposed() === true, and method calls fail with a disposed error. Individual windows, webviews, contexts, and tray icons also support dispose() and Symbol.dispose.

Check out examples directory for more examples:

Run any example with: node examples/menu-system.mjs (after building the project)

Building executables

[!WARNING] The CLI feature is very experimental and may not work as expected. Please report any issues you find.

The webview CLI compiles your app into a single self-contained executable. The runtime is auto-detected (Bun → bun, Deno → deno, otherwise Node.js), or you can override it:

# Auto-detected runtime
webview --build --input ./path/to/your/script.js --output ./dist --name my-app

# Explicit runtime
webview --build --runtime node --input ./src/index.js --name my-app
webview --build --runtime deno --input ./src/index.ts --name my-app
webview --build --runtime bun  --input ./src/index.ts --name my-app

| Flag | Default | Description | | -------------------- | ------------- | -------------------------- | | --runtime / -R | auto-detected | node, deno, or bun | | --input / -i | ./index.js | Entry file | | --output / -o | ./dist | Output directory | | --name / -n | webviewjs | Executable name | | --resources / -r | — | JSON asset map (node only) |

For the full compilation guide including cross-compilation and code signing, see Building Executables.

Development

Prerequisites

Setup

bun install

Build

bun run build