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

clientbox

v0.2.2

Published

Pure client-side, in-browser code execution engine for JavaScript, TypeScript, Python, HTML/CSS/JS, C#, Java, PHP, Dart, and Go

Downloads

155

Readme


Install

npm install clientbox

Quick start

import { ClientBox } from "clientbox";

const box = new ClientBox();

const result = await box.run("node", {
  files: { "/index.js": 'console.log("Hello from clientbox!")' },
  entryPoint: "/index.js",
});

console.log(result.stdout); // "Hello from clientbox!"

box.destroy();

Supported languages

| Language | Key | Runtime | | ----------- | -------- | ----------------------------------- | | JavaScript | node | Web Worker + virtual FS | | TypeScript | node | Web Worker + type-stripping | | Python | python | Pyodide (CDN, ~12 MB first load) | | HTML/CSS/JS | web | Sandboxed iframe | | C# | csharp | Transpiler (C#-to-JS in iframe) | | Java | java | CheerpJ (CDN) + transpiler fallback | | PHP | php | Transpiler (PHP-to-JS in iframe) | | Dart | dart | Transpiler (Dart-to-JS in iframe) | | Go | go | Transpiler (Go-to-JS in iframe) |

Usage examples

JavaScript (Node-style)

Multi-file with require:

const result = await box.run("node", {
  files: {
    "/index.js": `
      const { greet } = require('./utils');
      console.log(greet('world'));
    `,
    "/utils.js": `
      module.exports = {
        greet: (name) => \`Hello, \${name}!\`,
      };
    `,
  },
  entryPoint: "/index.js",
});
// stdout: "Hello, world!"

ESM imports are also supported:

const result = await box.run("node", {
  files: {
    "/index.js": `
      import { add } from './math.js';
      console.log(add(2, 3));
    `,
    "/math.js": `
      export const add = (a, b) => a + b;
    `,
  },
  entryPoint: "/index.js",
});
// stdout: "5"

Python

const result = await box.run("python", {
  files: {
    "/main.py": `
import utils

for i in range(5):
    print(utils.square(i))
    `,
    "/utils.py": `
def square(n):
    return n * n
    `,
  },
  entryPoint: "/main.py",
});
// stdout: "0\n1\n4\n9\n16"

HTML/CSS/JS (Web)

const result = await box.run("web", {
  files: {
    "/index.html": `
      <!DOCTYPE html>
      <html>
        <head><link rel="stylesheet" href="style.css"></head>
        <body>
          <h1>Hello</h1>
          <script src="app.js"></script>
        </body>
      </html>
    `,
    "/style.css": "h1 { color: blue; }",
    "/app.js": 'console.log("page loaded");',
  },
  entryPoint: "/index.html",
});
// stdout: "page loaded"

C#

const result = await box.run("csharp", {
  files: {
    "/Program.cs": `
using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello from C#!");
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"i = {i}");
        }
    }
}
    `,
  },
  entryPoint: "/Program.cs",
});
// stdout: "Hello from C#!\ni = 0\ni = 1\ni = 2\ni = 3\ni = 4"

Java

const result = await box.run("java", {
  files: {
    "/Main.java": `
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello from Java!");
        for (int i = 0; i < 5; i++) {
            System.out.println("i = " + i);
        }
    }
}
    `,
  },
  entryPoint: "/Main.java",
});
// stdout: "Hello from Java!\ni = 0\ni = 1\ni = 2\ni = 3\ni = 4"

PHP

const result = await box.run("php", {
  files: {
    "/index.php": `
<?php
function factorial($n) {
    if ($n <= 1) return 1;
    return $n * factorial($n - 1);
}

echo "Hello from PHP!\\n";
for ($i = 1; $i <= 5; $i++) {
    echo "Factorial of $i = " . factorial($i) . "\\n";
}
?>
    `,
  },
  entryPoint: "/index.php",
});
// stdout: "Hello from PHP!\nFactorial of 1 = 1\n..."

Dart

const result = await box.run("dart", {
  files: {
    "/main.dart": `
void main() {
  print('Hello from Dart!');
  for (int i = 1; i <= 5; i++) {
    print('Fibonacci(\$i) = \${fib(i)}');
  }
}

int fib(int n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}
    `,
  },
  entryPoint: "/main.dart",
});
// stdout: "Hello from Dart!\nFibonacci(1) = 1\n..."

Go

const result = await box.run("go", {
  files: {
    "/main.go": `
package main

import "fmt"

func main() {
    for i := 1; i <= 5; i++ {
        fmt.Printf("%d! = %d\\n", i, factorial(i))
    }
}

func factorial(n int) int {
    if n <= 1 { return 1 }
    return n * factorial(n-1)
}
    `,
  },
  entryPoint: "/main.go",
});
// stdout: "1! = 1\n2! = 2\n3! = 6\n4! = 24\n5! = 120"

Interactive input

User code can request input at runtime (e.g. Python's input(), C#'s Console.ReadLine(), Java's Scanner). Pass an onInput callback to run() and the runner will pause execution until you return a value. Pair it with onStdout / onStderr so the prompt text actually streams to your UI before the program blocks on input.

Minimal example

const result = await box.run("python", {
  files: {
    "/main.py": `
name = input("What is your name? ")
print(f"Hello, {name}!")
    `,
  },
  entryPoint: "/main.py",
  onStdout: (chunk) => process.stdout.write(chunk), // or terminal.write(chunk) in browser
  onStderr: (chunk) => process.stderr.write(chunk),
  onInput: (prompt) => window.prompt(prompt) ?? null, // return null to signal EOF
});

onInput is invoked with the stdout produced since the last input request, which is typically the prompt text the program just wrote. Return a string (no trailing newline needed) or null for EOF.

Pre-supplied stdin

You can still pre-feed input as a string. Pre-supplied lines are consumed first; onInput is only called once the pre-supplied input is exhausted.

await box.run("python", {
  files: { "/main.py": "a = input(); b = input(); print(int(a) + int(b))" },
  entryPoint: "/main.py",
  stdin: "2\n3", // both inputs supplied — onInput never fires
});

Streaming output without input

onStdout / onStderr work independently. Use them to render output as the program produces it, instead of waiting for the final aggregated result.stdout:

await box.run("python", {
  files: {
    "/main.py":
      "import time\nfor i in range(5):\n    print(i); time.sleep(0.2)",
  },
  entryPoint: "/main.py",
  onStdout: (chunk) => appendToTerminal(chunk),
});

Language-specific input calls

The host-facing API is the same for every language — only the user code differs:

| Language | What the user code calls | | -------- | ------------------------------------------------------------------------------- | | Python | input(prompt) | | Node/TS | prompt(message) (exposed as a global by the Node runner) | | C# | Console.ReadLine() | | Java | new Scanner(System.in).nextLine() / nextInt() / nextDouble() | | PHP | readline($prompt) or fgets(STDIN) | | Dart | stdin.readLineSync() | | Go | bufio.NewReader(os.Stdin).ReadString('\n'), fmt.Scanln(&x) | | Web | window.prompt() works natively inside the iframe — no onInput wiring needed |

Cross-origin isolation requirement (Python & Node only)

Python and Node make input look synchronous to user code by blocking the runtime's Web Worker on a SharedArrayBuffer. Browsers only expose SharedArrayBuffer when the host page is cross-origin isolated. Serve the page with these response headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: credentialless

(require-corp works too, but credentialless is friendlier — it doesn't force every cross-origin resource to send Cross-Origin-Resource-Policy.)

If the headers are missing, calling box.run("python" | "node", ...) with an onInput callback rejects with a clear error telling you what to fix. Iframe-based runners (C#, Java, PHP, Dart, Go) use async postMessage instead of SharedArrayBuffer and have no such requirement.

// Quick check before calling run()
if (!window.crossOriginIsolated) {
  console.warn("Interactive Python/Node input requires COOP/COEP headers");
}

Can't set server headers? Use a service worker

If you're deploying to a static host you don't control (GitHub Pages, Netlify free tier, etc.), a small service worker can inject the headers client-side. Drop the coi-serviceworker.js file in this repo at your site root and register it before anything else loads:

<head>
  <!-- Must come before any other script that needs SharedArrayBuffer -->
  <script src="/coi-serviceworker.js"></script>
  ...
</head>

On first visit the page reloads once to activate the worker. After that, window.crossOriginIsolated === true and Python/Node interactive input works. The bundle docs/index.html demos both use this approach.

Live demo

Try the interactive playground at yaman-cyber.github.io/clientbox -- no install required, runs entirely in your browser.

API reference

new ClientBox(config?)

Create a new execution engine instance.

interface ClientBoxConfig {
  timeout?: number; // Default execution timeout (ms). Default: 30000
  pyodideCdnUrl?: string; // Override Pyodide CDN base URL
  cheerpjCdnUrl?: string; // Override CheerpJ loader script URL
  dotnetCdnUrl?: string; // Override .NET WASM CDN base URL
  onStatusChange?: (event: StatusEvent) => void;
}

box.run(language, options)

Execute code and return the result.

type Language =
  | "node"
  | "python"
  | "web"
  | "csharp"
  | "java"
  | "php"
  | "dart"
  | "go";

interface RunOptions {
  files: Record<string, string>; // Virtual file path -> content
  entryPoint: string; // File to execute
  stdin?: string; // Pre-supplied stdin (consumed before onInput is called)
  timeout?: number; // Override timeout for this run

  // Interactive input: called when the program requests a line of input.
  // Return a string for the next line, or null to signal EOF.
  // The `prompt` argument is the stdout produced since the last input request.
  // See the "Interactive input" section above for per-language details and the
  // cross-origin isolation requirement for the python/node runners.
  onInput?: (prompt: string) => string | null | Promise<string | null>;

  // Streamed output, chunk by chunk, as the program produces it.
  // The final aggregated values are still returned in RunResult.stdout/stderr.
  onStdout?: (chunk: string) => void;
  onStderr?: (chunk: string) => void;
}

interface RunResult {
  stdout: string; // Captured standard output
  stderr: string; // Captured standard error
  error: string | null;
  exitCode: number; // 0 = success
  duration: number; // Wall-clock ms
}

box.destroy()

Tear down all workers and iframes. The instance cannot be reused after this.

Framework integration

clientbox is a zero-dependency TypeScript library that works with any framework or vanilla JS.

React / Next.js

"use client";
import { useCallback, useRef } from "react";
import { ClientBox } from "clientbox";

export function CodeRunner() {
  const boxRef = useRef<ClientBox | null>(null);

  const runCode = useCallback(async () => {
    if (!boxRef.current) boxRef.current = new ClientBox();
    const result = await boxRef.current.run("node", {
      files: { "/index.js": 'console.log("hello")' },
      entryPoint: "/index.js",
    });
    console.log(result.stdout);
  }, []);

  return <button onClick={runCode}>Run</button>;
}

Vue

<script setup lang="ts">
import { ref, onUnmounted } from "vue";
import { ClientBox } from "clientbox";

const box = new ClientBox();
const output = ref("");

async function run() {
  const result = await box.run("python", {
    files: { "/main.py": 'print("Hello from Python!")' },
    entryPoint: "/main.py",
  });
  output.value = result.stdout;
}

onUnmounted(() => box.destroy());
</script>

<template>
  <button @click="run">Run Python</button>
  <pre>{{ output }}</pre>
</template>

Svelte

<script lang="ts">
  import { ClientBox } from 'clientbox';
  import { onDestroy } from 'svelte';

  const box = new ClientBox();
  let output = '';

  async function run() {
    const result = await box.run('node', {
      files: { '/index.js': 'console.log(2 + 2)' },
      entryPoint: '/index.js',
    });
    output = result.stdout;
  }

  onDestroy(() => box.destroy());
</script>

<button on:click={run}>Run</button>
<pre>{output}</pre>

How it works

| Language | Execution strategy | | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | node | Web Worker with patched console, custom require(), ESM import, and in-memory virtual FS. TypeScript files are type-stripped before execution. | | python | Pyodide (CPython compiled to WebAssembly) running in a Web Worker. Loaded from CDN on first use (~12 MB). | | web | Sandboxed <iframe> with sandbox="allow-scripts". CSS/JS files are inlined via blob URLs. | | csharp | Iframe harness that transpiles C# to JS. Handles Console.WriteLine, string interpolation, foreach, multi-file class resolution, and more. | | java | Iframe with CheerpJ (WebAssembly JVM) from CDN + transpiler fallback. Handles System.out, typed variables, for-each, multi-class projects. | | php | Iframe harness that transpiles PHP to JS. Supports echo, $variables, string concatenation, foreach, arrays, and common stdlib functions. | | dart | Iframe harness that transpiles Dart to JS. Supports print, string interpolation, typed variables, collections, and function declarations. | | go | Iframe harness that transpiles Go to JS. Supports fmt.Print/Println/Printf, for/range loops, slices, maps, multi-file packages, and stdlib stubs. |

Runtimes for Python and Java are loaded lazily from CDN only when first needed. All other runners require no external downloads.

SSR safety

The package can be safely imported in server-side environments (Next.js, Nuxt, SvelteKit SSR). All browser APIs are only accessed when run() is called. If run() is called outside a browser, a clear error is thrown.

License

MIT