sandlot 0.1.3 → 0.2.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.
- package/dist/browser/bundler.d.ts +68 -0
- package/dist/browser/bundler.d.ts.map +1 -0
- package/dist/browser/executor.d.ts +46 -0
- package/dist/browser/executor.d.ts.map +1 -0
- package/dist/browser/index.d.ts +9 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2692 -0
- package/dist/browser/preset.d.ts +63 -0
- package/dist/browser/preset.d.ts.map +1 -0
- package/dist/commands/index.d.ts +20 -11
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/types.d.ts +31 -132
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/core/bundler-utils.d.ts +142 -0
- package/dist/core/bundler-utils.d.ts.map +1 -0
- package/dist/core/esm-types-resolver.d.ts +125 -0
- package/dist/core/esm-types-resolver.d.ts.map +1 -0
- package/dist/core/executor.d.ts +35 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/{fs.d.ts → core/fs.d.ts} +27 -29
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/sandbox.d.ts +30 -0
- package/dist/core/sandbox.d.ts.map +1 -0
- package/dist/core/sandlot.d.ts +30 -0
- package/dist/core/sandlot.d.ts.map +1 -0
- package/dist/core/shared-module-registry.d.ts +46 -0
- package/dist/core/shared-module-registry.d.ts.map +1 -0
- package/dist/core/typechecker.d.ts +60 -0
- package/dist/core/typechecker.d.ts.map +1 -0
- package/dist/index.d.ts +11 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1405 -2049
- package/dist/node/bundler.d.ts +48 -0
- package/dist/node/bundler.d.ts.map +1 -0
- package/dist/node/executor.d.ts +48 -0
- package/dist/node/executor.d.ts.map +1 -0
- package/dist/node/index.d.ts +9 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2646 -0
- package/dist/node/preset.d.ts +62 -0
- package/dist/node/preset.d.ts.map +1 -0
- package/dist/types.d.ts +525 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +27 -8
- package/src/browser/bundler.ts +294 -0
- package/src/browser/executor.ts +71 -0
- package/src/browser/index.ts +57 -0
- package/src/browser/preset.ts +179 -0
- package/src/commands/index.ts +526 -43
- package/src/commands/types.ts +82 -146
- package/src/core/bundler-utils.ts +630 -0
- package/src/core/esm-types-resolver.ts +432 -0
- package/src/core/executor.ts +161 -0
- package/src/{fs.ts → core/fs.ts} +59 -37
- package/src/core/sandbox.ts +621 -0
- package/src/core/sandlot.ts +77 -0
- package/src/core/shared-module-registry.ts +138 -0
- package/src/core/typechecker.ts +607 -0
- package/src/index.ts +104 -139
- package/src/node/bundler.ts +194 -0
- package/src/node/executor.ts +87 -0
- package/src/node/index.ts +39 -0
- package/src/node/preset.ts +178 -0
- package/src/types.ts +668 -0
- package/README.md +0 -243
- package/dist/build-emitter.d.ts +0 -47
- package/dist/build-emitter.d.ts.map +0 -1
- package/dist/builder.d.ts +0 -370
- package/dist/builder.d.ts.map +0 -1
- package/dist/bundler.d.ts +0 -148
- package/dist/bundler.d.ts.map +0 -1
- package/dist/commands/compile.d.ts +0 -13
- package/dist/commands/compile.d.ts.map +0 -1
- package/dist/commands/packages.d.ts +0 -17
- package/dist/commands/packages.d.ts.map +0 -1
- package/dist/commands/run.d.ts +0 -40
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands.d.ts +0 -179
- package/dist/commands.d.ts.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/internal.d.ts +0 -79
- package/dist/internal.d.ts.map +0 -1
- package/dist/internal.js +0 -1976
- package/dist/loader.d.ts +0 -164
- package/dist/loader.d.ts.map +0 -1
- package/dist/packages.d.ts +0 -199
- package/dist/packages.d.ts.map +0 -1
- package/dist/runner.d.ts +0 -314
- package/dist/runner.d.ts.map +0 -1
- package/dist/sandbox-manager.d.ts +0 -261
- package/dist/sandbox-manager.d.ts.map +0 -1
- package/dist/sandbox.d.ts +0 -267
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/shared-modules.d.ts +0 -148
- package/dist/shared-modules.d.ts.map +0 -1
- package/dist/shared-resources.d.ts +0 -102
- package/dist/shared-resources.d.ts.map +0 -1
- package/dist/ts-libs.d.ts +0 -98
- package/dist/ts-libs.d.ts.map +0 -1
- package/dist/typechecker.d.ts +0 -127
- package/dist/typechecker.d.ts.map +0 -1
- package/src/build-emitter.ts +0 -64
- package/src/builder.ts +0 -498
- package/src/bundler.ts +0 -542
- package/src/commands/compile.ts +0 -236
- package/src/commands/packages.ts +0 -154
- package/src/commands/run.ts +0 -245
- package/src/internal.ts +0 -119
- package/src/loader.ts +0 -229
- package/src/packages.ts +0 -936
- package/src/sandbox.ts +0 -396
- package/src/shared-modules.ts +0 -280
- package/src/shared-resources.ts +0 -166
- package/src/ts-libs.ts +0 -320
- package/src/typechecker.ts +0 -635
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser bundler implementation using esbuild-wasm.
|
|
3
|
+
*
|
|
4
|
+
* This module handles WASM initialization and provides a bundler that
|
|
5
|
+
* works entirely in the browser.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type * as EsbuildTypes from "esbuild-wasm";
|
|
9
|
+
import type {
|
|
10
|
+
IBundler,
|
|
11
|
+
BundleOptions,
|
|
12
|
+
BundleResult,
|
|
13
|
+
BundleWarning,
|
|
14
|
+
BundleError,
|
|
15
|
+
} from "../types";
|
|
16
|
+
import {
|
|
17
|
+
createVfsPlugin,
|
|
18
|
+
isEsbuildBuildFailure,
|
|
19
|
+
convertEsbuildMessage,
|
|
20
|
+
} from "../core/bundler-utils";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* esbuild-wasm version - should match what's in package.json
|
|
24
|
+
*/
|
|
25
|
+
const ESBUILD_VERSION = "0.27.2";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Global Singleton for esbuild-wasm initialization
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// esbuild-wasm can only be initialized once per page. We track this globally
|
|
31
|
+
// so multiple EsbuildWasmBundler instances can share the same initialization.
|
|
32
|
+
|
|
33
|
+
interface EsbuildGlobalState {
|
|
34
|
+
esbuild: typeof EsbuildTypes | null;
|
|
35
|
+
initialized: boolean;
|
|
36
|
+
initPromise: Promise<void> | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const GLOBAL_KEY = "__sandlot_esbuild__";
|
|
40
|
+
|
|
41
|
+
function getGlobalState(): EsbuildGlobalState {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
const g = globalThis as any;
|
|
44
|
+
if (!g[GLOBAL_KEY]) {
|
|
45
|
+
g[GLOBAL_KEY] = {
|
|
46
|
+
esbuild: null,
|
|
47
|
+
initialized: false,
|
|
48
|
+
initPromise: null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return g[GLOBAL_KEY];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface EsbuildWasmBundlerOptions {
|
|
55
|
+
/**
|
|
56
|
+
* URL to the esbuild WASM file.
|
|
57
|
+
* @default "https://unpkg.com/esbuild-wasm@{version}/esbuild.wasm"
|
|
58
|
+
*/
|
|
59
|
+
wasmUrl?: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* URL to load esbuild-wasm module from.
|
|
63
|
+
* @default "https://esm.sh/esbuild-wasm@{version}"
|
|
64
|
+
*/
|
|
65
|
+
esbuildUrl?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Base URL for CDN imports.
|
|
69
|
+
* npm imports like "lodash" are rewritten to "{cdnBaseUrl}/lodash@{version}".
|
|
70
|
+
* @default "https://esm.sh"
|
|
71
|
+
*/
|
|
72
|
+
cdnBaseUrl?: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether to initialize immediately on construction.
|
|
76
|
+
* If false, initialization happens lazily on first bundle() call.
|
|
77
|
+
* @default false
|
|
78
|
+
*/
|
|
79
|
+
eagerInit?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Browser bundler implementation using esbuild-wasm.
|
|
84
|
+
*
|
|
85
|
+
* Handles WASM initialization internally. The first bundle() call
|
|
86
|
+
* will wait for initialization if not already complete.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const bundler = new EsbuildWasmBundler();
|
|
91
|
+
* await bundler.initialize();
|
|
92
|
+
*
|
|
93
|
+
* const result = await bundler.bundle({
|
|
94
|
+
* fs: myFilesystem,
|
|
95
|
+
* entryPoint: "/src/index.ts",
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export class EsbuildWasmBundler implements IBundler {
|
|
100
|
+
private options: EsbuildWasmBundlerOptions;
|
|
101
|
+
|
|
102
|
+
constructor(options: EsbuildWasmBundlerOptions = {}) {
|
|
103
|
+
this.options = {
|
|
104
|
+
cdnBaseUrl: "https://esm.sh",
|
|
105
|
+
...options,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (options.eagerInit) {
|
|
109
|
+
this.initialize();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Initialize the esbuild WASM module.
|
|
115
|
+
* Called automatically on first bundle() if not already initialized.
|
|
116
|
+
*
|
|
117
|
+
* Uses a global singleton pattern since esbuild-wasm can only be
|
|
118
|
+
* initialized once per page.
|
|
119
|
+
*/
|
|
120
|
+
async initialize(): Promise<void> {
|
|
121
|
+
const state = getGlobalState();
|
|
122
|
+
|
|
123
|
+
// Already initialized globally
|
|
124
|
+
if (state.initialized && state.esbuild) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Another instance is initializing - wait for it
|
|
129
|
+
if (state.initPromise) {
|
|
130
|
+
await state.initPromise;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// We're the first - do the initialization
|
|
135
|
+
state.initPromise = this.doInitialize(state);
|
|
136
|
+
await state.initPromise;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async doInitialize(state: EsbuildGlobalState): Promise<void> {
|
|
140
|
+
// Check for cross-origin isolation (needed for SharedArrayBuffer)
|
|
141
|
+
this.checkCrossOriginIsolation();
|
|
142
|
+
|
|
143
|
+
// Load esbuild-wasm from CDN
|
|
144
|
+
const esbuildUrl =
|
|
145
|
+
this.options.esbuildUrl ?? `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
146
|
+
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
+
const mod: any = await import(/* @vite-ignore */ esbuildUrl);
|
|
149
|
+
const esbuild = mod.default ?? mod;
|
|
150
|
+
|
|
151
|
+
if (typeof esbuild?.initialize !== "function") {
|
|
152
|
+
throw new Error(
|
|
153
|
+
"Failed to load esbuild-wasm: initialize function not found"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Initialize with WASM binary
|
|
158
|
+
const wasmUrl =
|
|
159
|
+
this.options.wasmUrl ??
|
|
160
|
+
`https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
|
|
161
|
+
|
|
162
|
+
await esbuild.initialize({ wasmURL: wasmUrl });
|
|
163
|
+
|
|
164
|
+
// Store in global state
|
|
165
|
+
state.esbuild = esbuild;
|
|
166
|
+
state.initialized = true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get the initialized esbuild instance.
|
|
171
|
+
*/
|
|
172
|
+
private getEsbuild(): typeof EsbuildTypes {
|
|
173
|
+
const state = getGlobalState();
|
|
174
|
+
if (!state.esbuild) {
|
|
175
|
+
throw new Error("esbuild not initialized - call initialize() first");
|
|
176
|
+
}
|
|
177
|
+
return state.esbuild;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private checkCrossOriginIsolation(): void {
|
|
181
|
+
if (typeof window === "undefined") return;
|
|
182
|
+
|
|
183
|
+
if (!window.crossOriginIsolated) {
|
|
184
|
+
console.warn(
|
|
185
|
+
"[sandlot] Cross-origin isolation is not enabled. " +
|
|
186
|
+
"esbuild-wasm may have reduced performance or fail on some browsers.\n" +
|
|
187
|
+
"To enable, add these headers to your server:\n" +
|
|
188
|
+
" Cross-Origin-Embedder-Policy: require-corp\n" +
|
|
189
|
+
" Cross-Origin-Opener-Policy: same-origin"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async bundle(options: BundleOptions): Promise<BundleResult> {
|
|
195
|
+
await this.initialize();
|
|
196
|
+
|
|
197
|
+
const esbuild = this.getEsbuild();
|
|
198
|
+
|
|
199
|
+
const {
|
|
200
|
+
fs,
|
|
201
|
+
entryPoint,
|
|
202
|
+
installedPackages = {},
|
|
203
|
+
sharedModules = [],
|
|
204
|
+
sharedModuleRegistry,
|
|
205
|
+
external = [],
|
|
206
|
+
format = "esm",
|
|
207
|
+
minify = false,
|
|
208
|
+
sourcemap = false,
|
|
209
|
+
target = ["es2020"],
|
|
210
|
+
} = options;
|
|
211
|
+
|
|
212
|
+
// Normalize entry point to absolute path
|
|
213
|
+
const normalizedEntry = entryPoint.startsWith("/")
|
|
214
|
+
? entryPoint
|
|
215
|
+
: `/${entryPoint}`;
|
|
216
|
+
|
|
217
|
+
// Verify entry point exists
|
|
218
|
+
if (!fs.exists(normalizedEntry)) {
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
errors: [{ text: `Entry point not found: ${normalizedEntry}` }],
|
|
222
|
+
warnings: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Track files included in the bundle
|
|
227
|
+
const includedFiles = new Set<string>();
|
|
228
|
+
|
|
229
|
+
// Create the VFS plugin
|
|
230
|
+
const plugin = createVfsPlugin({
|
|
231
|
+
fs,
|
|
232
|
+
entryPoint: normalizedEntry,
|
|
233
|
+
installedPackages,
|
|
234
|
+
sharedModules: new Set(sharedModules),
|
|
235
|
+
sharedModuleRegistry: sharedModuleRegistry ?? null,
|
|
236
|
+
cdnBaseUrl: this.options.cdnBaseUrl!,
|
|
237
|
+
includedFiles,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
// Run esbuild
|
|
242
|
+
const result = await esbuild.build({
|
|
243
|
+
entryPoints: [normalizedEntry],
|
|
244
|
+
bundle: true,
|
|
245
|
+
write: false,
|
|
246
|
+
format,
|
|
247
|
+
minify,
|
|
248
|
+
sourcemap: sourcemap ? "inline" : false,
|
|
249
|
+
target,
|
|
250
|
+
external,
|
|
251
|
+
// Cast to esbuild's Plugin type since our minimal interface is compatible
|
|
252
|
+
plugins: [plugin as EsbuildTypes.Plugin],
|
|
253
|
+
jsx: "automatic",
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const code = result.outputFiles?.[0]?.text ?? "";
|
|
257
|
+
|
|
258
|
+
// Convert esbuild warnings to our format
|
|
259
|
+
const warnings: BundleWarning[] = result.warnings.map((w) =>
|
|
260
|
+
convertEsbuildMessage(w)
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
success: true,
|
|
265
|
+
code,
|
|
266
|
+
warnings,
|
|
267
|
+
includedFiles: Array.from(includedFiles),
|
|
268
|
+
};
|
|
269
|
+
} catch (err) {
|
|
270
|
+
// esbuild throws BuildFailure with .errors array
|
|
271
|
+
if (isEsbuildBuildFailure(err)) {
|
|
272
|
+
const errors: BundleError[] = err.errors.map((e) =>
|
|
273
|
+
convertEsbuildMessage(e)
|
|
274
|
+
);
|
|
275
|
+
const warnings: BundleWarning[] = err.warnings.map((w) =>
|
|
276
|
+
convertEsbuildMessage(w)
|
|
277
|
+
);
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
errors,
|
|
281
|
+
warnings,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Unknown error - wrap it
|
|
286
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
errors: [{ text: message }],
|
|
290
|
+
warnings: [],
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main thread executor for browser environments.
|
|
3
|
+
*
|
|
4
|
+
* This executor runs code directly in the main thread. It provides no
|
|
5
|
+
* isolation from the host environment - use only for trusted code.
|
|
6
|
+
*
|
|
7
|
+
* For untrusted code, consider using a Worker or iframe-based executor
|
|
8
|
+
* that provides proper sandboxing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { IExecutor } from "../types";
|
|
12
|
+
import { createBasicExecutor, type BasicExecutorOptions } from "../core/executor";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for creating a MainThreadExecutor.
|
|
16
|
+
*/
|
|
17
|
+
export type MainThreadExecutorOptions = BasicExecutorOptions;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load a module from code using a Blob URL.
|
|
21
|
+
* The URL is revoked after import to avoid memory leaks.
|
|
22
|
+
*/
|
|
23
|
+
async function loadModuleFromBlobUrl(code: string): Promise<Record<string, unknown>> {
|
|
24
|
+
const blob = new Blob([code], { type: "application/javascript" });
|
|
25
|
+
const url = URL.createObjectURL(blob);
|
|
26
|
+
try {
|
|
27
|
+
return await import(/* @vite-ignore */ url);
|
|
28
|
+
} finally {
|
|
29
|
+
URL.revokeObjectURL(url);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Executor that runs code in the main browser thread.
|
|
35
|
+
*
|
|
36
|
+
* WARNING: This executor provides NO isolation. The executed code has
|
|
37
|
+
* full access to the page's DOM, global variables, and network.
|
|
38
|
+
* Only use for trusted code (e.g., code you're developing).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const executor = createMainThreadExecutor();
|
|
43
|
+
* const result = await executor.execute(bundledCode, {
|
|
44
|
+
* entryExport: 'main',
|
|
45
|
+
* context: { args: ['--verbose'] },
|
|
46
|
+
* timeout: 5000,
|
|
47
|
+
* });
|
|
48
|
+
* console.log(result.logs);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export class MainThreadExecutor implements IExecutor {
|
|
52
|
+
private executor: IExecutor;
|
|
53
|
+
|
|
54
|
+
constructor(options: MainThreadExecutorOptions = {}) {
|
|
55
|
+
this.executor = createBasicExecutor(loadModuleFromBlobUrl, options);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
execute: IExecutor["execute"] = (...args) => this.executor.execute(...args);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a main thread executor.
|
|
63
|
+
*
|
|
64
|
+
* @param options - Executor options
|
|
65
|
+
* @returns A new MainThreadExecutor instance
|
|
66
|
+
*/
|
|
67
|
+
export function createMainThreadExecutor(
|
|
68
|
+
options?: MainThreadExecutorOptions
|
|
69
|
+
): MainThreadExecutor {
|
|
70
|
+
return new MainThreadExecutor(options);
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Sandlot v2 - Browser Entry Point
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Browser-specific implementations for Sandlot.
|
|
6
|
+
// Uses esbuild-wasm for bundling.
|
|
7
|
+
//
|
|
8
|
+
// Note: EsmTypesResolver is platform-independent and exported from the main
|
|
9
|
+
// "sandlot" entry point, not from "sandlot/browser".
|
|
10
|
+
//
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Browser Polyfills - inject before anything else loads
|
|
15
|
+
// =============================================================================
|
|
16
|
+
//
|
|
17
|
+
// Some dependencies (like just-bash) reference Node.js globals.
|
|
18
|
+
// Provide shims so they work in the browser without user configuration.
|
|
19
|
+
//
|
|
20
|
+
if (typeof window !== "undefined" && typeof globalThis.process === "undefined") {
|
|
21
|
+
(globalThis as Record<string, unknown>).process = {
|
|
22
|
+
env: {},
|
|
23
|
+
platform: "browser",
|
|
24
|
+
version: "v20.0.0",
|
|
25
|
+
browser: true,
|
|
26
|
+
cwd: () => "/",
|
|
27
|
+
nextTick: (fn: () => void) => setTimeout(fn, 0),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// -----------------------------------------------------------------------------
|
|
32
|
+
// Typechecker (platform-agnostic: re-exported for convenience)
|
|
33
|
+
// -----------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export { Typechecker, createTypechecker } from "../core/typechecker";
|
|
36
|
+
export type { TypecheckerOptions } from "../core/typechecker";
|
|
37
|
+
|
|
38
|
+
// -----------------------------------------------------------------------------
|
|
39
|
+
// Bundler (browser-specific: uses esbuild-wasm)
|
|
40
|
+
// -----------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export { EsbuildWasmBundler } from "./bundler";
|
|
43
|
+
export type { EsbuildWasmBundlerOptions } from "./bundler";
|
|
44
|
+
|
|
45
|
+
// -----------------------------------------------------------------------------
|
|
46
|
+
// Executor (browser-specific: runs in main thread)
|
|
47
|
+
// -----------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export { MainThreadExecutor, createMainThreadExecutor } from "./executor";
|
|
50
|
+
export type { MainThreadExecutorOptions } from "./executor";
|
|
51
|
+
|
|
52
|
+
// -----------------------------------------------------------------------------
|
|
53
|
+
// Convenience Preset
|
|
54
|
+
// -----------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
export { createBrowserSandlot } from "./preset";
|
|
57
|
+
export type { CreateBrowserSandlotOptions } from "./preset";
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createSandlot } from "../core/sandlot";
|
|
2
|
+
import {
|
|
3
|
+
EsmTypesResolver,
|
|
4
|
+
type EsmTypesResolverOptions,
|
|
5
|
+
} from "../core/esm-types-resolver";
|
|
6
|
+
import type { Sandlot, SandlotOptions } from "../types";
|
|
7
|
+
import { EsbuildWasmBundler, type EsbuildWasmBundlerOptions } from "./bundler";
|
|
8
|
+
import {
|
|
9
|
+
Typechecker,
|
|
10
|
+
type TypecheckerOptions,
|
|
11
|
+
} from "../core/typechecker";
|
|
12
|
+
import {
|
|
13
|
+
MainThreadExecutor,
|
|
14
|
+
type MainThreadExecutorOptions,
|
|
15
|
+
} from "./executor";
|
|
16
|
+
|
|
17
|
+
export interface CreateBrowserSandlotOptions
|
|
18
|
+
extends Omit<SandlotOptions, "bundler" | "typechecker" | "typesResolver" | "executor"> {
|
|
19
|
+
/**
|
|
20
|
+
* Custom bundler options, or a pre-configured bundler instance.
|
|
21
|
+
*/
|
|
22
|
+
bundler?: EsbuildWasmBundlerOptions | SandlotOptions["bundler"];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Custom typechecker options, or a pre-configured typechecker instance.
|
|
26
|
+
* Set to `false` to disable type checking.
|
|
27
|
+
*/
|
|
28
|
+
typechecker?:
|
|
29
|
+
| TypecheckerOptions
|
|
30
|
+
| SandlotOptions["typechecker"]
|
|
31
|
+
| false;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Custom types resolver options, or a pre-configured resolver instance.
|
|
35
|
+
* Set to `false` to disable type resolution.
|
|
36
|
+
*/
|
|
37
|
+
typesResolver?:
|
|
38
|
+
| EsmTypesResolverOptions
|
|
39
|
+
| SandlotOptions["typesResolver"]
|
|
40
|
+
| false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Custom executor options, or a pre-configured executor instance.
|
|
44
|
+
* Set to `false` to disable execution (sandbox.run() will throw).
|
|
45
|
+
* Defaults to MainThreadExecutor.
|
|
46
|
+
*/
|
|
47
|
+
executor?:
|
|
48
|
+
| MainThreadExecutorOptions
|
|
49
|
+
| SandlotOptions["executor"]
|
|
50
|
+
| false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create a Sandlot instance pre-configured for browser environments.
|
|
55
|
+
*
|
|
56
|
+
* This is a convenience function that sets up sensible defaults:
|
|
57
|
+
* - EsbuildWasmBundler for bundling
|
|
58
|
+
* - Typechecker for type checking
|
|
59
|
+
* - FetchTypesResolver for npm type resolution
|
|
60
|
+
*
|
|
61
|
+
* @example Basic usage
|
|
62
|
+
* ```ts
|
|
63
|
+
* const sandlot = await createBrowserSandlot();
|
|
64
|
+
* const sandbox = await sandlot.createSandbox();
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @example With shared modules
|
|
68
|
+
* ```ts
|
|
69
|
+
* import React from "react";
|
|
70
|
+
* import ReactDOM from "react-dom/client";
|
|
71
|
+
*
|
|
72
|
+
* const sandlot = await createBrowserSandlot({
|
|
73
|
+
* sharedModules: {
|
|
74
|
+
* react: React,
|
|
75
|
+
* "react-dom/client": ReactDOM,
|
|
76
|
+
* },
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example Disable type checking for faster builds
|
|
81
|
+
* ```ts
|
|
82
|
+
* const sandlot = await createBrowserSandlot({
|
|
83
|
+
* typechecker: false,
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export async function createBrowserSandlot(
|
|
88
|
+
options: CreateBrowserSandlotOptions = {}
|
|
89
|
+
): Promise<Sandlot> {
|
|
90
|
+
const { bundler, typechecker, typesResolver, executor, ...rest } = options;
|
|
91
|
+
|
|
92
|
+
// Create or use provided bundler
|
|
93
|
+
const bundlerInstance = isBundler(bundler)
|
|
94
|
+
? bundler
|
|
95
|
+
: new EsbuildWasmBundler(bundler as EsbuildWasmBundlerOptions | undefined);
|
|
96
|
+
|
|
97
|
+
// Initialize bundler (loads WASM)
|
|
98
|
+
await bundlerInstance.initialize();
|
|
99
|
+
|
|
100
|
+
// Create or use provided typechecker
|
|
101
|
+
const typecheckerInstance =
|
|
102
|
+
typechecker === false
|
|
103
|
+
? undefined
|
|
104
|
+
: isTypechecker(typechecker)
|
|
105
|
+
? typechecker
|
|
106
|
+
: new Typechecker(
|
|
107
|
+
typechecker as TypecheckerOptions | undefined
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Create or use provided types resolver
|
|
111
|
+
const typesResolverInstance =
|
|
112
|
+
typesResolver === false
|
|
113
|
+
? undefined
|
|
114
|
+
: isTypesResolver(typesResolver)
|
|
115
|
+
? typesResolver
|
|
116
|
+
: new EsmTypesResolver(
|
|
117
|
+
typesResolver as EsmTypesResolverOptions | undefined
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Create or use provided executor (defaults to MainThreadExecutor)
|
|
121
|
+
const executorInstance =
|
|
122
|
+
executor === false
|
|
123
|
+
? undefined
|
|
124
|
+
: isExecutor(executor)
|
|
125
|
+
? executor
|
|
126
|
+
: new MainThreadExecutor(
|
|
127
|
+
executor as MainThreadExecutorOptions | undefined
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return createSandlot({
|
|
131
|
+
...rest,
|
|
132
|
+
bundler: bundlerInstance,
|
|
133
|
+
typechecker: typecheckerInstance,
|
|
134
|
+
typesResolver: typesResolverInstance,
|
|
135
|
+
executor: executorInstance,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Type guards for detecting pre-configured instances
|
|
140
|
+
|
|
141
|
+
function isBundler(
|
|
142
|
+
value: unknown
|
|
143
|
+
): value is SandlotOptions["bundler"] & { initialize(): Promise<void> } {
|
|
144
|
+
return (
|
|
145
|
+
typeof value === "object" &&
|
|
146
|
+
value !== null &&
|
|
147
|
+
"bundle" in value &&
|
|
148
|
+
typeof (value as { bundle: unknown }).bundle === "function"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isTypechecker(value: unknown): value is SandlotOptions["typechecker"] {
|
|
153
|
+
return (
|
|
154
|
+
typeof value === "object" &&
|
|
155
|
+
value !== null &&
|
|
156
|
+
"typecheck" in value &&
|
|
157
|
+
typeof (value as { typecheck: unknown }).typecheck === "function"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isTypesResolver(
|
|
162
|
+
value: unknown
|
|
163
|
+
): value is SandlotOptions["typesResolver"] {
|
|
164
|
+
return (
|
|
165
|
+
typeof value === "object" &&
|
|
166
|
+
value !== null &&
|
|
167
|
+
"resolveTypes" in value &&
|
|
168
|
+
typeof (value as { resolveTypes: unknown }).resolveTypes === "function"
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isExecutor(value: unknown): value is SandlotOptions["executor"] {
|
|
173
|
+
return (
|
|
174
|
+
typeof value === "object" &&
|
|
175
|
+
value !== null &&
|
|
176
|
+
"execute" in value &&
|
|
177
|
+
typeof (value as { execute: unknown }).execute === "function"
|
|
178
|
+
);
|
|
179
|
+
}
|