sandlot 0.1.1 → 0.1.2
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 +52 -155
- package/dist/build-emitter.d.ts +29 -0
- package/dist/build-emitter.d.ts.map +1 -0
- package/dist/bundler.d.ts +2 -2
- package/dist/bundler.d.ts.map +1 -1
- package/dist/fs.d.ts +18 -4
- package/dist/fs.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -69
- package/dist/internal.d.ts +5 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +79 -24
- package/dist/sandbox-manager.d.ts +0 -12
- package/dist/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox.d.ts +0 -8
- package/dist/sandbox.d.ts.map +1 -1
- package/package.json +2 -7
- package/src/build-emitter.ts +61 -0
- package/src/bundler.ts +68 -27
- package/src/fs.ts +19 -6
- package/src/index.ts +18 -1
- package/src/internal.ts +5 -2
- package/src/sandbox-manager.ts +1 -82
- package/src/sandbox.ts +1 -75
- package/src/ts-libs.ts +1 -1
- package/src/react.tsx +0 -331
package/dist/internal.js
CHANGED
|
@@ -1081,12 +1081,35 @@ function formatDiagnosticsForAgent(diagnostics) {
|
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
1083
|
// src/bundler.ts
|
|
1084
|
-
|
|
1084
|
+
var esbuild = null;
|
|
1085
|
+
async function getEsbuild() {
|
|
1086
|
+
if (esbuild)
|
|
1087
|
+
return esbuild;
|
|
1088
|
+
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
1089
|
+
const mod = await import(cdnUrl);
|
|
1090
|
+
esbuild = mod.default ?? mod;
|
|
1091
|
+
if (typeof esbuild?.initialize !== "function") {
|
|
1092
|
+
console.error("esbuild-wasm module structure:", mod);
|
|
1093
|
+
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
1094
|
+
}
|
|
1095
|
+
return esbuild;
|
|
1096
|
+
}
|
|
1097
|
+
var ESBUILD_VERSION = "0.27.2";
|
|
1085
1098
|
var initialized = false;
|
|
1086
1099
|
var initPromise = null;
|
|
1087
1100
|
function getWasmUrl() {
|
|
1088
|
-
|
|
1089
|
-
|
|
1101
|
+
return `https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
|
|
1102
|
+
}
|
|
1103
|
+
function checkCrossOriginIsolation() {
|
|
1104
|
+
if (typeof window === "undefined")
|
|
1105
|
+
return;
|
|
1106
|
+
if (!window.crossOriginIsolated) {
|
|
1107
|
+
console.warn(`[sandlot] Cross-origin isolation is not enabled. esbuild-wasm may have reduced performance or fail on some browsers.
|
|
1108
|
+
To enable, add these headers to your dev server:
|
|
1109
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
1110
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
1111
|
+
In Vite, add a plugin to configureServer. See sandlot README for details.`);
|
|
1112
|
+
}
|
|
1090
1113
|
}
|
|
1091
1114
|
async function initBundler() {
|
|
1092
1115
|
if (initialized)
|
|
@@ -1095,9 +1118,13 @@ async function initBundler() {
|
|
|
1095
1118
|
await initPromise;
|
|
1096
1119
|
return;
|
|
1097
1120
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1121
|
+
checkCrossOriginIsolation();
|
|
1122
|
+
initPromise = (async () => {
|
|
1123
|
+
const es = await getEsbuild();
|
|
1124
|
+
await es.initialize({
|
|
1125
|
+
wasmURL: getWasmUrl()
|
|
1126
|
+
});
|
|
1127
|
+
})();
|
|
1101
1128
|
await initPromise;
|
|
1102
1129
|
initialized = true;
|
|
1103
1130
|
}
|
|
@@ -1148,8 +1175,8 @@ function createVfsPlugin(options) {
|
|
|
1148
1175
|
} = options;
|
|
1149
1176
|
return {
|
|
1150
1177
|
name: "virtual-fs",
|
|
1151
|
-
setup(
|
|
1152
|
-
|
|
1178
|
+
setup(build) {
|
|
1179
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
1153
1180
|
if (args.kind === "entry-point") {
|
|
1154
1181
|
return { path: entryPoint, namespace: "vfs" };
|
|
1155
1182
|
}
|
|
@@ -1205,28 +1232,18 @@ function createVfsPlugin(options) {
|
|
|
1205
1232
|
}
|
|
1206
1233
|
return { errors: [{ text: `Cannot resolve: ${args.path} from ${args.resolveDir}` }] };
|
|
1207
1234
|
});
|
|
1208
|
-
|
|
1209
|
-
const contents = `
|
|
1210
|
-
export * from ${JSON.stringify(args.path)};
|
|
1211
|
-
// Re-export all named exports by importing from registry
|
|
1212
|
-
const __mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
1213
|
-
for (const __k__ in __mod__) {
|
|
1214
|
-
if (__k__ !== 'default') Object.defineProperty(exports, __k__, {
|
|
1215
|
-
enumerable: true,
|
|
1216
|
-
get: function() { return __mod__[__k__]; }
|
|
1217
|
-
});
|
|
1218
|
-
}`;
|
|
1219
|
-
const esmContents = `
|
|
1235
|
+
build.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
|
|
1236
|
+
const contents = `
|
|
1220
1237
|
const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
1221
1238
|
export default __sandlot_mod__.default ?? __sandlot_mod__;
|
|
1222
1239
|
${generateNamedExports(args.path)}
|
|
1223
1240
|
`;
|
|
1224
1241
|
return {
|
|
1225
|
-
contents:
|
|
1242
|
+
contents: contents.trim(),
|
|
1226
1243
|
loader: "js"
|
|
1227
1244
|
};
|
|
1228
1245
|
});
|
|
1229
|
-
|
|
1246
|
+
build.onLoad({ filter: /.*/, namespace: "vfs" }, async (args) => {
|
|
1230
1247
|
try {
|
|
1231
1248
|
const contents = await fs.readFile(args.path);
|
|
1232
1249
|
includedFiles.add(args.path);
|
|
@@ -1325,7 +1342,8 @@ async function bundle(options) {
|
|
|
1325
1342
|
includedFiles,
|
|
1326
1343
|
sharedModuleIds
|
|
1327
1344
|
});
|
|
1328
|
-
const
|
|
1345
|
+
const es = await getEsbuild();
|
|
1346
|
+
const result = await es.build({
|
|
1329
1347
|
entryPoints: [normalizedEntry],
|
|
1330
1348
|
bundle: true,
|
|
1331
1349
|
write: false,
|
|
@@ -1876,6 +1894,42 @@ function createDefaultCommands(deps) {
|
|
|
1876
1894
|
createListCommand(deps)
|
|
1877
1895
|
];
|
|
1878
1896
|
}
|
|
1897
|
+
// src/build-emitter.ts
|
|
1898
|
+
class BuildEmitter {
|
|
1899
|
+
listeners = new Set;
|
|
1900
|
+
lastResult = null;
|
|
1901
|
+
emit = async (result) => {
|
|
1902
|
+
this.lastResult = result;
|
|
1903
|
+
const promises = [];
|
|
1904
|
+
for (const listener of this.listeners) {
|
|
1905
|
+
const ret = listener(result);
|
|
1906
|
+
if (ret instanceof Promise) {
|
|
1907
|
+
promises.push(ret);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
await Promise.all(promises);
|
|
1911
|
+
};
|
|
1912
|
+
on(callback) {
|
|
1913
|
+
this.listeners.add(callback);
|
|
1914
|
+
return () => {
|
|
1915
|
+
this.listeners.delete(callback);
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
waitFor() {
|
|
1919
|
+
if (this.lastResult) {
|
|
1920
|
+
const result = this.lastResult;
|
|
1921
|
+
this.lastResult = null;
|
|
1922
|
+
return Promise.resolve(result);
|
|
1923
|
+
}
|
|
1924
|
+
return new Promise((resolve) => {
|
|
1925
|
+
const unsub = this.on((result) => {
|
|
1926
|
+
unsub();
|
|
1927
|
+
this.lastResult = null;
|
|
1928
|
+
resolve(result);
|
|
1929
|
+
});
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1879
1933
|
export {
|
|
1880
1934
|
revokeModuleUrl,
|
|
1881
1935
|
resolveToEsmUrl,
|
|
@@ -1893,5 +1947,6 @@ export {
|
|
|
1893
1947
|
createModuleUrl,
|
|
1894
1948
|
SharedModuleRegistry,
|
|
1895
1949
|
LibCache,
|
|
1896
|
-
InMemoryTypesCache
|
|
1950
|
+
InMemoryTypesCache,
|
|
1951
|
+
BuildEmitter
|
|
1897
1952
|
};
|
|
@@ -26,13 +26,6 @@ import { type IndexedDbFsOptions } from "./fs";
|
|
|
26
26
|
import { type BundleResult } from "./bundler";
|
|
27
27
|
import { type SharedResources, type SharedResourcesOptions } from "./shared-resources";
|
|
28
28
|
import type { Sandbox } from "./sandbox";
|
|
29
|
-
export type { BundleResult } from "./bundler";
|
|
30
|
-
export type { TypecheckResult } from "./typechecker";
|
|
31
|
-
export type { SharedResources, TypesCache } from "./shared-resources";
|
|
32
|
-
export type { PackageManifest, InstallResult } from "./packages";
|
|
33
|
-
export { installPackage, uninstallPackage, listPackages, getPackageManifest } from "./packages";
|
|
34
|
-
export { InMemoryTypesCache } from "./shared-resources";
|
|
35
|
-
export { loadModule, loadExport, loadDefault, getExportNames, hasExport, createModuleUrl, revokeModuleUrl, ModuleLoadError, ExportNotFoundError, } from "./loader";
|
|
36
29
|
/**
|
|
37
30
|
* Options for creating a sandbox via the manager
|
|
38
31
|
*/
|
|
@@ -216,11 +209,6 @@ export declare class SandboxManager {
|
|
|
216
209
|
* Get the shared resources (for advanced use cases)
|
|
217
210
|
*/
|
|
218
211
|
getResources(): SharedResources | null;
|
|
219
|
-
/**
|
|
220
|
-
* Get the shared lib files (for advanced use cases)
|
|
221
|
-
* @deprecated Use getResources().libFiles instead
|
|
222
|
-
*/
|
|
223
|
-
getLibFiles(): Map<string, string>;
|
|
224
212
|
}
|
|
225
213
|
/**
|
|
226
214
|
* Create a new SandboxManager instance.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox-manager.d.ts","sourceRoot":"","sources":["../src/sandbox-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAQ,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAe,KAAK,kBAAkB,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"sandbox-manager.d.ts","sourceRoot":"","sources":["../src/sandbox-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -2,14 +2,6 @@ import { Bash, defineCommand } from "just-bash/browser";
|
|
|
2
2
|
import { IndexedDbFs, type IndexedDbFsOptions } from "./fs";
|
|
3
3
|
import { type BundleResult } from "./bundler";
|
|
4
4
|
import { type SharedResources } from "./shared-resources";
|
|
5
|
-
export type { BundleResult } from "./bundler";
|
|
6
|
-
export type { TypecheckResult } from "./typechecker";
|
|
7
|
-
export type { SharedResources, TypesCache } from "./shared-resources";
|
|
8
|
-
export type { PackageManifest, InstallResult } from "./packages";
|
|
9
|
-
export type { RunContext, RunOptions, RunResult } from "./commands";
|
|
10
|
-
export { installPackage, uninstallPackage, listPackages, getPackageManifest } from "./packages";
|
|
11
|
-
export { InMemoryTypesCache } from "./shared-resources";
|
|
12
|
-
export { loadModule, loadExport, loadDefault, getExportNames, hasExport, createModuleUrl, revokeModuleUrl, ModuleLoadError, ExportNotFoundError, } from "./loader";
|
|
13
5
|
/**
|
|
14
6
|
* Options for creating a sandbox environment
|
|
15
7
|
*/
|
package/dist/sandbox.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG/E
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE3D,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG/E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAE/B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzD;;OAEG;IACH,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,EAAE,CAAC;IAEpD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,EAAE,EAAE,WAAW,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IAEX;;;;;;;;;;OAUG;IACH,OAAO,IAAI,OAAO,CAAC;IAEnB;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzB;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IAEd;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC;CAC/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAgElF;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,GACL,OAAO,CAAC,OAAO,CAAC,CA8DlB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandlot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Browser-based TypeScript sandbox with esbuild bundling and type checking",
|
|
5
5
|
"author": "blindmansion",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,11 +22,6 @@
|
|
|
22
22
|
"import": "./dist/index.js",
|
|
23
23
|
"default": "./dist/index.js"
|
|
24
24
|
},
|
|
25
|
-
"./react": {
|
|
26
|
-
"types": "./dist/react.d.ts",
|
|
27
|
-
"import": "./dist/react.js",
|
|
28
|
-
"default": "./dist/react.js"
|
|
29
|
-
},
|
|
30
25
|
"./internal": {
|
|
31
26
|
"types": "./dist/internal.d.ts",
|
|
32
27
|
"import": "./dist/internal.js",
|
|
@@ -39,7 +34,7 @@
|
|
|
39
34
|
],
|
|
40
35
|
"scripts": {
|
|
41
36
|
"build": "bun run build:js && bun run build:types",
|
|
42
|
-
"build:js": "bun build ./src/index.ts ./src/
|
|
37
|
+
"build:js": "bun build ./src/index.ts ./src/internal.ts --outdir ./dist --format esm --packages external",
|
|
43
38
|
"build:types": "tsc --emitDeclarationOnly",
|
|
44
39
|
"prepublishOnly": "bun run build"
|
|
45
40
|
},
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build event emitter for sandbox environments.
|
|
3
|
+
*
|
|
4
|
+
* Simple typed event emitter for build results.
|
|
5
|
+
* Caches the last result so waitFor() can be called after build completes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BundleResult } from "./bundler";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Simple typed event emitter for build results.
|
|
12
|
+
* Caches the last result so waitFor() can be called after build completes.
|
|
13
|
+
*/
|
|
14
|
+
export class BuildEmitter {
|
|
15
|
+
private listeners = new Set<(result: BundleResult) => void | Promise<void>>();
|
|
16
|
+
private lastResult: BundleResult | null = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Emit a build result to all listeners and cache it
|
|
20
|
+
*/
|
|
21
|
+
emit = async (result: BundleResult): Promise<void> => {
|
|
22
|
+
this.lastResult = result;
|
|
23
|
+
const promises: Promise<void>[] = [];
|
|
24
|
+
for (const listener of this.listeners) {
|
|
25
|
+
const ret = listener(result);
|
|
26
|
+
if (ret instanceof Promise) {
|
|
27
|
+
promises.push(ret);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await Promise.all(promises);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to build events. Returns an unsubscribe function.
|
|
35
|
+
*/
|
|
36
|
+
on(callback: (result: BundleResult) => void | Promise<void>): () => void {
|
|
37
|
+
this.listeners.add(callback);
|
|
38
|
+
return () => {
|
|
39
|
+
this.listeners.delete(callback);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the last build result, or wait for the next one if none exists.
|
|
45
|
+
* Clears the cached result after returning, so subsequent calls wait for new builds.
|
|
46
|
+
*/
|
|
47
|
+
waitFor(): Promise<BundleResult> {
|
|
48
|
+
if (this.lastResult) {
|
|
49
|
+
const result = this.lastResult;
|
|
50
|
+
this.lastResult = null;
|
|
51
|
+
return Promise.resolve(result);
|
|
52
|
+
}
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const unsub = this.on((result) => {
|
|
55
|
+
unsub();
|
|
56
|
+
this.lastResult = null;
|
|
57
|
+
resolve(result);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/bundler.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import type { IFileSystem } from "just-bash/browser";
|
|
2
|
-
import * as
|
|
2
|
+
import type * as EsbuildTypes from "esbuild-wasm";
|
|
3
3
|
import { getPackageManifest, resolveToEsmUrl } from "./packages";
|
|
4
4
|
import { getSharedModuleRuntimeCode } from "./shared-modules";
|
|
5
5
|
|
|
6
|
+
// Lazily loaded esbuild module - loaded from CDN to avoid bundler issues
|
|
7
|
+
let esbuild: typeof EsbuildTypes | null = null;
|
|
8
|
+
|
|
9
|
+
async function getEsbuild(): Promise<typeof EsbuildTypes> {
|
|
10
|
+
if (esbuild) return esbuild;
|
|
11
|
+
|
|
12
|
+
// Load esbuild-wasm from esm.sh CDN to avoid bundler transformation issues
|
|
13
|
+
// esm.sh provides proper ESM wrappers for npm packages
|
|
14
|
+
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
const mod: any = await import(/* @vite-ignore */ cdnUrl);
|
|
18
|
+
|
|
19
|
+
// esm.sh typically provides both default and named exports
|
|
20
|
+
esbuild = mod.default ?? mod;
|
|
21
|
+
|
|
22
|
+
// Verify we have the initialize function
|
|
23
|
+
if (typeof esbuild?.initialize !== 'function') {
|
|
24
|
+
console.error('esbuild-wasm module structure:', mod);
|
|
25
|
+
throw new Error('Failed to load esbuild-wasm: initialize function not found');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return esbuild;
|
|
29
|
+
}
|
|
30
|
+
|
|
6
31
|
/**
|
|
7
32
|
* How to handle npm package imports (bare imports like "react").
|
|
8
33
|
*
|
|
@@ -96,7 +121,7 @@ export interface BundleResult {
|
|
|
96
121
|
/**
|
|
97
122
|
* Any warnings from esbuild
|
|
98
123
|
*/
|
|
99
|
-
warnings:
|
|
124
|
+
warnings: EsbuildTypes.Message[];
|
|
100
125
|
|
|
101
126
|
/**
|
|
102
127
|
* List of files that were included in the bundle
|
|
@@ -104,6 +129,11 @@ export interface BundleResult {
|
|
|
104
129
|
includedFiles: string[];
|
|
105
130
|
}
|
|
106
131
|
|
|
132
|
+
/**
|
|
133
|
+
* esbuild-wasm version - MUST match the version in package.json dependencies
|
|
134
|
+
*/
|
|
135
|
+
const ESBUILD_VERSION = "0.27.2";
|
|
136
|
+
|
|
107
137
|
// Track initialization state
|
|
108
138
|
let initialized = false;
|
|
109
139
|
let initPromise: Promise<void> | null = null;
|
|
@@ -112,9 +142,27 @@ let initPromise: Promise<void> | null = null;
|
|
|
112
142
|
* Get the esbuild-wasm binary URL based on the installed version
|
|
113
143
|
*/
|
|
114
144
|
function getWasmUrl(): string {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
return `https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if the browser environment supports cross-origin isolation.
|
|
150
|
+
* This is needed for SharedArrayBuffer which esbuild-wasm may use.
|
|
151
|
+
*/
|
|
152
|
+
function checkCrossOriginIsolation(): void {
|
|
153
|
+
if (typeof window === "undefined") return; // Not in browser
|
|
154
|
+
|
|
155
|
+
// crossOriginIsolated is true when COOP/COEP headers are set correctly
|
|
156
|
+
if (!window.crossOriginIsolated) {
|
|
157
|
+
console.warn(
|
|
158
|
+
"[sandlot] Cross-origin isolation is not enabled. " +
|
|
159
|
+
"esbuild-wasm may have reduced performance or fail on some browsers.\n" +
|
|
160
|
+
"To enable, add these headers to your dev server:\n" +
|
|
161
|
+
" Cross-Origin-Embedder-Policy: require-corp\n" +
|
|
162
|
+
" Cross-Origin-Opener-Policy: same-origin\n" +
|
|
163
|
+
"In Vite, add a plugin to configureServer. See sandlot README for details."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
118
166
|
}
|
|
119
167
|
|
|
120
168
|
/**
|
|
@@ -129,9 +177,14 @@ export async function initBundler(): Promise<void> {
|
|
|
129
177
|
return;
|
|
130
178
|
}
|
|
131
179
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
180
|
+
checkCrossOriginIsolation();
|
|
181
|
+
|
|
182
|
+
initPromise = (async () => {
|
|
183
|
+
const es = await getEsbuild();
|
|
184
|
+
await es.initialize({
|
|
185
|
+
wasmURL: getWasmUrl(),
|
|
186
|
+
});
|
|
187
|
+
})();
|
|
135
188
|
|
|
136
189
|
await initPromise;
|
|
137
190
|
initialized = true;
|
|
@@ -147,7 +200,7 @@ function isBareImport(path: string): boolean {
|
|
|
147
200
|
/**
|
|
148
201
|
* Get the appropriate loader based on file extension
|
|
149
202
|
*/
|
|
150
|
-
function getLoader(path: string):
|
|
203
|
+
function getLoader(path: string): EsbuildTypes.Loader {
|
|
151
204
|
const ext = path.split(".").pop()?.toLowerCase();
|
|
152
205
|
switch (ext) {
|
|
153
206
|
case "ts":
|
|
@@ -207,7 +260,7 @@ function matchesSharedModule(importPath: string, sharedModuleIds: Set<string>):
|
|
|
207
260
|
/**
|
|
208
261
|
* Create an esbuild plugin that reads from a virtual filesystem
|
|
209
262
|
*/
|
|
210
|
-
function createVfsPlugin(options: VfsPluginOptions):
|
|
263
|
+
function createVfsPlugin(options: VfsPluginOptions): EsbuildTypes.Plugin {
|
|
211
264
|
const {
|
|
212
265
|
fs,
|
|
213
266
|
entryPoint,
|
|
@@ -303,27 +356,14 @@ function createVfsPlugin(options: VfsPluginOptions): esbuild.Plugin {
|
|
|
303
356
|
|
|
304
357
|
// Load shared modules from the registry
|
|
305
358
|
build.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
|
|
306
|
-
// Generate code that
|
|
307
|
-
const contents = `
|
|
308
|
-
export * from ${JSON.stringify(args.path)};
|
|
309
|
-
// Re-export all named exports by importing from registry
|
|
310
|
-
const __mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
311
|
-
for (const __k__ in __mod__) {
|
|
312
|
-
if (__k__ !== 'default') Object.defineProperty(exports, __k__, {
|
|
313
|
-
enumerable: true,
|
|
314
|
-
get: function() { return __mod__[__k__]; }
|
|
315
|
-
});
|
|
316
|
-
}`;
|
|
317
|
-
|
|
318
|
-
// For ESM format, we need a different approach
|
|
319
|
-
// Generate a simple module that re-exports from registry
|
|
320
|
-
const esmContents = `
|
|
359
|
+
// Generate ESM code that re-exports from the shared module registry
|
|
360
|
+
const contents = `
|
|
321
361
|
const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
322
362
|
export default __sandlot_mod__.default ?? __sandlot_mod__;
|
|
323
363
|
${generateNamedExports(args.path)}
|
|
324
364
|
`;
|
|
325
365
|
return {
|
|
326
|
-
contents:
|
|
366
|
+
contents: contents.trim(),
|
|
327
367
|
loader: 'js'
|
|
328
368
|
};
|
|
329
369
|
});
|
|
@@ -445,7 +485,8 @@ export async function bundle(options: BundleOptions): Promise<BundleResult> {
|
|
|
445
485
|
sharedModuleIds,
|
|
446
486
|
});
|
|
447
487
|
|
|
448
|
-
const
|
|
488
|
+
const es = await getEsbuild();
|
|
489
|
+
const result = await es.build({
|
|
449
490
|
entryPoints: [normalizedEntry],
|
|
450
491
|
bundle: true,
|
|
451
492
|
write: false,
|
package/src/fs.ts
CHANGED
|
@@ -23,9 +23,12 @@ interface ReadFileOptions {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Options for writing files
|
|
26
|
+
* Options for writing files.
|
|
27
|
+
* Note: In this browser-based filesystem, content is stored as-is.
|
|
28
|
+
* The encoding option is accepted for API compatibility but not used.
|
|
27
29
|
*/
|
|
28
30
|
interface WriteFileOptions {
|
|
31
|
+
/** Accepted for API compatibility but not used - content is stored as-is */
|
|
29
32
|
encoding?: BufferEncoding;
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -276,7 +279,7 @@ export class IndexedDbFs implements IFileSystem {
|
|
|
276
279
|
async writeFile(
|
|
277
280
|
path: string,
|
|
278
281
|
content: FileContent,
|
|
279
|
-
|
|
282
|
+
_options?: WriteFileOptions | BufferEncoding
|
|
280
283
|
): Promise<void> {
|
|
281
284
|
const normalizedPath = this.normalizePath(path);
|
|
282
285
|
this.checkSizeLimit(content);
|
|
@@ -926,10 +929,20 @@ export class IndexedDbFs implements IFileSystem {
|
|
|
926
929
|
}
|
|
927
930
|
|
|
928
931
|
/**
|
|
929
|
-
*
|
|
932
|
+
* Synchronous factory function for creating an in-memory filesystem.
|
|
933
|
+
*
|
|
934
|
+
* Note: For IndexedDB-backed persistence, use `IndexedDbFs.create()` instead.
|
|
935
|
+
* This function exists for compatibility with sync factory patterns.
|
|
936
|
+
*
|
|
937
|
+
* @param initialFiles - Optional initial files to populate the filesystem
|
|
938
|
+
* @returns An in-memory filesystem (no persistence)
|
|
930
939
|
*/
|
|
931
|
-
export function
|
|
932
|
-
// For sync factory usage, return in-memory version
|
|
933
|
-
// Use IndexedDbFs.create() for async with persistence
|
|
940
|
+
export function createInMemoryFs(initialFiles?: InitialFiles): IFileSystem {
|
|
934
941
|
return IndexedDbFs.createInMemory({ initialFiles });
|
|
935
942
|
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* @deprecated Use `createInMemoryFs` for in-memory filesystems or
|
|
946
|
+
* `IndexedDbFs.create()` for IndexedDB-backed persistence.
|
|
947
|
+
*/
|
|
948
|
+
export const createIndexedDbFs = createInMemoryFs;
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Browser polyfills - inject before anything else loads
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
// Some dependencies (like just-bash) reference Node.js globals.
|
|
6
|
+
// Provide shims so they work in the browser without user configuration.
|
|
7
|
+
if (typeof window !== "undefined" && typeof globalThis.process === "undefined") {
|
|
8
|
+
(globalThis as Record<string, unknown>).process = {
|
|
9
|
+
env: {},
|
|
10
|
+
platform: "browser",
|
|
11
|
+
version: "v20.0.0",
|
|
12
|
+
browser: true,
|
|
13
|
+
cwd: () => "/",
|
|
14
|
+
nextTick: (fn: () => void) => setTimeout(fn, 0),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
// =============================================================================
|
|
2
19
|
// CORE API - Most users only need these
|
|
3
20
|
// =============================================================================
|
|
@@ -115,7 +132,7 @@ export {
|
|
|
115
132
|
|
|
116
133
|
export {
|
|
117
134
|
IndexedDbFs,
|
|
118
|
-
|
|
135
|
+
createInMemoryFs,
|
|
119
136
|
type IndexedDbFsOptions,
|
|
120
137
|
} from "./fs";
|
|
121
138
|
|
package/src/internal.ts
CHANGED
|
@@ -112,5 +112,8 @@ export { formatEsbuildMessages } from "./commands";
|
|
|
112
112
|
// BuildEmitter (for custom build event handling)
|
|
113
113
|
// =============================================================================
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Build event emitter for sandbox environments.
|
|
117
|
+
* Use this for custom build event handling in advanced use cases.
|
|
118
|
+
*/
|
|
119
|
+
export { BuildEmitter } from "./build-emitter";
|
package/src/sandbox-manager.ts
CHANGED
|
@@ -32,82 +32,9 @@ import {
|
|
|
32
32
|
type SharedResourcesOptions,
|
|
33
33
|
} from "./shared-resources";
|
|
34
34
|
import { createDefaultCommands, type CommandDeps } from "./commands";
|
|
35
|
+
import { BuildEmitter } from "./build-emitter";
|
|
35
36
|
import type { Sandbox } from "./sandbox";
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
-
* Simple typed event emitter for build results.
|
|
39
|
-
* Caches the last result so waitFor() can be called after build completes.
|
|
40
|
-
*/
|
|
41
|
-
class BuildEmitter {
|
|
42
|
-
private listeners = new Set<(result: BundleResult) => void | Promise<void>>();
|
|
43
|
-
private lastResult: BundleResult | null = null;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Emit a build result to all listeners and cache it
|
|
47
|
-
*/
|
|
48
|
-
emit = async (result: BundleResult): Promise<void> => {
|
|
49
|
-
this.lastResult = result;
|
|
50
|
-
const promises: Promise<void>[] = [];
|
|
51
|
-
for (const listener of this.listeners) {
|
|
52
|
-
const ret = listener(result);
|
|
53
|
-
if (ret instanceof Promise) {
|
|
54
|
-
promises.push(ret);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
await Promise.all(promises);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Subscribe to build events. Returns an unsubscribe function.
|
|
62
|
-
*/
|
|
63
|
-
on(callback: (result: BundleResult) => void | Promise<void>): () => void {
|
|
64
|
-
this.listeners.add(callback);
|
|
65
|
-
return () => {
|
|
66
|
-
this.listeners.delete(callback);
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get the last build result, or wait for the next one if none exists.
|
|
72
|
-
* Clears the cached result after returning, so subsequent calls wait for new builds.
|
|
73
|
-
*/
|
|
74
|
-
waitFor(): Promise<BundleResult> {
|
|
75
|
-
if (this.lastResult) {
|
|
76
|
-
const result = this.lastResult;
|
|
77
|
-
this.lastResult = null;
|
|
78
|
-
return Promise.resolve(result);
|
|
79
|
-
}
|
|
80
|
-
return new Promise((resolve) => {
|
|
81
|
-
const unsub = this.on((result) => {
|
|
82
|
-
unsub();
|
|
83
|
-
this.lastResult = null;
|
|
84
|
-
resolve(result);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Re-export for convenience
|
|
91
|
-
export type { BundleResult } from "./bundler";
|
|
92
|
-
export type { TypecheckResult } from "./typechecker";
|
|
93
|
-
export type { SharedResources, TypesCache } from "./shared-resources";
|
|
94
|
-
export type { PackageManifest, InstallResult } from "./packages";
|
|
95
|
-
export { installPackage, uninstallPackage, listPackages, getPackageManifest } from "./packages";
|
|
96
|
-
export { InMemoryTypesCache } from "./shared-resources";
|
|
97
|
-
|
|
98
|
-
// Loader utilities
|
|
99
|
-
export {
|
|
100
|
-
loadModule,
|
|
101
|
-
loadExport,
|
|
102
|
-
loadDefault,
|
|
103
|
-
getExportNames,
|
|
104
|
-
hasExport,
|
|
105
|
-
createModuleUrl,
|
|
106
|
-
revokeModuleUrl,
|
|
107
|
-
ModuleLoadError,
|
|
108
|
-
ExportNotFoundError,
|
|
109
|
-
} from "./loader";
|
|
110
|
-
|
|
111
38
|
/**
|
|
112
39
|
* Options for creating a sandbox via the manager
|
|
113
40
|
*/
|
|
@@ -447,14 +374,6 @@ export class SandboxManager {
|
|
|
447
374
|
getResources(): SharedResources | null {
|
|
448
375
|
return this.resources;
|
|
449
376
|
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Get the shared lib files (for advanced use cases)
|
|
453
|
-
* @deprecated Use getResources().libFiles instead
|
|
454
|
-
*/
|
|
455
|
-
getLibFiles(): Map<string, string> {
|
|
456
|
-
return this.resources?.libFiles ?? new Map();
|
|
457
|
-
}
|
|
458
377
|
}
|
|
459
378
|
|
|
460
379
|
/**
|