almostnode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +731 -0
  3. package/dist/__sw__.js +394 -0
  4. package/dist/ai-chatbot-demo-entry.d.ts +6 -0
  5. package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
  6. package/dist/ai-chatbot-demo.d.ts +42 -0
  7. package/dist/ai-chatbot-demo.d.ts.map +1 -0
  8. package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
  9. package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
  10. package/dist/convex-app-demo-entry.d.ts +6 -0
  11. package/dist/convex-app-demo-entry.d.ts.map +1 -0
  12. package/dist/convex-app-demo.d.ts +68 -0
  13. package/dist/convex-app-demo.d.ts.map +1 -0
  14. package/dist/cors-proxy.d.ts +46 -0
  15. package/dist/cors-proxy.d.ts.map +1 -0
  16. package/dist/create-runtime.d.ts +42 -0
  17. package/dist/create-runtime.d.ts.map +1 -0
  18. package/dist/demo.d.ts +6 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/dev-server.d.ts +97 -0
  21. package/dist/dev-server.d.ts.map +1 -0
  22. package/dist/frameworks/next-dev-server.d.ts +202 -0
  23. package/dist/frameworks/next-dev-server.d.ts.map +1 -0
  24. package/dist/frameworks/vite-dev-server.d.ts +85 -0
  25. package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
  26. package/dist/index.cjs +14965 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.ts +71 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.mjs +14867 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/next-demo.d.ts +49 -0
  33. package/dist/next-demo.d.ts.map +1 -0
  34. package/dist/npm/index.d.ts +71 -0
  35. package/dist/npm/index.d.ts.map +1 -0
  36. package/dist/npm/registry.d.ts +66 -0
  37. package/dist/npm/registry.d.ts.map +1 -0
  38. package/dist/npm/resolver.d.ts +52 -0
  39. package/dist/npm/resolver.d.ts.map +1 -0
  40. package/dist/npm/tarball.d.ts +29 -0
  41. package/dist/npm/tarball.d.ts.map +1 -0
  42. package/dist/runtime-interface.d.ts +90 -0
  43. package/dist/runtime-interface.d.ts.map +1 -0
  44. package/dist/runtime.d.ts +103 -0
  45. package/dist/runtime.d.ts.map +1 -0
  46. package/dist/sandbox-helpers.d.ts +43 -0
  47. package/dist/sandbox-helpers.d.ts.map +1 -0
  48. package/dist/sandbox-runtime.d.ts +65 -0
  49. package/dist/sandbox-runtime.d.ts.map +1 -0
  50. package/dist/server-bridge.d.ts +89 -0
  51. package/dist/server-bridge.d.ts.map +1 -0
  52. package/dist/shims/assert.d.ts +51 -0
  53. package/dist/shims/assert.d.ts.map +1 -0
  54. package/dist/shims/async_hooks.d.ts +37 -0
  55. package/dist/shims/async_hooks.d.ts.map +1 -0
  56. package/dist/shims/buffer.d.ts +20 -0
  57. package/dist/shims/buffer.d.ts.map +1 -0
  58. package/dist/shims/child_process-browser.d.ts +92 -0
  59. package/dist/shims/child_process-browser.d.ts.map +1 -0
  60. package/dist/shims/child_process.d.ts +93 -0
  61. package/dist/shims/child_process.d.ts.map +1 -0
  62. package/dist/shims/chokidar.d.ts +55 -0
  63. package/dist/shims/chokidar.d.ts.map +1 -0
  64. package/dist/shims/cluster.d.ts +52 -0
  65. package/dist/shims/cluster.d.ts.map +1 -0
  66. package/dist/shims/crypto.d.ts +122 -0
  67. package/dist/shims/crypto.d.ts.map +1 -0
  68. package/dist/shims/dgram.d.ts +34 -0
  69. package/dist/shims/dgram.d.ts.map +1 -0
  70. package/dist/shims/diagnostics_channel.d.ts +80 -0
  71. package/dist/shims/diagnostics_channel.d.ts.map +1 -0
  72. package/dist/shims/dns.d.ts +87 -0
  73. package/dist/shims/dns.d.ts.map +1 -0
  74. package/dist/shims/domain.d.ts +25 -0
  75. package/dist/shims/domain.d.ts.map +1 -0
  76. package/dist/shims/esbuild.d.ts +105 -0
  77. package/dist/shims/esbuild.d.ts.map +1 -0
  78. package/dist/shims/events.d.ts +37 -0
  79. package/dist/shims/events.d.ts.map +1 -0
  80. package/dist/shims/fs.d.ts +115 -0
  81. package/dist/shims/fs.d.ts.map +1 -0
  82. package/dist/shims/fsevents.d.ts +67 -0
  83. package/dist/shims/fsevents.d.ts.map +1 -0
  84. package/dist/shims/http.d.ts +217 -0
  85. package/dist/shims/http.d.ts.map +1 -0
  86. package/dist/shims/http2.d.ts +81 -0
  87. package/dist/shims/http2.d.ts.map +1 -0
  88. package/dist/shims/https.d.ts +36 -0
  89. package/dist/shims/https.d.ts.map +1 -0
  90. package/dist/shims/inspector.d.ts +25 -0
  91. package/dist/shims/inspector.d.ts.map +1 -0
  92. package/dist/shims/module.d.ts +22 -0
  93. package/dist/shims/module.d.ts.map +1 -0
  94. package/dist/shims/net.d.ts +100 -0
  95. package/dist/shims/net.d.ts.map +1 -0
  96. package/dist/shims/os.d.ts +159 -0
  97. package/dist/shims/os.d.ts.map +1 -0
  98. package/dist/shims/path.d.ts +72 -0
  99. package/dist/shims/path.d.ts.map +1 -0
  100. package/dist/shims/perf_hooks.d.ts +50 -0
  101. package/dist/shims/perf_hooks.d.ts.map +1 -0
  102. package/dist/shims/process.d.ts +93 -0
  103. package/dist/shims/process.d.ts.map +1 -0
  104. package/dist/shims/querystring.d.ts +23 -0
  105. package/dist/shims/querystring.d.ts.map +1 -0
  106. package/dist/shims/readdirp.d.ts +52 -0
  107. package/dist/shims/readdirp.d.ts.map +1 -0
  108. package/dist/shims/readline.d.ts +62 -0
  109. package/dist/shims/readline.d.ts.map +1 -0
  110. package/dist/shims/rollup.d.ts +34 -0
  111. package/dist/shims/rollup.d.ts.map +1 -0
  112. package/dist/shims/sentry.d.ts +163 -0
  113. package/dist/shims/sentry.d.ts.map +1 -0
  114. package/dist/shims/stream.d.ts +181 -0
  115. package/dist/shims/stream.d.ts.map +1 -0
  116. package/dist/shims/tls.d.ts +53 -0
  117. package/dist/shims/tls.d.ts.map +1 -0
  118. package/dist/shims/tty.d.ts +30 -0
  119. package/dist/shims/tty.d.ts.map +1 -0
  120. package/dist/shims/url.d.ts +64 -0
  121. package/dist/shims/url.d.ts.map +1 -0
  122. package/dist/shims/util.d.ts +106 -0
  123. package/dist/shims/util.d.ts.map +1 -0
  124. package/dist/shims/v8.d.ts +73 -0
  125. package/dist/shims/v8.d.ts.map +1 -0
  126. package/dist/shims/vfs-adapter.d.ts +126 -0
  127. package/dist/shims/vfs-adapter.d.ts.map +1 -0
  128. package/dist/shims/vm.d.ts +45 -0
  129. package/dist/shims/vm.d.ts.map +1 -0
  130. package/dist/shims/worker_threads.d.ts +66 -0
  131. package/dist/shims/worker_threads.d.ts.map +1 -0
  132. package/dist/shims/ws.d.ts +66 -0
  133. package/dist/shims/ws.d.ts.map +1 -0
  134. package/dist/shims/zlib.d.ts +161 -0
  135. package/dist/shims/zlib.d.ts.map +1 -0
  136. package/dist/transform.d.ts +24 -0
  137. package/dist/transform.d.ts.map +1 -0
  138. package/dist/virtual-fs.d.ts +226 -0
  139. package/dist/virtual-fs.d.ts.map +1 -0
  140. package/dist/vite-demo.d.ts +35 -0
  141. package/dist/vite-demo.d.ts.map +1 -0
  142. package/dist/vite-sw.js +132 -0
  143. package/dist/worker/runtime-worker.d.ts +8 -0
  144. package/dist/worker/runtime-worker.d.ts.map +1 -0
  145. package/dist/worker-runtime.d.ts +50 -0
  146. package/dist/worker-runtime.d.ts.map +1 -0
  147. package/package.json +85 -0
  148. package/src/ai-chatbot-demo-entry.ts +244 -0
  149. package/src/ai-chatbot-demo.ts +509 -0
  150. package/src/convex-app-demo-entry.ts +1107 -0
  151. package/src/convex-app-demo.ts +1316 -0
  152. package/src/cors-proxy.ts +81 -0
  153. package/src/create-runtime.ts +147 -0
  154. package/src/demo.ts +304 -0
  155. package/src/dev-server.ts +274 -0
  156. package/src/frameworks/next-dev-server.ts +2224 -0
  157. package/src/frameworks/vite-dev-server.ts +702 -0
  158. package/src/index.ts +101 -0
  159. package/src/next-demo.ts +1784 -0
  160. package/src/npm/index.ts +347 -0
  161. package/src/npm/registry.ts +152 -0
  162. package/src/npm/resolver.ts +385 -0
  163. package/src/npm/tarball.ts +209 -0
  164. package/src/runtime-interface.ts +103 -0
  165. package/src/runtime.ts +1046 -0
  166. package/src/sandbox-helpers.ts +173 -0
  167. package/src/sandbox-runtime.ts +252 -0
  168. package/src/server-bridge.ts +426 -0
  169. package/src/shims/assert.ts +664 -0
  170. package/src/shims/async_hooks.ts +86 -0
  171. package/src/shims/buffer.ts +75 -0
  172. package/src/shims/child_process-browser.ts +217 -0
  173. package/src/shims/child_process.ts +463 -0
  174. package/src/shims/chokidar.ts +313 -0
  175. package/src/shims/cluster.ts +67 -0
  176. package/src/shims/crypto.ts +830 -0
  177. package/src/shims/dgram.ts +47 -0
  178. package/src/shims/diagnostics_channel.ts +196 -0
  179. package/src/shims/dns.ts +172 -0
  180. package/src/shims/domain.ts +58 -0
  181. package/src/shims/esbuild.ts +805 -0
  182. package/src/shims/events.ts +195 -0
  183. package/src/shims/fs.ts +803 -0
  184. package/src/shims/fsevents.ts +63 -0
  185. package/src/shims/http.ts +904 -0
  186. package/src/shims/http2.ts +96 -0
  187. package/src/shims/https.ts +86 -0
  188. package/src/shims/inspector.ts +30 -0
  189. package/src/shims/module.ts +82 -0
  190. package/src/shims/net.ts +359 -0
  191. package/src/shims/os.ts +195 -0
  192. package/src/shims/path.ts +199 -0
  193. package/src/shims/perf_hooks.ts +92 -0
  194. package/src/shims/process.ts +346 -0
  195. package/src/shims/querystring.ts +97 -0
  196. package/src/shims/readdirp.ts +228 -0
  197. package/src/shims/readline.ts +110 -0
  198. package/src/shims/rollup.ts +80 -0
  199. package/src/shims/sentry.ts +133 -0
  200. package/src/shims/stream.ts +1126 -0
  201. package/src/shims/tls.ts +95 -0
  202. package/src/shims/tty.ts +64 -0
  203. package/src/shims/url.ts +171 -0
  204. package/src/shims/util.ts +312 -0
  205. package/src/shims/v8.ts +113 -0
  206. package/src/shims/vfs-adapter.ts +402 -0
  207. package/src/shims/vm.ts +83 -0
  208. package/src/shims/worker_threads.ts +111 -0
  209. package/src/shims/ws.ts +382 -0
  210. package/src/shims/zlib.ts +289 -0
  211. package/src/transform.ts +313 -0
  212. package/src/types/external.d.ts +67 -0
  213. package/src/virtual-fs.ts +903 -0
  214. package/src/vite-demo.ts +577 -0
  215. package/src/worker/runtime-worker.ts +128 -0
  216. package/src/worker-runtime.ts +145 -0
package/README.md ADDED
@@ -0,0 +1,731 @@
1
+ # almostnode
2
+
3
+ **Node.js in your browser. Just like that.**
4
+
5
+ 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.
6
+
7
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-green.svg)](https://nodejs.org/)
10
+
11
+ > **Warning:** This project is experimental and may contain bugs. Use with caution in production environments.
12
+
13
+ ---
14
+
15
+ ## Features
16
+
17
+ - **Virtual File System** - Full in-memory filesystem with Node.js-compatible API
18
+ - **Node.js API Shims** - 40+ shimmed modules (`fs`, `path`, `http`, `events`, and more)
19
+ - **npm Package Installation** - Install and run real npm packages in the browser
20
+ - **Dev Servers** - Built-in Vite and Next.js development servers
21
+ - **Hot Module Replacement** - React Refresh support for instant updates
22
+ - **TypeScript Support** - First-class TypeScript/TSX transformation via esbuild-wasm
23
+ - **Service Worker Architecture** - Intercepts requests for seamless dev experience
24
+ - **Optional Web Worker Support** - Offload code execution to a Web Worker for improved UI responsiveness
25
+ - **Secure by Default** - Cross-origin sandbox support for running untrusted code safely
26
+
27
+ ---
28
+
29
+ ## Requirements
30
+
31
+ - **Node.js 20+** - Required for development and building
32
+ - **Modern browser** - Chrome, Firefox, Safari, or Edge with ES2020+ support
33
+
34
+ > **Note:** almostnode runs in the browser and emulates Node.js 20 APIs. The Node.js requirement is only for development tooling (Vite, Vitest, TypeScript).
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ### Installation
41
+
42
+ ```bash
43
+ npm install almostnode
44
+ ```
45
+
46
+ ### Basic Usage
47
+
48
+ ```typescript
49
+ import { createContainer } from 'almostnode';
50
+
51
+ // Create a Node.js container in the browser
52
+ const container = createContainer();
53
+
54
+ // Execute JavaScript code directly
55
+ const result = container.execute(`
56
+ const path = require('path');
57
+ const fs = require('fs');
58
+
59
+ // Use Node.js APIs in the browser!
60
+ fs.writeFileSync('/hello.txt', 'Hello from the browser!');
61
+ module.exports = fs.readFileSync('/hello.txt', 'utf8');
62
+ `);
63
+
64
+ console.log(result.exports); // "Hello from the browser!"
65
+ ```
66
+
67
+ ### Working with Virtual File System
68
+
69
+ ```typescript
70
+ import { createContainer } from 'almostnode';
71
+
72
+ const container = createContainer();
73
+ const { vfs } = container;
74
+
75
+ // Pre-populate the virtual filesystem
76
+ vfs.writeFileSync('/src/index.js', `
77
+ const data = require('./data.json');
78
+ console.log('Users:', data.users.length);
79
+ module.exports = data;
80
+ `);
81
+
82
+ vfs.writeFileSync('/src/data.json', JSON.stringify({
83
+ users: [{ name: 'Alice' }, { name: 'Bob' }]
84
+ }));
85
+
86
+ // Run from the virtual filesystem
87
+ const result = container.runFile('/src/index.js');
88
+ ```
89
+
90
+ ### With npm Packages
91
+
92
+ ```typescript
93
+ import { createContainer } from 'almostnode';
94
+
95
+ const container = createContainer();
96
+
97
+ // Install a package
98
+ await container.npm.install('lodash');
99
+
100
+ // Use it in your code
101
+ container.execute(`
102
+ const _ = require('lodash');
103
+ console.log(_.capitalize('hello world'));
104
+ `);
105
+ // Output: Hello world
106
+ ```
107
+
108
+ ### With Next.js Dev Server
109
+
110
+ ```typescript
111
+ import { VirtualFS, NextDevServer, getServerBridge } from 'almostnode';
112
+
113
+ const vfs = new VirtualFS();
114
+
115
+ // Create a Next.js page
116
+ vfs.mkdirSync('/pages', { recursive: true });
117
+ vfs.writeFileSync('/pages/index.jsx', `
118
+ import { useState } from 'react';
119
+
120
+ export default function Home() {
121
+ const [count, setCount] = useState(0);
122
+ return (
123
+ <div>
124
+ <h1>Count: {count}</h1>
125
+ <button onClick={() => setCount(c => c + 1)}>+</button>
126
+ </div>
127
+ );
128
+ }
129
+ `);
130
+
131
+ // Start the dev server
132
+ const server = new NextDevServer(vfs, { port: 3000 });
133
+ const bridge = getServerBridge();
134
+ await bridge.initServiceWorker();
135
+ bridge.registerServer(server, 3000);
136
+
137
+ // Access at: /__virtual__/3000/
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Comparison with WebContainers
143
+
144
+ | Feature | almostnode | WebContainers |
145
+ |---------|-----------|---------------|
146
+ | **Bundle Size** | ~50KB | ~2MB |
147
+ | **Startup Time** | Instant | 2-5 seconds |
148
+ | **Execution Model** | Main thread or Web Worker (configurable) | Web Worker isolates |
149
+ | **Shell** | `just-bash` (POSIX subset) | Full Linux kernel |
150
+ | **Native Modules** | Stubs only | Full support |
151
+ | **Networking** | Virtual ports | Real TCP/IP |
152
+ | **Use Case** | Lightweight playgrounds, demos | Full development environments |
153
+
154
+ ### When to use almostnode
155
+
156
+ - Building code playgrounds or tutorials
157
+ - Creating interactive documentation
158
+ - Prototyping without server setup
159
+ - Educational tools
160
+ - Lightweight sandboxed execution
161
+
162
+ ### Example: Code Playground
163
+
164
+ ```typescript
165
+ import { createContainer } from 'almostnode';
166
+
167
+ function createPlayground() {
168
+ const container = createContainer();
169
+
170
+ return {
171
+ run: (code: string) => {
172
+ try {
173
+ const result = container.execute(code);
174
+ return { success: true, result: result.exports };
175
+ } catch (error) {
176
+ return { success: false, error: error.message };
177
+ }
178
+ },
179
+ reset: () => container.runtime.clearCache(),
180
+ };
181
+ }
182
+
183
+ // Usage
184
+ const playground = createPlayground();
185
+ const output = playground.run(`
186
+ const crypto = require('crypto');
187
+ module.exports = crypto.randomUUID();
188
+ `);
189
+ console.log(output); // { success: true, result: "550e8400-e29b-..." }
190
+ ```
191
+
192
+ ### When to use WebContainers
193
+
194
+ - Full-fidelity Node.js development
195
+ - Running native modules
196
+ - Complex build pipelines
197
+ - Production-like environments
198
+
199
+ ---
200
+
201
+ ## API Reference
202
+
203
+ ### `createContainer(options?)`
204
+
205
+ Creates a new container with all components initialized.
206
+
207
+ ```typescript
208
+ interface ContainerOptions {
209
+ cwd?: string; // Working directory (default: '/')
210
+ env?: Record<string, string>; // Environment variables
211
+ onConsole?: (method: string, args: any[]) => void; // Console hook
212
+ }
213
+
214
+ const container = createContainer({
215
+ cwd: '/app',
216
+ env: { NODE_ENV: 'development' },
217
+ onConsole: (method, args) => console.log(`[${method}]`, ...args),
218
+ });
219
+ ```
220
+
221
+ Returns:
222
+ - `container.vfs` - VirtualFS instance
223
+ - `container.runtime` - Runtime instance
224
+ - `container.npm` - PackageManager instance
225
+ - `container.serverBridge` - ServerBridge instance
226
+
227
+ ### VirtualFS
228
+
229
+ Node.js-compatible filesystem API.
230
+
231
+ ```typescript
232
+ // Synchronous operations
233
+ vfs.writeFileSync(path, content);
234
+ vfs.readFileSync(path, encoding?);
235
+ vfs.mkdirSync(path, { recursive: true });
236
+ vfs.readdirSync(path);
237
+ vfs.statSync(path);
238
+ vfs.unlinkSync(path);
239
+ vfs.rmdirSync(path);
240
+ vfs.existsSync(path);
241
+ vfs.renameSync(oldPath, newPath);
242
+
243
+ // Async operations
244
+ await vfs.readFile(path, encoding?);
245
+ await vfs.stat(path);
246
+
247
+ // File watching
248
+ vfs.watch(path, { recursive: true }, (event, filename) => {
249
+ console.log(`${event}: ${filename}`);
250
+ });
251
+ ```
252
+
253
+ ### Runtime
254
+
255
+ Execute JavaScript/TypeScript code.
256
+
257
+ ```typescript
258
+ // Execute code string
259
+ runtime.execute('console.log("Hello")');
260
+
261
+ // Run a file from VirtualFS
262
+ runtime.runFile('/path/to/file.js');
263
+
264
+ // Require a module
265
+ const module = runtime.require('/path/to/module.js');
266
+ ```
267
+
268
+ ### createRuntime (Async Runtime Factory)
269
+
270
+ For advanced use cases, use `createRuntime` to create a runtime with security options:
271
+
272
+ ```typescript
273
+ import { createRuntime, VirtualFS } from 'almostnode';
274
+
275
+ const vfs = new VirtualFS();
276
+
277
+ // RECOMMENDED: Cross-origin sandbox (fully isolated)
278
+ const secureRuntime = await createRuntime(vfs, {
279
+ sandbox: 'https://your-sandbox.vercel.app',
280
+ });
281
+
282
+ // For demos/trusted code: Same-origin with explicit opt-in
283
+ const demoRuntime = await createRuntime(vfs, {
284
+ dangerouslyAllowSameOrigin: true,
285
+ useWorker: true, // Optional: run in Web Worker
286
+ cwd: '/project',
287
+ env: { NODE_ENV: 'development' },
288
+ });
289
+
290
+ // Both modes use the same async API
291
+ const result = await secureRuntime.execute('module.exports = 1 + 1;');
292
+ console.log(result.exports); // 2
293
+ ```
294
+
295
+ #### Security Modes
296
+
297
+ | Mode | Option | Security Level | Use Case |
298
+ |------|--------|----------------|----------|
299
+ | **Cross-origin sandbox** | `sandbox: 'https://...'` | Highest | Production, untrusted code |
300
+ | **Same-origin Worker** | `dangerouslyAllowSameOrigin: true, useWorker: true` | Medium | Demos with trusted code |
301
+ | **Same-origin main thread** | `dangerouslyAllowSameOrigin: true` | Lowest | Trusted code only |
302
+
303
+ **Security by default:** `createRuntime()` throws an error if neither `sandbox` nor `dangerouslyAllowSameOrigin` is provided.
304
+
305
+ ---
306
+
307
+ ## Sandbox Setup
308
+
309
+ 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).
310
+
311
+ ### Quick Setup (Vercel)
312
+
313
+ ```typescript
314
+ import { generateSandboxFiles } from 'almostnode';
315
+
316
+ const files = generateSandboxFiles();
317
+ // Write files['index.html'] and files['vercel.json'] to a directory
318
+ // Deploy: cd sandbox && vercel --prod
319
+ ```
320
+
321
+ ### Manual Setup (Any Platform)
322
+
323
+ The sandbox requires two things:
324
+
325
+ #### 1. The sandbox HTML page
326
+
327
+ Create an `index.html` that loads almostnode and handles postMessage:
328
+
329
+ ```html
330
+ <!DOCTYPE html>
331
+ <html>
332
+ <head><meta charset="UTF-8"></head>
333
+ <body>
334
+ <script type="module">
335
+ import { VirtualFS, Runtime } from 'https://unpkg.com/almostnode/dist/index.js';
336
+
337
+ let vfs = null;
338
+ let runtime = null;
339
+
340
+ window.addEventListener('message', async (event) => {
341
+ const { type, id, code, filename, vfsSnapshot, options, path, content } = event.data;
342
+
343
+ try {
344
+ switch (type) {
345
+ case 'init':
346
+ vfs = VirtualFS.fromSnapshot(vfsSnapshot);
347
+ runtime = new Runtime(vfs, {
348
+ cwd: options?.cwd,
349
+ env: options?.env,
350
+ onConsole: (method, args) => {
351
+ parent.postMessage({ type: 'console', consoleMethod: method, consoleArgs: args }, '*');
352
+ },
353
+ });
354
+ break;
355
+ case 'execute':
356
+ const result = runtime.execute(code, filename);
357
+ parent.postMessage({ type: 'result', id, result }, '*');
358
+ break;
359
+ case 'runFile':
360
+ const runResult = runtime.runFile(filename);
361
+ parent.postMessage({ type: 'result', id, result: runResult }, '*');
362
+ break;
363
+ case 'syncFile':
364
+ if (content === null) { try { vfs.unlinkSync(path); } catch {} }
365
+ else { vfs.writeFileSync(path, content); }
366
+ break;
367
+ case 'clearCache':
368
+ runtime?.clearCache();
369
+ break;
370
+ }
371
+ } catch (error) {
372
+ if (id) parent.postMessage({ type: 'error', id, error: error.message }, '*');
373
+ }
374
+ });
375
+
376
+ parent.postMessage({ type: 'ready' }, '*');
377
+ </script>
378
+ </body>
379
+ </html>
380
+ ```
381
+
382
+ #### 2. Required HTTP headers
383
+
384
+ The sandbox server must include these headers:
385
+
386
+ ```
387
+ Access-Control-Allow-Origin: *
388
+ Cross-Origin-Resource-Policy: cross-origin
389
+ ```
390
+
391
+ **Example configurations:**
392
+
393
+ <details>
394
+ <summary>Nginx</summary>
395
+
396
+ ```nginx
397
+ server {
398
+ listen 3002;
399
+ root /path/to/sandbox;
400
+
401
+ location / {
402
+ add_header Access-Control-Allow-Origin *;
403
+ add_header Cross-Origin-Resource-Policy cross-origin;
404
+ }
405
+ }
406
+ ```
407
+ </details>
408
+
409
+ <details>
410
+ <summary>Apache (.htaccess)</summary>
411
+
412
+ ```apache
413
+ Header set Access-Control-Allow-Origin "*"
414
+ Header set Cross-Origin-Resource-Policy "cross-origin"
415
+ ```
416
+ </details>
417
+
418
+ <details>
419
+ <summary>Express.js</summary>
420
+
421
+ ```javascript
422
+ app.use((req, res, next) => {
423
+ res.setHeader('Access-Control-Allow-Origin', '*');
424
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
425
+ next();
426
+ });
427
+ app.use(express.static('sandbox'));
428
+ app.listen(3002);
429
+ ```
430
+ </details>
431
+
432
+ <details>
433
+ <summary>Python (http.server)</summary>
434
+
435
+ ```python
436
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
437
+
438
+ class CORSHandler(SimpleHTTPRequestHandler):
439
+ def end_headers(self):
440
+ self.send_header('Access-Control-Allow-Origin', '*')
441
+ self.send_header('Cross-Origin-Resource-Policy', 'cross-origin')
442
+ super().end_headers()
443
+
444
+ HTTPServer(('', 3002), CORSHandler).serve_forever()
445
+ ```
446
+ </details>
447
+
448
+ ### Use in your app
449
+
450
+ ```typescript
451
+ const runtime = await createRuntime(vfs, {
452
+ sandbox: 'https://sandbox.yourdomain.com', // Must be different origin!
453
+ });
454
+
455
+ // Code runs in isolated cross-origin iframe
456
+ const result = await runtime.execute(untrustedCode);
457
+ ```
458
+
459
+ ### Local Development
460
+
461
+ For local testing, run the sandbox on a different port:
462
+
463
+ ```bash
464
+ # Terminal 1: Main app on port 5173
465
+ npm run dev
466
+
467
+ # Terminal 2: Sandbox on port 3002
468
+ npm run sandbox
469
+ ```
470
+
471
+ Then use `sandbox: 'http://localhost:3002/sandbox/'` in your app.
472
+
473
+ ### What cross-origin sandbox protects
474
+
475
+ | Threat | Status |
476
+ |--------|--------|
477
+ | Cookies | Blocked (different origin) |
478
+ | localStorage | Blocked (different origin) |
479
+ | IndexedDB | Blocked (different origin) |
480
+ | DOM access | Blocked (cross-origin iframe) |
481
+
482
+ **Note:** Network requests from the sandbox are still possible. Add CSP headers for additional protection.
483
+
484
+ ### PackageManager
485
+
486
+ Install npm packages.
487
+
488
+ ```typescript
489
+ // Install a package
490
+ await npm.install('react');
491
+ await npm.install('lodash@4.17.21');
492
+
493
+ // Install multiple packages
494
+ await npm.install(['react', 'react-dom']);
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Supported Node.js APIs
500
+
501
+ **967 compatibility tests** verify our Node.js API coverage.
502
+
503
+ ### Fully Shimmed Modules
504
+
505
+ | Module | Tests | Coverage | Notes |
506
+ |--------|-------|----------|-------|
507
+ | `path` | 219 | High | POSIX paths (no Windows) |
508
+ | `buffer` | 95 | High | All common operations |
509
+ | `fs` | 76 | High | Sync + promises API |
510
+ | `url` | 67 | High | WHATWG URL + legacy parser |
511
+ | `util` | 77 | High | format, inspect, promisify |
512
+ | `process` | 60 | High | env, cwd, hrtime, EventEmitter |
513
+ | `events` | 50 | High | Full EventEmitter API |
514
+ | `os` | 58 | High | Platform info (simulated) |
515
+ | `crypto` | 57 | High | Hash, HMAC, random, sign/verify |
516
+ | `querystring` | 52 | High | parse, stringify, escape |
517
+ | `stream` | 44 | Medium | Readable, Writable, Transform |
518
+ | `zlib` | 39 | High | gzip, deflate, brotli |
519
+ | `tty` | 40 | High | ReadStream, WriteStream |
520
+ | `perf_hooks` | 33 | High | Performance API |
521
+
522
+ ### Stubbed Modules
523
+
524
+ These modules export empty objects or no-op functions:
525
+ - `net`, `tls`, `dns`, `dgram`
526
+ - `cluster`, `worker_threads`
527
+ - `vm`, `v8`, `inspector`
528
+ - `async_hooks`
529
+
530
+ ---
531
+
532
+ ## Framework Support
533
+
534
+ ### Vite
535
+
536
+ ```typescript
537
+ import { VirtualFS, ViteDevServer, getServerBridge } from 'almostnode';
538
+
539
+ const vfs = new VirtualFS();
540
+
541
+ // Create a React app
542
+ vfs.writeFileSync('/index.html', `
543
+ <!DOCTYPE html>
544
+ <html>
545
+ <body>
546
+ <div id="root"></div>
547
+ <script type="module" src="/src/main.jsx"></script>
548
+ </body>
549
+ </html>
550
+ `);
551
+
552
+ vfs.mkdirSync('/src', { recursive: true });
553
+ vfs.writeFileSync('/src/main.jsx', `
554
+ import React from 'react';
555
+ import ReactDOM from 'react-dom/client';
556
+
557
+ function App() {
558
+ return <h1>Hello Vite!</h1>;
559
+ }
560
+
561
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
562
+ `);
563
+
564
+ // Start Vite dev server
565
+ const server = new ViteDevServer(vfs, { port: 5173 });
566
+ ```
567
+
568
+ ### Next.js
569
+
570
+ Supports both **Pages Router** and **App Router**:
571
+
572
+ #### Pages Router
573
+
574
+ ```
575
+ /pages
576
+ /index.jsx → /
577
+ /about.jsx → /about
578
+ /users/[id].jsx → /users/:id
579
+ /api/hello.js → /api/hello
580
+ ```
581
+
582
+ #### App Router
583
+
584
+ ```
585
+ /app
586
+ /layout.jsx → Root layout
587
+ /page.jsx → /
588
+ /about/page.jsx → /about
589
+ /users/[id]/page.jsx → /users/:id
590
+ ```
591
+
592
+ ---
593
+
594
+ ## Hot Module Replacement (HMR)
595
+
596
+ 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.
597
+
598
+ ### How It Works
599
+
600
+ HMR is automatically enabled when using `NextDevServer` or `ViteDevServer`. The system uses:
601
+
602
+ 1. **VirtualFS file watching** - Detects file changes via `vfs.watch()`
603
+ 2. **postMessage API** - Communicates updates between the main page and preview iframe
604
+ 3. **React Refresh** - Preserves React component state during updates
605
+
606
+ ```typescript
607
+ // HMR works automatically - just edit files and save
608
+ vfs.writeFileSync('/app/page.tsx', updatedContent);
609
+ // The preview iframe will automatically refresh with the new content
610
+ ```
611
+
612
+ ### Setup Requirements
613
+
614
+ For security, the preview iframe should be sandboxed. HMR uses `postMessage` for communication, which works correctly with sandboxed iframes:
615
+
616
+ ```typescript
617
+ // Create sandboxed iframe for security
618
+ const iframe = document.createElement('iframe');
619
+ iframe.src = '/__virtual__/3000/';
620
+ // Sandbox restricts the iframe's capabilities - add only what you need
621
+ iframe.sandbox = 'allow-forms allow-scripts allow-same-origin allow-popups';
622
+ container.appendChild(iframe);
623
+
624
+ // Register the iframe as HMR target after it loads
625
+ iframe.onload = () => {
626
+ if (iframe.contentWindow) {
627
+ devServer.setHMRTarget(iframe.contentWindow);
628
+ }
629
+ };
630
+ ```
631
+
632
+ **Recommended sandbox permissions:**
633
+ - `allow-scripts` - Required for JavaScript execution
634
+ - `allow-same-origin` - Allows the iframe to access cookies, localStorage, and IndexedDB (only add if your app needs these; omit for better isolation)
635
+ - `allow-forms` - If your app uses forms
636
+ - `allow-popups` - If your app opens new windows/tabs
637
+
638
+ > **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`.
639
+
640
+ ### Manual HMR Triggering
641
+
642
+ If you need to manually trigger HMR updates (e.g., after programmatic file changes):
643
+
644
+ ```typescript
645
+ function triggerHMR(path: string, iframe: HTMLIFrameElement): void {
646
+ if (iframe.contentWindow) {
647
+ iframe.contentWindow.postMessage({
648
+ type: 'update',
649
+ path,
650
+ timestamp: Date.now(),
651
+ channel: 'next-hmr', // Use 'vite-hmr' for Vite
652
+ }, '*');
653
+ }
654
+ }
655
+
656
+ // After writing a file
657
+ vfs.writeFileSync('/app/page.tsx', newContent);
658
+ triggerHMR('/app/page.tsx', iframe);
659
+ ```
660
+
661
+ ### Supported File Types
662
+
663
+ | File Type | HMR Behavior |
664
+ |-----------|--------------|
665
+ | `.jsx`, `.tsx` | React Refresh (preserves state) |
666
+ | `.js`, `.ts` | Full module reload |
667
+ | `.css` | Style injection (no reload) |
668
+ | `.json` | Full page reload |
669
+
670
+ ---
671
+
672
+ ## Development
673
+
674
+ ### Setup
675
+
676
+ ```bash
677
+ git clone https://github.com/user/almostnode.git
678
+ cd almostnode
679
+ npm install
680
+ ```
681
+
682
+ ### Run Tests
683
+
684
+ ```bash
685
+ # Unit tests
686
+ npm test
687
+
688
+ # E2E tests (requires Playwright)
689
+ npm run test:e2e
690
+ ```
691
+
692
+ ### Development Server
693
+
694
+ ```bash
695
+ npm run dev
696
+ ```
697
+
698
+ Open `http://localhost:5173/examples/next-demo.html` to see the Next.js demo.
699
+
700
+ ---
701
+
702
+ ## Contributing
703
+
704
+ Contributions are welcome! Please:
705
+
706
+ 1. Fork the repository
707
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
708
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
709
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
710
+ 5. Open a Pull Request
711
+
712
+ ---
713
+
714
+ ## License
715
+
716
+ MIT License - see [LICENSE](LICENSE) for details.
717
+
718
+ ---
719
+
720
+ ## Acknowledgments
721
+
722
+ - [esbuild-wasm](https://github.com/evanw/esbuild) - Lightning-fast JavaScript/TypeScript transformation
723
+ - [just-bash](https://github.com/user/just-bash) - POSIX shell in WebAssembly
724
+ - [React Refresh](https://github.com/facebook/react/tree/main/packages/react-refresh) - Hot module replacement for React
725
+ - [Comlink](https://github.com/GoogleChromeLabs/comlink) - Web Worker communication made simple
726
+
727
+ ---
728
+
729
+ <p align="center">
730
+ Made with care for the browser
731
+ </p>