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

almostnode

v0.2.14

Published

Node.js in your browser. Just like that.

Readme

almostnode

Node.js in your browser. Just like that.

A lightweight, browser-native Node.js runtime environment. Run Node.js code, install npm packages, and develop with Vite or Next.js - all without a server.

MIT License TypeScript Node.js

Built by the creators of Macaly.com — a tool that lets anyone build websites and web apps, even without coding experience. Think Claude Code for non-developers.

Warning: This project is experimental and may contain bugs. Use with caution in production environments.


Features

  • Virtual File System - Full in-memory filesystem with Node.js-compatible API
  • Node.js API Shims - 40+ shimmed modules (fs, path, http, events, and more)
  • npm Package Installation - Install and run real npm packages in the browser with automatic bin stub creation
  • Run Any CLI Tool - npm packages with bin entries (vitest, eslint, tsc, etc.) work automatically
  • Dev Servers - Built-in Vite and Next.js development servers
  • Hot Module Replacement - React Refresh support for instant updates
  • TypeScript Support - First-class TypeScript/TSX transformation via esbuild-wasm
  • Service Worker Architecture - Intercepts requests for seamless dev experience
  • Optional Web Worker Support - Offload code execution to a Web Worker for improved UI responsiveness
  • Secure by Default - Cross-origin sandbox support for running untrusted code safely

Requirements

  • Node.js 20+ - Required for development and building
  • Modern browser - Chrome, Firefox, Safari, or Edge with ES2020+ support

Note: almostnode runs in the browser and emulates Node.js 20 APIs. The Node.js requirement is only for development tooling (Vite, Vitest, TypeScript).


Quick Start

Installation

npm install almostnode

Basic Usage

import { createContainer } from 'almostnode';

// Create a Node.js container in the browser
const container = createContainer();

// Execute JavaScript code directly
const result = container.execute(`
  const path = require('path');
  const fs = require('fs');

  // Use Node.js APIs in the browser!
  fs.writeFileSync('/hello.txt', 'Hello from the browser!');
  module.exports = fs.readFileSync('/hello.txt', 'utf8');
`);

console.log(result.exports); // "Hello from the browser!"

⚠️ Security Warning: The example above runs code on the main thread with full access to your page. Do not use createContainer() or container.execute() with untrusted code. For untrusted code, use createRuntime() with a cross-origin sandbox - see Sandbox Setup.

Running Untrusted Code Securely

import { createRuntime, VirtualFS } from 'almostnode';

const vfs = new VirtualFS();

// Create a secure runtime with cross-origin isolation
const runtime = await createRuntime(vfs, {
  sandbox: 'https://your-sandbox.vercel.app', // Deploy with generateSandboxFiles()
});

// Now it's safe to run untrusted code
const result = await runtime.execute(untrustedCode);

See Sandbox Setup for deployment instructions.

Working with Virtual File System

import { createContainer } from 'almostnode';

const container = createContainer();
const { vfs } = container;

// Pre-populate the virtual filesystem
vfs.writeFileSync('/src/index.js', `
  const data = require('./data.json');
  console.log('Users:', data.users.length);
  module.exports = data;
`);

vfs.writeFileSync('/src/data.json', JSON.stringify({
  users: [{ name: 'Alice' }, { name: 'Bob' }]
}));

// Run from the virtual filesystem
const result = container.runFile('/src/index.js');

With npm Packages

import { createContainer } from 'almostnode';

const container = createContainer();

// Install a package
await container.npm.install('lodash');

// Use it in your code
container.execute(`
  const _ = require('lodash');
  console.log(_.capitalize('hello world'));
`);
// Output: Hello world

Running Shell Commands

import { createContainer } from 'almostnode';

const container = createContainer();

// Write a package.json with scripts
container.vfs.writeFileSync('/package.json', JSON.stringify({
  name: 'my-app',
  scripts: {
    build: 'echo Building...',
    test: 'vitest run'
  }
}));

// Run shell commands directly
const result = await container.run('npm run build');
console.log(result.stdout); // "Building..."

await container.run('npm test');
await container.run('echo hello && echo world');
await container.run('ls /');

Supported npm commands: npm run <script>, npm start, npm test, npm install, npm ls. Pre/post lifecycle scripts (prebuild, postbuild, etc.) run automatically.

Running CLI Tools

Any npm package with a bin field works automatically after install — no configuration needed.

// Install a package that includes a CLI tool
await container.npm.install('vitest');

// Run it directly — bin stubs are created in /node_modules/.bin/
const result = await container.run('vitest run');
console.log(result.stdout); // Test results

This works because npm install reads each package's bin field and creates executable scripts in /node_modules/.bin/. The shell's PATH includes /node_modules/.bin, so tools like vitest, eslint, tsc, etc. resolve automatically.

Streaming Output & Long-Running Commands

For commands that run continuously (like watch mode), use streaming callbacks and abort signals:

const controller = new AbortController();

await container.run('vitest --watch', {
  onStdout: (data) => console.log(data),
  onStderr: (data) => console.error(data),
  signal: controller.signal,
});

// Send input to the running process
container.sendInput('a'); // Press 'a' to re-run all tests

// Stop the command
controller.abort();

With Next.js Dev Server

import { VirtualFS, NextDevServer, getServerBridge } from 'almostnode';

const vfs = new VirtualFS();

// Create a Next.js page
vfs.mkdirSync('/pages', { recursive: true });
vfs.writeFileSync('/pages/index.jsx', `
  import { useState } from 'react';

  export default function Home() {
    const [count, setCount] = useState(0);
    return (
      <div>
        <h1>Count: {count}</h1>
        <button onClick={() => setCount(c => c + 1)}>+</button>
      </div>
    );
  }
`);

// Start the dev server
const server = new NextDevServer(vfs, { port: 3000 });
const bridge = getServerBridge();
await bridge.initServiceWorker();
bridge.registerServer(server, 3000);

// Access at: /__virtual__/3000/

Service Worker Setup

almostnode uses a Service Worker to intercept HTTP requests and route them to virtual dev servers (e.g., ViteDevServer, NextDevServer).

Note: The service worker is only needed if you're using dev servers with URL access (e.g., /__virtual__/3000/). If you're only executing code with runtime.execute(), you don't need the service worker.

Which Setup Do I Need?

| Use Case | Setup Required | |----------|----------------| | Cross-origin sandbox (recommended for untrusted code) | generateSandboxFiles() - includes everything | | Same-origin with Vite | almostnodePlugin from almostnode/vite | | Same-origin with Next.js | getServiceWorkerContent from almostnode/next | | Same-origin with other frameworks | Manual copy to public directory |


Option 1: Cross-Origin Sandbox (Recommended)

When using createRuntime() with a cross-origin sandbox URL, the service worker must be deployed with the sandbox, not your main app.

The generateSandboxFiles() helper generates all required files:

import { generateSandboxFiles } from 'almostnode';
import fs from 'fs';

const files = generateSandboxFiles();

// Creates: index.html, vercel.json, __sw__.js
fs.mkdirSync('sandbox', { recursive: true });
for (const [filename, content] of Object.entries(files)) {
  fs.writeFileSync(`sandbox/${filename}`, content);
}

// Deploy to a different origin:
// cd sandbox && vercel --prod

Generated files: | File | Purpose | |------|---------| | index.html | Sandbox page that loads almostnode and registers the service worker | | vercel.json | CORS headers for cross-origin iframe embedding | | __sw__.js | Service worker for intercepting dev server requests |

See Sandbox Setup for full deployment instructions.


Option 2: Same-Origin with Vite

For trusted code using dangerouslyAllowSameOrigin: true:

// vite.config.ts
import { defineConfig } from 'vite';
import { almostnodePlugin } from 'almostnode/vite';

export default defineConfig({
  plugins: [almostnodePlugin()]
});

The plugin serves /__sw__.js automatically during development.

Custom path:

// vite.config.ts
almostnodePlugin({ swPath: '/custom/__sw__.js' })

// Then in your app:
await bridge.initServiceWorker({ swUrl: '/custom/__sw__.js' });

Option 3: Same-Origin with Next.js

For trusted code using dangerouslyAllowSameOrigin: true:

App Router:

// app/__sw__.js/route.ts
import { getServiceWorkerContent } from 'almostnode/next';

export async function GET() {
  return new Response(getServiceWorkerContent(), {
    headers: {
      'Content-Type': 'application/javascript',
      'Cache-Control': 'no-cache',
    },
  });
}

Pages Router:

// pages/api/__sw__.ts
import { getServiceWorkerContent } from 'almostnode/next';
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.setHeader('Content-Type', 'application/javascript');
  res.setHeader('Cache-Control', 'no-cache');
  res.send(getServiceWorkerContent());
}

Initialize with the correct path:

// App Router (file-based route)
await bridge.initServiceWorker({ swUrl: '/__sw__.js' });

// Pages Router (API route)
await bridge.initServiceWorker({ swUrl: '/api/__sw__' });

Available exports from almostnode/next:

| Export | Description | |--------|-------------| | getServiceWorkerContent() | Returns the service worker file content as a string | | getServiceWorkerPath() | Returns the absolute path to the service worker file |


Option 4: Manual Setup (Other Frameworks)

Copy the service worker to your public directory:

cp node_modules/almostnode/dist/__sw__.js ./public/

Or programmatically:

import { getServiceWorkerPath } from 'almostnode/next';
import fs from 'fs';

fs.copyFileSync(getServiceWorkerPath(), './public/__sw__.js');

Comparison with WebContainers

| Feature | almostnode | WebContainers | |---------|-----------|---------------| | Bundle Size | ~250KB gzipped | ~2MB | | Startup Time | Instant | 2-5 seconds | | Execution Model | Main thread or Web Worker (configurable) | Web Worker isolates | | Shell | just-bash (POSIX subset) | Full Linux kernel | | Native Modules | Stubs only | Full support | | Networking | Virtual ports | Real TCP/IP | | Use Case | Lightweight playgrounds, demos | Full development environments |

When to use almostnode

  • Building code playgrounds or tutorials
  • Creating interactive documentation
  • Prototyping without server setup
  • Educational tools
  • Lightweight sandboxed execution

Example: Code Playground

import { createContainer } from 'almostnode';

function createPlayground() {
  const container = createContainer();

  return {
    run: (code: string) => {
      try {
        const result = container.execute(code);
        return { success: true, result: result.exports };
      } catch (error) {
        return { success: false, error: error.message };
      }
    },
    reset: () => container.runtime.clearCache(),
  };
}

// Usage
const playground = createPlayground();
const output = playground.run(`
  const crypto = require('crypto');
  module.exports = crypto.randomUUID();
`);
console.log(output); // { success: true, result: "550e8400-e29b-..." }

When to use WebContainers

  • Full-fidelity Node.js development
  • Running native modules
  • Complex build pipelines
  • Production-like environments

API Reference

createContainer(options?)

Creates a new container with all components initialized.

interface ContainerOptions {
  cwd?: string;           // Working directory (default: '/')
  env?: Record<string, string>;  // Environment variables
  onConsole?: (method: string, args: any[]) => void;  // Console hook
}

const container = createContainer({
  cwd: '/app',
  env: { NODE_ENV: 'development' },
  onConsole: (method, args) => console.log(`[${method}]`, ...args),
});

Returns:

  • container.vfs - VirtualFS instance
  • container.runtime - Runtime instance
  • container.npm - PackageManager instance
  • container.serverBridge - ServerBridge instance
  • container.run(command, options?) - Run a shell command (returns Promise<RunResult>)
  • container.sendInput(data) - Send stdin data to the currently running process
  • container.execute(code) - Execute JavaScript code
  • container.runFile(filename) - Run a file from VirtualFS

container.run(command, options?)

interface RunResult {
  stdout: string;
  stderr: string;
  exitCode: number;
}

interface RunOptions {
  onStdout?: (data: string) => void;  // Stream stdout in real-time
  onStderr?: (data: string) => void;  // Stream stderr in real-time
  signal?: AbortSignal;                // Cancel the command
}

container.sendInput(data)

Sends data to the stdin of the currently running process. Emits both data and keypress events for compatibility with readline-based tools (e.g., vitest watch mode).

VirtualFS

Node.js-compatible filesystem API.

// Synchronous operations
vfs.writeFileSync(path, content);
vfs.readFileSync(path, encoding?);
vfs.mkdirSync(path, { recursive: true });
vfs.readdirSync(path);
vfs.statSync(path);
vfs.unlinkSync(path);
vfs.rmdirSync(path);
vfs.existsSync(path);
vfs.renameSync(oldPath, newPath);

// Async operations
await vfs.readFile(path, encoding?);
await vfs.stat(path);

// File watching
vfs.watch(path, { recursive: true }, (event, filename) => {
  console.log(`${event}: ${filename}`);
});

Runtime

Execute JavaScript/TypeScript code.

// Execute code string
runtime.execute('console.log("Hello")');

// Run a file from VirtualFS
runtime.runFile('/path/to/file.js');

// Require a module
const module = runtime.require('/path/to/module.js');

createRuntime (Async Runtime Factory)

For advanced use cases, use createRuntime to create a runtime with security options:

import { createRuntime, VirtualFS } from 'almostnode';

const vfs = new VirtualFS();

// RECOMMENDED: Cross-origin sandbox (fully isolated)
const secureRuntime = await createRuntime(vfs, {
  sandbox: 'https://your-sandbox.vercel.app',
});

// For demos/trusted code: Same-origin with explicit opt-in
const demoRuntime = await createRuntime(vfs, {
  dangerouslyAllowSameOrigin: true,
  useWorker: true,  // Optional: run in Web Worker
  cwd: '/project',
  env: { NODE_ENV: 'development' },
});

// Both modes use the same async API
const result = await secureRuntime.execute('module.exports = 1 + 1;');
console.log(result.exports); // 2

Security Modes

| Mode | Option | Security Level | Use Case | |------|--------|----------------|----------| | Cross-origin sandbox | sandbox: 'https://...' | Highest | Production, untrusted code | | Same-origin Worker | dangerouslyAllowSameOrigin: true, useWorker: true | Medium | Demos with trusted code | | Same-origin main thread | dangerouslyAllowSameOrigin: true | Lowest | Trusted code only |

Security by default: createRuntime() throws an error if neither sandbox nor dangerouslyAllowSameOrigin is provided.


Sandbox Setup

For running untrusted code securely, deploy a cross-origin sandbox. The key requirement is that the sandbox must be served from a different origin (different domain, subdomain, or port).

Quick Setup (Vercel)

import { generateSandboxFiles } from 'almostnode';
import fs from 'fs';

const files = generateSandboxFiles();
// Generates: index.html, vercel.json, __sw__.js

fs.mkdirSync('sandbox', { recursive: true });
for (const [filename, content] of Object.entries(files)) {
  fs.writeFileSync(`sandbox/${filename}`, content);
}

// Deploy: cd sandbox && vercel --prod

The generated files include:

  • index.html - Sandbox page with service worker registration
  • vercel.json - CORS headers for cross-origin iframe embedding
  • __sw__.js - Service worker for dev server URL access

Manual Setup (Any Platform)

The sandbox requires two things:

1. The sandbox HTML page

Create an index.html that loads almostnode and handles postMessage:

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body>
<script type="module">
  import { VirtualFS, Runtime } from 'https://unpkg.com/almostnode/dist/index.js';

  let vfs = null;
  let runtime = null;

  window.addEventListener('message', async (event) => {
    const { type, id, code, filename, vfsSnapshot, options, path, content } = event.data;

    try {
      switch (type) {
        case 'init':
          vfs = VirtualFS.fromSnapshot(vfsSnapshot);
          runtime = new Runtime(vfs, {
            cwd: options?.cwd,
            env: options?.env,
            onConsole: (method, args) => {
              parent.postMessage({ type: 'console', consoleMethod: method, consoleArgs: args }, '*');
            },
          });
          break;
        case 'execute':
          const result = runtime.execute(code, filename);
          parent.postMessage({ type: 'result', id, result }, '*');
          break;
        case 'runFile':
          const runResult = runtime.runFile(filename);
          parent.postMessage({ type: 'result', id, result: runResult }, '*');
          break;
        case 'syncFile':
          if (content === null) { try { vfs.unlinkSync(path); } catch {} }
          else { vfs.writeFileSync(path, content); }
          break;
        case 'clearCache':
          runtime?.clearCache();
          break;
      }
    } catch (error) {
      if (id) parent.postMessage({ type: 'error', id, error: error.message }, '*');
    }
  });

  parent.postMessage({ type: 'ready' }, '*');
</script>
</body>
</html>

2. Required HTTP headers

The sandbox server must include these headers:

Access-Control-Allow-Origin: *
Cross-Origin-Resource-Policy: cross-origin

Example configurations:

server {
    listen 3002;
    root /path/to/sandbox;

    location / {
        add_header Access-Control-Allow-Origin *;
        add_header Cross-Origin-Resource-Policy cross-origin;
    }
}
Header set Access-Control-Allow-Origin "*"
Header set Cross-Origin-Resource-Policy "cross-origin"
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
  next();
});
app.use(express.static('sandbox'));
app.listen(3002);
from http.server import HTTPServer, SimpleHTTPRequestHandler

class CORSHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Cross-Origin-Resource-Policy', 'cross-origin')
        super().end_headers()

HTTPServer(('', 3002), CORSHandler).serve_forever()

Use in your app

const runtime = await createRuntime(vfs, {
  sandbox: 'https://sandbox.yourdomain.com',  // Must be different origin!
});

// Code runs in isolated cross-origin iframe
const result = await runtime.execute(untrustedCode);

Local Development

For local testing, run the sandbox on a different port:

# Terminal 1: Main app on port 5173
npm run dev

# Terminal 2: Sandbox on port 3002
npm run sandbox

Then use sandbox: 'http://localhost:3002/sandbox/' in your app.

What cross-origin sandbox protects

| Threat | Status | |--------|--------| | Cookies | Blocked (different origin) | | localStorage | Blocked (different origin) | | IndexedDB | Blocked (different origin) | | DOM access | Blocked (cross-origin iframe) |

Note: Network requests from the sandbox are still possible. Add CSP headers for additional protection.

PackageManager

Install npm packages.

// Install a package
await npm.install('react');
await npm.install('[email protected]');

// Install multiple packages
await npm.install(['react', 'react-dom']);

Supported Node.js APIs

967 compatibility tests verify our Node.js API coverage.

Fully Shimmed Modules

| Module | Tests | Coverage | Notes | |--------|-------|----------|-------| | path | 219 | High | POSIX paths (no Windows) | | buffer | 95 | High | All common operations | | fs | 76 | High | Sync + promises API | | url | 67 | High | WHATWG URL + legacy parser | | util | 77 | High | format, inspect, promisify | | process | 60 | High | env, cwd, hrtime, EventEmitter | | events | 50 | High | Full EventEmitter API | | os | 58 | High | Platform info (simulated) | | crypto | 57 | High | Hash, HMAC, random, sign/verify | | querystring | 52 | High | parse, stringify, escape | | stream | 44 | Medium | Readable, Writable, Transform | | zlib | 39 | High | gzip, deflate, brotli | | tty | 40 | High | ReadStream, WriteStream | | perf_hooks | 33 | High | Performance API |

Stubbed Modules

These modules export empty objects or no-op functions:

  • net, tls, dns, dgram
  • cluster, worker_threads
  • vm, v8, inspector
  • async_hooks

Framework Support

Vite

import { VirtualFS, ViteDevServer, getServerBridge } from 'almostnode';

const vfs = new VirtualFS();

// Create a React app
vfs.writeFileSync('/index.html', `
  <!DOCTYPE html>
  <html>
    <body>
      <div id="root"></div>
      <script type="module" src="/src/main.jsx"></script>
    </body>
  </html>
`);

vfs.mkdirSync('/src', { recursive: true });
vfs.writeFileSync('/src/main.jsx', `
  import React from 'react';
  import ReactDOM from 'react-dom/client';

  function App() {
    return <h1>Hello Vite!</h1>;
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
`);

// Start Vite dev server
const server = new ViteDevServer(vfs, { port: 5173 });

Next.js

Supports both Pages Router and App Router:

Pages Router

/pages
  /index.jsx      → /
  /about.jsx      → /about
  /users/[id].jsx → /users/:id
  /api/hello.js   → /api/hello

App Router

/app
  /layout.jsx           → Root layout
  /page.jsx             → /
  /about/page.jsx       → /about
  /users/[id]/page.jsx  → /users/:id

Hot Module Replacement (HMR)

almostnode includes built-in Hot Module Replacement support for instant updates during development. When you edit files, changes appear immediately in the preview without a full page reload.

How It Works

HMR is automatically enabled when using NextDevServer or ViteDevServer. The system uses:

  1. VirtualFS file watching - Detects file changes via vfs.watch()
  2. postMessage API - Communicates updates between the main page and preview iframe
  3. React Refresh - Preserves React component state during updates
// HMR works automatically - just edit files and save
vfs.writeFileSync('/app/page.tsx', updatedContent);
// The preview iframe will automatically refresh with the new content

Setup Requirements

For security, the preview iframe should be sandboxed. HMR uses postMessage for communication, which works correctly with sandboxed iframes:

// Create sandboxed iframe for security
const iframe = document.createElement('iframe');
iframe.src = '/__virtual__/3000/';
// Sandbox restricts the iframe's capabilities - add only what you need
iframe.sandbox = 'allow-forms allow-scripts allow-same-origin allow-popups';
container.appendChild(iframe);

// Register the iframe as HMR target after it loads
iframe.onload = () => {
  if (iframe.contentWindow) {
    devServer.setHMRTarget(iframe.contentWindow);
  }
};

Recommended sandbox permissions:

  • allow-scripts - Required for JavaScript execution
  • allow-same-origin - Allows the iframe to access cookies, localStorage, and IndexedDB (only add if your app needs these; omit for better isolation)
  • allow-forms - If your app uses forms
  • allow-popups - If your app opens new windows/tabs

Note: The service worker intercepts /__virtual__/ requests at the origin level, not the iframe level. The allow-same-origin attribute does NOT affect service worker functionality. For maximum security isolation, consider using cross-origin sandbox mode (see below) which doesn't use allow-same-origin.

Manual HMR Triggering

If you need to manually trigger HMR updates (e.g., after programmatic file changes):

function triggerHMR(path: string, iframe: HTMLIFrameElement): void {
  if (iframe.contentWindow) {
    iframe.contentWindow.postMessage({
      type: 'update',
      path,
      timestamp: Date.now(),
      channel: 'next-hmr', // Use 'vite-hmr' for Vite
    }, '*');
  }
}

// After writing a file
vfs.writeFileSync('/app/page.tsx', newContent);
triggerHMR('/app/page.tsx', iframe);

Supported File Types

| File Type | HMR Behavior | |-----------|--------------| | .jsx, .tsx | React Refresh (preserves state) | | .js, .ts | Full module reload | | .css | Style injection (no reload) | | .json | Full page reload |


Demos

Start the dev server with npm run dev and open any demo at http://localhost:5173:

| Demo | Path | Description | |------|------|-------------| | Next.js | /examples/next-demo.html | Pages & App Router, CSS modules, route groups, API routes, HMR | | Vite | /examples/vite-demo.html | Vite dev server with React and HMR | | Vitest | /examples/vitest-demo.html | Real vitest execution with xterm.js terminal and watch mode | | Express | /examples/express-demo.html | Express.js HTTP server running in the browser | | Convex | /examples/demo-convex-app.html | Real-time todo app with Convex cloud deployment | | Vercel AI SDK | /examples/demo-vercel-ai-sdk.html | Streaming AI chatbot with Next.js and OpenAI | | Bash | /examples/bash-demo.html | Interactive POSIX shell emulator |


Development

Setup

git clone https://github.com/macaly/almostnode.git
cd almostnode
npm install

Run Tests

# Unit tests
npm test

# E2E tests (requires Playwright)
npm run test:e2e

Development Server

npm run dev

See the Demos section for all available examples.


Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE for details.


Acknowledgments