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

@devscholar/node-with-window

v0.0.0

Published

Cross-platform window library with WebView support

Readme

Node with Window

⚠️ This project is still in pre-alpha stage, expect breaking changes.

A cross-platform windowing library for Node.js/Deno/Bun with an Electron-compatible API. Uses node-ps1-dotnet (WPF + WebView2) on Windows and node-with-gjs (GTK + WebKitGTK) on Linux.

Prerequisites

Windows

  • Node.js 18+

  • PowerShell 5.1

  • .NET Framework 4.8

  • WebView2 runtime (pre-installed on Windows 11; install from Microsoft on Windows 10)

  • WebView2 SDK DLLs in runtimes/webview2/:

    • Microsoft.Web.WebView2.Core.dll
    • Microsoft.Web.WebView2.Wpf.dll

    Install via the parent project's install script:

    node ../node-ps1-dotnet/scripts/webview2-install.js install

    Then copy or symlink the resulting DLLs into runtimes/webview2/.

Linux

  • Node.js 18+
  • GJS (GNOME JavaScript runtime)
  • GTK 4
  • WebKitGTK 6.0

These are typically pre-installed on Ubuntu 24.04 LTS / GNOME desktops. If missing:

sudo apt install gjs gir1.2-gtk-4.0 gir1.2-webkit2-6.0

WebKit sandbox in virtual machines

When running inside a VMware (or similar) virtual machine, WebKitGTK's bubblewrap sandbox may fail with Permission denied because the VM kernel restricts unprivileged user namespaces:

bwrap: setting up uid map: Permission denied
Failed to fully launch dbus-proxy

node-with-window automatically sets WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1 when spawning the GJS host, so this error is suppressed by default. On production systems that support user namespaces, you can instead enable the sandbox properly:

sudo sysctl -w kernel.unprivileged_userns_clone=1
# To persist across reboots:
echo 'kernel.unprivileged_userns_clone=1' | sudo tee /etc/sysctl.d/99-userns.conf

Install

npm install

Examples

See node-with-window-examples.

Windows

cd ../node-with-window-examples
npm install
npm run notepad

Linux

If you are copying files from another machine (e.g. a Windows shared folder into a VM), do a clean install to avoid stale platform-specific binaries:

cd ../node-with-window-examples
rm -rf dist node_modules
npm install

npm install triggers a postinstall script that compiles @devscholar/node-with-gjs (used internally) from its TypeScript source using the bundled esbuild.

Then run:

npm run notepad

The WebKit sandbox is disabled automatically by the library (see WebKit sandbox in virtual machines).

API

The API mirrors Electron — replace import ... from 'electron' with import ... from '@devscholar/node-with-window'.

Writing your own app

  1. Create a new project and install node-with-window:
mkdir myapp
cd myapp
npm init -y
npm install /path/to/node-with-window
  1. Create main.ts:
import { app, BrowserWindow, ipcMain } from 'node-with-window';
import * as path from 'node:path';
import * as url from 'node:url';

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

app.whenReady().then(() => {
    ipcMain.handle('greet', (event, name: string) => `Hello, ${name}!`);

    const win = new BrowserWindow({ 
        title: 'My App', 
        width: 600, 
        height: 400,
        webPreferences: {
            nodeIntegration: true
        }
    });
    win.loadFile(path.join(__dirname, 'index.html'));
    win.show();
});
  1. Create index.html (no need to add the bridge script manually):
<!DOCTYPE html>
<html>
<body>
    <div id="output">Loading...</div>
    <script>
        // Use Node.js APIs directly when nodeIntegration is enabled
        const fs = require('fs');
        const path = require('path');
        
        window.ipcRenderer.invoke('greet', 'world').then(msg => {
            document.getElementById('output').textContent = msg;
        });
        
        console.log('Current directory:', process.cwd());
    </script>
</body>
</html>
  1. Run using the start.js helper:
node /path/to/node-with-window/start.js main.ts

Developing

This library depends on node-ps1-dotnet (Windows) and node-with-gjs (Linux), both installed as file: symlinks in node_modules. After changing either dependency, rebuild in order:

# From the repo root (c:/amateur-programming or ~/amateur-programming):
node rebuild.js          # incremental rebuild
node rebuild.js --clean  # wipe all dist/ folders first, then rebuild

The script builds node-ps1-dotnet then node-with-window. Because node-with-window-examples resolves both via file: symlinks, no copy step is needed — the rebuilt dist/ is picked up immediately.

If you only changed node-with-window itself:

cd node-with-window && npm run build

How it works

Windows (WPF + WebView2)

  • The main process communicates with a PowerShell-hosted .NET runtime over a Windows Named Pipe via the node-ps1-dotnet bridge
  • show() sends a StartApplication command to the .NET host, which immediately acknowledges and then calls Application.Run(window) — blocking the .NET thread in the WPF message loop without blocking the Node.js event loop
  • Node.js polls for events every 16 ms with a Poll command that drains a thread-safe queue. WPF event handlers (like WebMessageReceived) enqueue their payload instead of blocking on synchronous IPC, so async ipcMain.handle() callbacks work normally
  • When loadFile is called before show(), the HTML is read, the ipcRenderer bridge is injected into <head>, and the modified HTML is written to a temp file. webView.Source is set to this temp file URL before Application.Run() — identical to the one-navigation pattern in the reference webview2-browser.ts example
  • The WebMessageReceived handler enqueues the raw JSON payload. The next Poll delivers it to Node.js, which dispatches it to the registered ipcMain handler and sends the reply via PostWebMessageAsString
  • Node integration uses NODE_WITH_WINDOW: prefixed messages via chrome.webview.postMessage() to provide access to Node.js APIs in the renderer

Linux (GJS + GTK 4 + WebKitGTK)

  • LinuxWindow spawns a dedicated GJS script (scripts/linux/host.js) as a child process. The host script runs the GTK 4 main loop (GLib.MainLoop) and owns the Gtk.Window + WebKit.WebView.
  • Node.js and the GJS host communicate over two Unix FIFOs (passed as fd 3 and fd 4) using a synchronous newline-delimited JSON request/response protocol — the same as the Windows pipe bridge.
  • WebKit → Node.js IPC: the HTML renderer posts messages via window.webkit.messageHandlers.ipc.postMessage(json). The GJS host queues them. Node.js drains the queue every 16 ms with a Poll command.
  • Node.js → WebKit IPC: replies and push messages are delivered by sending a SendToRenderer command, which calls webView.evaluate_javascript() in GJS.
  • Async ipcMain.handle() handlers are fully supported (the Node.js event loop stays alive between polls).
  • File/message dialogs are implemented natively with Gtk.FileChooserDialog / Gtk.MessageDialog using a nested GLib.MainContext.iteration() loop for synchronous behaviour without blocking IPC.