SkillAgentSearch skills...

Almostnode

Node.js in your browser. Just like that.

Install / Use

/learn @macaly/Almostnode
About this skill

Quality Score

0/100

Supported Platforms

Universal

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.se
View on GitHub
GitHub Stars1.0k
CategoryDevelopment
Updated1h ago
Forks85

Languages

TypeScript

Security Score

100/100

Audited on Mar 27, 2026

No findings