sandlot 0.1.2 → 0.1.4
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.
- package/README.md +138 -408
- package/dist/build-emitter.d.ts +31 -13
- package/dist/build-emitter.d.ts.map +1 -1
- package/dist/builder.d.ts +370 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/bundler.d.ts +6 -2
- package/dist/bundler.d.ts.map +1 -1
- package/dist/commands/compile.d.ts +13 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/index.d.ts +17 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/packages.d.ts +17 -0
- package/dist/commands/packages.d.ts.map +1 -0
- package/dist/commands/run.d.ts +40 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/types.d.ts +141 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/fs.d.ts +53 -49
- package/dist/fs.d.ts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -511
- package/dist/internal.js +161 -171
- package/dist/runner.d.ts +314 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/sandbox-manager.d.ts +45 -21
- package/dist/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox.d.ts +144 -62
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/shared-modules.d.ts +22 -3
- package/dist/shared-modules.d.ts.map +1 -1
- package/dist/shared-resources.d.ts +0 -3
- package/dist/shared-resources.d.ts.map +1 -1
- package/dist/ts-libs.d.ts +7 -20
- package/dist/ts-libs.d.ts.map +1 -1
- package/dist/typechecker.d.ts +1 -1
- package/package.json +5 -5
- package/src/build-emitter.ts +32 -29
- package/src/builder.ts +498 -0
- package/src/bundler.ts +76 -55
- package/src/commands/compile.ts +236 -0
- package/src/commands/index.ts +51 -0
- package/src/commands/packages.ts +154 -0
- package/src/commands/run.ts +245 -0
- package/src/commands/types.ts +172 -0
- package/src/fs.ts +82 -221
- package/src/index.ts +17 -12
- package/src/sandbox.ts +219 -149
- package/src/shared-modules.ts +74 -4
- package/src/shared-resources.ts +0 -3
- package/src/ts-libs.ts +19 -121
- package/src/typechecker.ts +1 -1
- package/dist/react.d.ts +0 -159
- package/dist/react.d.ts.map +0 -1
- package/dist/react.js +0 -149
- package/src/commands.ts +0 -733
- package/src/sandbox-manager.ts +0 -409
package/README.md
CHANGED
|
@@ -1,472 +1,233 @@
|
|
|
1
1
|
# Sandlot
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Sandlot provides a complete in-browser development environment with a virtual filesystem, TypeScript type checking, esbuild bundling, and npm package management—all without a server.
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Virtual Filesystem** - In-memory or IndexedDB-backed file storage
|
|
10
|
-
- **TypeScript Type Checking** - Full type checking
|
|
11
|
-
- **esbuild Bundling** - Fast bundling with automatic npm import handling via esbuild-wasm
|
|
12
|
-
- **Package Management** - Install npm packages via esm.sh CDN
|
|
13
|
-
- **Bash Shell** - Familiar command interface (`tsc`, `build`, `install`, etc.) via just-bash
|
|
3
|
+
Browser-based TypeScript sandbox with esbuild bundling and type checking. Designed for AI agent workflows where code needs to be written, validated, and executed in real-time, all in the browser.
|
|
14
4
|
|
|
15
5
|
## Installation
|
|
16
6
|
|
|
17
7
|
```bash
|
|
18
8
|
npm install sandlot
|
|
19
|
-
# or
|
|
20
|
-
bun add sandlot
|
|
21
9
|
```
|
|
22
10
|
|
|
23
11
|
## Quick Start
|
|
24
12
|
|
|
25
13
|
```typescript
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
import { createBuilder, registerSharedModules } from "sandlot";
|
|
15
|
+
import * as React from "react";
|
|
16
|
+
|
|
17
|
+
// Share React with dynamic code to avoid duplicate instances
|
|
18
|
+
registerSharedModules({ react: React });
|
|
19
|
+
|
|
20
|
+
// Create a reusable builder
|
|
21
|
+
const runAgent = createBuilder({
|
|
22
|
+
sandboxOptions: { sharedModules: ["react"] },
|
|
23
|
+
build: async (sandbox, prompt) => {
|
|
24
|
+
// Your agent logic here - execute commands via sandbox.bash.exec()
|
|
25
|
+
await sandbox.bash.exec(
|
|
26
|
+
'echo "export const App = () => <div>Hello</div>" > /src/index.tsx',
|
|
27
|
+
);
|
|
28
|
+
await sandbox.bash.exec("build /src/index.tsx");
|
|
36
29
|
},
|
|
37
30
|
});
|
|
38
31
|
|
|
39
|
-
//
|
|
40
|
-
await
|
|
41
|
-
"/src/index.ts",
|
|
42
|
-
`export function greet(name: string) {
|
|
43
|
-
return \`Hello, \${name}!\`;
|
|
44
|
-
}`,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Capture build result via callback
|
|
48
|
-
let bundle: BundleResult | null = null;
|
|
49
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
50
|
-
bundle = result;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Build
|
|
54
|
-
const result = await sandbox.bash.exec("build /src/index.ts");
|
|
55
|
-
unsubscribe();
|
|
32
|
+
// Run with a prompt
|
|
33
|
+
const result = await runAgent("Create a counter component");
|
|
56
34
|
|
|
57
|
-
if (result.
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
if (result.module?.App) {
|
|
36
|
+
// Use the generated component
|
|
37
|
+
const App = result.module.App as React.ComponentType;
|
|
60
38
|
}
|
|
61
|
-
|
|
62
|
-
sandbox.close();
|
|
63
39
|
```
|
|
64
40
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
The Sandbox API provides direct access to the filesystem and bash shell.
|
|
41
|
+
## Builder API
|
|
68
42
|
|
|
69
|
-
|
|
43
|
+
The `createBuilder()` function is the primary API for agent workflows. It handles sandbox lifecycle, build capture, validation, and cancellation.
|
|
70
44
|
|
|
71
|
-
|
|
72
|
-
import { createSandbox, createInMemorySandbox } from "sandlot";
|
|
45
|
+
### `createBuilder(options)`
|
|
73
46
|
|
|
74
|
-
|
|
75
|
-
const sandbox = await createSandbox({
|
|
76
|
-
fsOptions: {
|
|
77
|
-
dbName: "my-project",
|
|
78
|
-
initialFiles: {
|
|
79
|
-
"/src/index.ts": "export const x = 1;",
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// In-memory sandbox (no persistence)
|
|
85
|
-
const memSandbox = await createInMemorySandbox({
|
|
86
|
-
initialFiles: {
|
|
87
|
-
"/src/index.ts": "export const x = 1;",
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Using Bash Commands
|
|
47
|
+
Returns a reusable builder function that can be called with different prompts.
|
|
93
48
|
|
|
94
49
|
```typescript
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
const tscResult = await sandbox.bash.exec("tsc /src/index.ts");
|
|
100
|
-
console.log(tscResult.stdout);
|
|
50
|
+
const runAgent = createBuilder<T>({
|
|
51
|
+
// Sandbox configuration (pick one)
|
|
52
|
+
sandbox?: Sandbox, // Reuse an existing sandbox (state persists between calls)
|
|
53
|
+
sandboxOptions?: SandboxOptions, // Create fresh sandbox per call (isolated runs)
|
|
101
54
|
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
105
|
-
bundleResult = result;
|
|
55
|
+
// Your agent logic
|
|
56
|
+
build: (sandbox: Sandbox, prompt: string) => Promise<T>,
|
|
106
57
|
});
|
|
107
|
-
|
|
108
|
-
const buildCmd = await sandbox.bash.exec("build /src/index.ts");
|
|
109
|
-
if (buildCmd.exitCode === 0 && bundleResult) {
|
|
110
|
-
console.log(bundleResult.code); // Compiled JavaScript
|
|
111
|
-
}
|
|
112
|
-
unsubscribe();
|
|
113
|
-
|
|
114
|
-
// Install packages
|
|
115
|
-
await sandbox.bash.exec("install react lodash@4.17.21");
|
|
116
|
-
|
|
117
|
-
// List installed packages
|
|
118
|
-
const listResult = await sandbox.bash.exec("list");
|
|
119
|
-
console.log(listResult.stdout);
|
|
120
|
-
|
|
121
|
-
// Run code (entry point required)
|
|
122
|
-
await sandbox.bash.exec("run /src/script.ts");
|
|
123
58
|
```
|
|
124
59
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
build <entry> [options]
|
|
129
|
-
|
|
130
|
-
Arguments:
|
|
131
|
-
entry Entry point file (required, e.g., /src/index.ts)
|
|
60
|
+
#### Options
|
|
132
61
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
62
|
+
| Option | Type | Description |
|
|
63
|
+
| ---------------- | --------------------------------- | ------------------------------------------------------- |
|
|
64
|
+
| `sandbox` | `Sandbox` | Existing sandbox to reuse. Files persist between calls. |
|
|
65
|
+
| `sandboxOptions` | `SandboxOptions` | Options for creating fresh sandboxes per call. |
|
|
66
|
+
| `build` | `(sandbox, prompt) => Promise<T>` | Your agent logic. Receives the sandbox and prompt. |
|
|
138
67
|
|
|
139
|
-
###
|
|
68
|
+
### Calling the Builder
|
|
140
69
|
|
|
141
70
|
```typescript
|
|
142
|
-
|
|
143
|
-
let bundleResult: BundleResult | null = null;
|
|
144
|
-
const unsubscribe = sandbox.onBuild((result) => {
|
|
145
|
-
bundleResult = result;
|
|
146
|
-
console.log("Build completed:", result.code.length, "bytes");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Build with entry point (required)
|
|
150
|
-
const buildCmd = await sandbox.bash.exec("build /src/index.ts");
|
|
151
|
-
if (buildCmd.exitCode !== 0) {
|
|
152
|
-
console.error("Build failed:", buildCmd.stderr);
|
|
153
|
-
} else if (bundleResult) {
|
|
154
|
-
console.log(bundleResult.code);
|
|
155
|
-
}
|
|
156
|
-
unsubscribe();
|
|
157
|
-
|
|
158
|
-
// Check for unsaved changes
|
|
159
|
-
if (sandbox.isDirty()) {
|
|
160
|
-
await sandbox.save(); // Persist to IndexedDB
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Clean up
|
|
164
|
-
sandbox.close();
|
|
71
|
+
const result = await runAgent(prompt, options?);
|
|
165
72
|
```
|
|
166
73
|
|
|
167
|
-
|
|
168
|
-
> Use it to capture the `BundleResult` for loading modules.
|
|
74
|
+
#### Call Options
|
|
169
75
|
|
|
170
|
-
|
|
76
|
+
| Option | Type | Description |
|
|
77
|
+
| ---------- | --------------- | --------------------------------------------------------------- |
|
|
78
|
+
| `validate` | `(module) => M` | Validate exports during build. Errors are visible to the agent. |
|
|
79
|
+
| `timeout` | `number` | Max time in ms. Throws `AbortError` if exceeded. |
|
|
80
|
+
| `signal` | `AbortSignal` | For external cancellation. |
|
|
171
81
|
|
|
172
|
-
|
|
82
|
+
#### Result
|
|
173
83
|
|
|
174
84
|
```typescript
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// Create multiple sandboxes - they share TypeScript libs and bundler
|
|
182
|
-
const sandbox1 = await manager.createSandbox({ id: "agent-1" });
|
|
183
|
-
const sandbox2 = await manager.createSandbox({ id: "agent-2" });
|
|
184
|
-
|
|
185
|
-
// Run operations in parallel
|
|
186
|
-
await Promise.all([sandbox1.bash.exec("build"), sandbox2.bash.exec("build")]);
|
|
187
|
-
|
|
188
|
-
// Save all with unsaved changes
|
|
189
|
-
await manager.saveAll();
|
|
190
|
-
|
|
191
|
-
// Get dirty sandbox IDs
|
|
192
|
-
const unsaved = manager.getDirtySandboxes();
|
|
193
|
-
|
|
194
|
-
// Clean up
|
|
195
|
-
manager.destroyAll();
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Sandbox Options
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
interface SandboxOptions {
|
|
202
|
-
// Filesystem configuration
|
|
203
|
-
fsOptions?: {
|
|
204
|
-
dbName?: string; // IndexedDB database name
|
|
205
|
-
initialFiles?: Record<string, string>;
|
|
206
|
-
maxSizeBytes?: number; // Filesystem size limit
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
// Build configuration
|
|
210
|
-
tsconfigPath?: string; // Default: "/tsconfig.json"
|
|
211
|
-
|
|
212
|
-
// Module sharing (see React Integration)
|
|
213
|
-
sharedModules?: string[];
|
|
214
|
-
|
|
215
|
-
// Build callback - use to capture successful build results
|
|
216
|
-
onBuild?: (result: BundleResult) => void | Promise<void>;
|
|
217
|
-
|
|
218
|
-
// Custom bash commands
|
|
219
|
-
customCommands?: Command[];
|
|
85
|
+
interface BuildResult<T, M> {
|
|
86
|
+
result: T | undefined; // Return value from your build function
|
|
87
|
+
error: Error | null; // Error if build function threw
|
|
88
|
+
bundle: BundleResult | null; // The compiled JavaScript bundle
|
|
89
|
+
module: M | null; // Loaded module exports (validated if validate provided)
|
|
90
|
+
sandbox: Sandbox; // The sandbox used (for inspection or reuse)
|
|
220
91
|
}
|
|
221
92
|
```
|
|
222
93
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
## Package Management
|
|
94
|
+
### Validation
|
|
226
95
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
### Installing Packages
|
|
96
|
+
Validation runs as part of the `build` command. If it throws, the build fails and the agent sees the error, allowing it to fix the code and retry.
|
|
230
97
|
|
|
231
98
|
```typescript
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
1. Resolves package version from esm.sh CDN
|
|
243
|
-
2. Fetches TypeScript type definitions
|
|
244
|
-
3. Stores types in `/node_modules/<package>/`
|
|
245
|
-
4. Updates `/package.json` with installed version
|
|
246
|
-
5. At bundle time, imports are rewritten to esm.sh URLs
|
|
247
|
-
|
|
248
|
-
### Package Commands
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
install <package>[@version] [...] # Install packages
|
|
252
|
-
uninstall <package> [...] # Remove packages
|
|
253
|
-
list # Show installed packages
|
|
99
|
+
const result = await runAgent("Create a counter", {
|
|
100
|
+
validate: (mod) => {
|
|
101
|
+
if (typeof mod.App !== "function") {
|
|
102
|
+
throw new Error("Must export an App component");
|
|
103
|
+
}
|
|
104
|
+
return { App: mod.App as React.ComponentType };
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
// result.module is typed as { App: React.ComponentType } | null
|
|
254
108
|
```
|
|
255
109
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
## Loading Modules
|
|
259
|
-
|
|
260
|
-
After building, use loaders to access the compiled code:
|
|
110
|
+
With Zod:
|
|
261
111
|
|
|
262
112
|
```typescript
|
|
263
|
-
import {
|
|
264
|
-
|
|
265
|
-
// Load all exports
|
|
266
|
-
const mod = await loadModule<{ add: Function; multiply: Function }>(
|
|
267
|
-
result.bundle,
|
|
268
|
-
);
|
|
113
|
+
import { z } from "zod";
|
|
269
114
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"add",
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
// Load default export
|
|
277
|
-
const Calculator = await loadDefault<typeof Calculator>(result.bundle);
|
|
278
|
-
|
|
279
|
-
// Check what's exported
|
|
280
|
-
const names = await getExportNames(result.bundle); // ["add", "multiply", "default"]
|
|
281
|
-
const hasAdd = await hasExport(result.bundle, "add"); // true
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## Advanced Usage
|
|
287
|
-
|
|
288
|
-
### Direct Bundler Access
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
import { bundle, initBundler } from "sandlot";
|
|
292
|
-
|
|
293
|
-
// Pre-warm the bundler (optional)
|
|
294
|
-
await initBundler();
|
|
295
|
-
|
|
296
|
-
const result = await bundle({
|
|
297
|
-
fs: myFilesystem,
|
|
298
|
-
entryPoint: "/src/index.ts",
|
|
299
|
-
format: "esm", // "esm" | "iife" | "cjs"
|
|
300
|
-
minify: false,
|
|
301
|
-
sourcemap: false,
|
|
302
|
-
sharedModules: ["react"],
|
|
303
|
-
npmImports: "cdn", // "cdn" | "external" | "bundle"
|
|
115
|
+
const Schema = z.object({
|
|
116
|
+
App: z.custom<React.ComponentType>((v) => typeof v === "function"),
|
|
117
|
+
initialCount: z.number().optional(),
|
|
304
118
|
});
|
|
305
119
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
console.log(result.includedFiles);
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### Direct Type Checking
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
import {
|
|
315
|
-
typecheck,
|
|
316
|
-
formatDiagnostics,
|
|
317
|
-
formatDiagnosticsForAgent,
|
|
318
|
-
} from "sandlot";
|
|
319
|
-
|
|
320
|
-
const result = await typecheck({
|
|
321
|
-
fs: myFilesystem,
|
|
322
|
-
entryPoint: "/src/index.ts",
|
|
323
|
-
tsconfigPath: "/tsconfig.json",
|
|
324
|
-
libFiles: myLibFiles, // Map of lib name to content
|
|
120
|
+
const result = await runAgent("Create a counter", {
|
|
121
|
+
validate: (mod) => Schema.parse(mod),
|
|
325
122
|
});
|
|
326
|
-
|
|
327
|
-
if (result.hasErrors) {
|
|
328
|
-
// Human-readable format
|
|
329
|
-
console.log(formatDiagnostics(result.diagnostics));
|
|
330
|
-
|
|
331
|
-
// Agent-friendly format
|
|
332
|
-
console.log(formatDiagnosticsForAgent(result.diagnostics));
|
|
333
|
-
}
|
|
334
123
|
```
|
|
335
124
|
|
|
336
|
-
###
|
|
125
|
+
### Cancellation
|
|
337
126
|
|
|
338
127
|
```typescript
|
|
339
|
-
|
|
128
|
+
const controller = new AbortController();
|
|
340
129
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
initialFiles: { "/README.md": "# Hello" },
|
|
345
|
-
maxSizeBytes: 50 * 1024 * 1024, // 50MB limit
|
|
130
|
+
const promise = runAgent("Create a dashboard", {
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
timeout: 60_000, // 1 minute
|
|
346
133
|
});
|
|
347
134
|
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
initialFiles: { "/README.md": "# Hello" },
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
// File operations
|
|
354
|
-
await fs.writeFile("/src/index.ts", "export const x = 1;");
|
|
355
|
-
const content = await fs.readFile("/src/index.ts");
|
|
356
|
-
const exists = await fs.exists("/src/index.ts");
|
|
357
|
-
await fs.mkdir("/src/utils", { recursive: true });
|
|
358
|
-
await fs.rm("/src/old.ts");
|
|
359
|
-
|
|
360
|
-
// Persistence
|
|
361
|
-
if (fs.isDirty()) {
|
|
362
|
-
await fs.save();
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
fs.close();
|
|
135
|
+
// Cancel on user action
|
|
136
|
+
cancelButton.onclick = () => controller.abort();
|
|
366
137
|
```
|
|
367
138
|
|
|
368
|
-
|
|
139
|
+
## Sandbox API
|
|
369
140
|
|
|
370
|
-
|
|
141
|
+
For lower-level control, use `createSandbox()` directly.
|
|
371
142
|
|
|
372
143
|
```typescript
|
|
373
|
-
import {
|
|
374
|
-
|
|
375
|
-
// Create custom resources
|
|
376
|
-
const resources = await createSharedResources({
|
|
377
|
-
libs: ["ES2022", "DOM", "DOM.Iterable"],
|
|
378
|
-
});
|
|
144
|
+
import { createSandbox } from "sandlot";
|
|
379
145
|
|
|
380
|
-
// Use with sandbox
|
|
381
146
|
const sandbox = await createSandbox({
|
|
382
|
-
|
|
147
|
+
initialFiles: {
|
|
148
|
+
"/src/index.ts": "export const x = 1;",
|
|
149
|
+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
|
|
150
|
+
},
|
|
151
|
+
sharedModules: ["react", "react-dom/client"],
|
|
152
|
+
onBuild: (result) => console.log("Build succeeded:", result),
|
|
383
153
|
});
|
|
384
154
|
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
## TypeScript Configuration
|
|
392
|
-
|
|
393
|
-
Sandlot respects your `tsconfig.json`:
|
|
155
|
+
// Execute shell commands
|
|
156
|
+
await sandbox.bash.exec("tsc /src/index.ts"); // Type check
|
|
157
|
+
await sandbox.bash.exec("build /src/index.ts"); // Bundle
|
|
158
|
+
await sandbox.bash.exec("install lodash"); // Install package
|
|
159
|
+
await sandbox.bash.exec("list"); // List packages
|
|
394
160
|
|
|
395
|
-
|
|
396
|
-
{
|
|
397
|
-
|
|
398
|
-
"target": "ES2020",
|
|
399
|
-
"module": "ESNext",
|
|
400
|
-
"moduleResolution": "bundler",
|
|
401
|
-
"jsx": "react-jsx",
|
|
402
|
-
"strict": true,
|
|
403
|
-
"esModuleInterop": true,
|
|
404
|
-
"skipLibCheck": true,
|
|
405
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"]
|
|
406
|
-
}
|
|
161
|
+
// Access the last successful build
|
|
162
|
+
if (sandbox.lastBuild) {
|
|
163
|
+
const { bundle, module } = sandbox.lastBuild;
|
|
407
164
|
}
|
|
165
|
+
|
|
166
|
+
// Serialize state for persistence
|
|
167
|
+
const state = sandbox.getState();
|
|
168
|
+
localStorage.setItem("project", JSON.stringify(state));
|
|
408
169
|
```
|
|
409
170
|
|
|
410
|
-
|
|
171
|
+
### Sandbox Options
|
|
411
172
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
173
|
+
| Option | Type | Description |
|
|
174
|
+
| --------------- | ------------------------ | --------------------------------------------------- |
|
|
175
|
+
| `initialFiles` | `Record<string, string>` | Files to populate the filesystem with. |
|
|
176
|
+
| `sharedModules` | `string[]` | Modules to resolve from host (e.g., `['react']`). |
|
|
177
|
+
| `tsconfigPath` | `string` | Path to tsconfig.json (default: `/tsconfig.json`). |
|
|
178
|
+
| `onBuild` | `(result) => void` | Callback when a build succeeds. |
|
|
179
|
+
| `bashOptions` | `SandboxBashOptions` | Options for the just-bash shell (env, limits). |
|
|
419
180
|
|
|
420
|
-
|
|
181
|
+
## Shell Commands
|
|
421
182
|
|
|
422
|
-
|
|
183
|
+
The sandbox provides these built-in commands:
|
|
423
184
|
|
|
424
|
-
|
|
185
|
+
| Command | Description |
|
|
186
|
+
| ------------------------- | ----------------------------------- |
|
|
187
|
+
| `tsc [entry]` | Type check (uses tsconfig.json) |
|
|
188
|
+
| `build [entry] [options]` | Bundle (runs typecheck first) |
|
|
189
|
+
| `install <pkg>` | Install npm package (fetches types) |
|
|
190
|
+
| `uninstall <pkg>` | Remove package |
|
|
191
|
+
| `list` | List installed packages |
|
|
192
|
+
| `run <entry>` | Execute a script |
|
|
425
193
|
|
|
426
|
-
|
|
427
|
-
// Sandbox API
|
|
428
|
-
export { createSandbox, createInMemorySandbox } from "sandlot";
|
|
429
|
-
export { SandboxManager, createSandboxManager } from "sandlot";
|
|
194
|
+
Build options: `--format <esm\|iife\|cjs>`, `--minify`, `--skip-typecheck`
|
|
430
195
|
|
|
431
|
-
|
|
432
|
-
export { loadModule, loadExport, loadDefault } from "sandlot";
|
|
433
|
-
export { getExportNames, hasExport } from "sandlot";
|
|
196
|
+
## Shared Modules
|
|
434
197
|
|
|
435
|
-
|
|
436
|
-
export { registerSharedModules, clearSharedModules } from "sandlot";
|
|
437
|
-
|
|
438
|
-
// Types
|
|
439
|
-
export type { Sandbox, SandboxOptions } from "sandlot";
|
|
440
|
-
export type { BundleResult, TypecheckResult } from "sandlot";
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### Exports from `sandlot/react`
|
|
198
|
+
To avoid duplicate library instances (important for React context/hooks), register shared modules before creating sandboxes:
|
|
444
199
|
|
|
445
200
|
```typescript
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
201
|
+
import { registerSharedModules } from "sandlot";
|
|
202
|
+
import * as React from "react";
|
|
203
|
+
import * as ReactDOM from "react-dom/client";
|
|
204
|
+
|
|
205
|
+
registerSharedModules({
|
|
206
|
+
react: React,
|
|
207
|
+
"react-dom/client": ReactDOM,
|
|
208
|
+
});
|
|
449
209
|
```
|
|
450
210
|
|
|
451
|
-
|
|
211
|
+
Then include them in `sharedModules` when creating a sandbox.
|
|
452
212
|
|
|
453
|
-
##
|
|
213
|
+
## Cross-Origin Isolation
|
|
454
214
|
|
|
455
|
-
|
|
215
|
+
For optimal esbuild-wasm performance, enable cross-origin isolation by adding these headers to your dev server:
|
|
456
216
|
|
|
457
|
-
|
|
217
|
+
```
|
|
218
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
219
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
220
|
+
```
|
|
458
221
|
|
|
459
|
-
|
|
460
|
-
// vite.config.ts
|
|
461
|
-
import { defineConfig } from "vite";
|
|
222
|
+
In Vite:
|
|
462
223
|
|
|
224
|
+
```typescript
|
|
463
225
|
export default defineConfig({
|
|
464
226
|
plugins: [
|
|
465
|
-
// Add COOP/COEP headers for SharedArrayBuffer (recommended for esbuild-wasm)
|
|
466
227
|
{
|
|
467
|
-
name: "
|
|
228
|
+
name: "isolation",
|
|
468
229
|
configureServer: (server) => {
|
|
469
|
-
server.middlewares.use((
|
|
230
|
+
server.middlewares.use((_, res, next) => {
|
|
470
231
|
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
471
232
|
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
472
233
|
next();
|
|
@@ -477,37 +238,6 @@ export default defineConfig({
|
|
|
477
238
|
});
|
|
478
239
|
```
|
|
479
240
|
|
|
480
|
-
> **Note:** Without these headers, sandlot will still work but may have reduced performance.
|
|
481
|
-
> The console will show a warning if cross-origin isolation is not enabled.
|
|
482
|
-
|
|
483
|
-
### Next.js
|
|
484
|
-
|
|
485
|
-
Add headers in `next.config.js`:
|
|
486
|
-
|
|
487
|
-
```javascript
|
|
488
|
-
module.exports = {
|
|
489
|
-
async headers() {
|
|
490
|
-
return [
|
|
491
|
-
{
|
|
492
|
-
source: "/(.*)",
|
|
493
|
-
headers: [
|
|
494
|
-
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
|
|
495
|
-
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
496
|
-
],
|
|
497
|
-
},
|
|
498
|
-
];
|
|
499
|
-
},
|
|
500
|
-
};
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
---
|
|
504
|
-
|
|
505
|
-
## Requirements
|
|
506
|
-
|
|
507
|
-
- Modern browser with ES2020 support
|
|
508
|
-
- IndexedDB (for persistent filesystems)
|
|
509
|
-
- WebAssembly (for esbuild)
|
|
510
|
-
|
|
511
241
|
## License
|
|
512
242
|
|
|
513
243
|
MIT
|