sandlot 0.1.4 → 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 +1399 -2010
- 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 +16 -6
- 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 -152
- 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 -1942
- 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 -85
- 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 -575
- 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 -398
- package/src/shared-modules.ts +0 -280
- package/src/shared-resources.ts +0 -166
- package/src/ts-libs.ts +0 -218
- package/src/typechecker.ts +0 -635
package/src/bundler.ts
DELETED
|
@@ -1,575 +0,0 @@
|
|
|
1
|
-
import type { IFileSystem } from "just-bash/browser";
|
|
2
|
-
import type * as EsbuildTypes from "esbuild-wasm";
|
|
3
|
-
import { getPackageManifest, resolveToEsmUrl } from "./packages";
|
|
4
|
-
import { getSharedModuleRuntimeCode, getSharedModuleExports } from "./shared-modules";
|
|
5
|
-
|
|
6
|
-
// Lazily loaded esbuild module
|
|
7
|
-
let esbuild: typeof EsbuildTypes | null = null;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Detect if we're running in a server environment (Node.js, Bun, Deno)
|
|
11
|
-
* vs a browser environment.
|
|
12
|
-
*/
|
|
13
|
-
function isServerEnvironment(): boolean {
|
|
14
|
-
// Check for Bun
|
|
15
|
-
if (typeof globalThis !== "undefined" && "Bun" in globalThis) {
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
// Check for Deno
|
|
19
|
-
if (typeof globalThis !== "undefined" && "Deno" in globalThis) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
// Check for Node.js (process.versions.node exists)
|
|
23
|
-
if (typeof process !== "undefined" && process.versions?.node) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function getEsbuild(): Promise<typeof EsbuildTypes> {
|
|
30
|
-
if (esbuild) return esbuild;
|
|
31
|
-
|
|
32
|
-
if (isServerEnvironment()) {
|
|
33
|
-
// In server environments, use native esbuild for better performance
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
-
const mod: any = await import("esbuild");
|
|
36
|
-
esbuild = mod.default ?? mod;
|
|
37
|
-
} else {
|
|
38
|
-
// In browser, load esbuild-wasm from esm.sh CDN
|
|
39
|
-
const cdnUrl = `https://esm.sh/esbuild-wasm@${ESBUILD_VERSION}`;
|
|
40
|
-
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
-
const mod: any = await import(/* @vite-ignore */ cdnUrl);
|
|
43
|
-
|
|
44
|
-
// esm.sh typically provides both default and named exports
|
|
45
|
-
esbuild = mod.default ?? mod;
|
|
46
|
-
|
|
47
|
-
// Verify we have the initialize function (only needed for wasm version)
|
|
48
|
-
if (typeof esbuild?.initialize !== "function") {
|
|
49
|
-
console.error("esbuild-wasm module structure:", mod);
|
|
50
|
-
throw new Error("Failed to load esbuild-wasm: initialize function not found");
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return esbuild!;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* How to handle npm package imports (bare imports like "react").
|
|
59
|
-
*
|
|
60
|
-
* - "cdn" (default): Rewrite to esm.sh CDN URLs using installed package versions.
|
|
61
|
-
* Requires packages to be installed via the `install` command.
|
|
62
|
-
* - "external": Mark as external, don't rewrite. The consumer must handle
|
|
63
|
-
* module resolution (useful for SSR or custom bundling).
|
|
64
|
-
* - "bundle": Attempt to bundle from node_modules. Rarely useful in browser
|
|
65
|
-
* since node_modules typically doesn't exist in the virtual filesystem.
|
|
66
|
-
*/
|
|
67
|
-
export type NpmImportsMode = "cdn" | "external" | "bundle";
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Options for bundling
|
|
71
|
-
*/
|
|
72
|
-
export interface BundleOptions {
|
|
73
|
-
/**
|
|
74
|
-
* The virtual filesystem to read source files from
|
|
75
|
-
*/
|
|
76
|
-
fs: IFileSystem;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Entry point path (absolute path in the virtual filesystem)
|
|
80
|
-
*/
|
|
81
|
-
entryPoint: string;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Module names to mark as external (won't be bundled).
|
|
85
|
-
* These are in addition to bare imports when using npmImports: "external".
|
|
86
|
-
*/
|
|
87
|
-
external?: string[];
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* How to handle npm package imports (bare imports like "react").
|
|
91
|
-
*
|
|
92
|
-
* - "cdn" (default): Rewrite to esm.sh CDN URLs using installed package versions
|
|
93
|
-
* - "external": Mark as external, don't rewrite (consumer must handle)
|
|
94
|
-
* - "bundle": Attempt to bundle from node_modules (rarely useful in browser)
|
|
95
|
-
*/
|
|
96
|
-
npmImports?: NpmImportsMode;
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Module IDs that should be resolved from the host's SharedModuleRegistry
|
|
100
|
-
* instead of esm.sh CDN. The host must have registered these modules.
|
|
101
|
-
*
|
|
102
|
-
* Example: ['react', 'react-dom/client']
|
|
103
|
-
*
|
|
104
|
-
* When specified, imports of these modules will use the host's instances,
|
|
105
|
-
* allowing dynamic components to share React context, hooks, etc.
|
|
106
|
-
*/
|
|
107
|
-
sharedModules?: string[];
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Output format: 'esm' (default), 'iife', or 'cjs'
|
|
111
|
-
*/
|
|
112
|
-
format?: "esm" | "iife" | "cjs";
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Enable minification
|
|
116
|
-
* Default: false
|
|
117
|
-
*/
|
|
118
|
-
minify?: boolean;
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Enable source maps (inline)
|
|
122
|
-
* Default: false
|
|
123
|
-
*/
|
|
124
|
-
sourcemap?: boolean;
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Global name for IIFE format
|
|
128
|
-
*/
|
|
129
|
-
globalName?: string;
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Target environment(s)
|
|
133
|
-
* Default: ['es2020']
|
|
134
|
-
*/
|
|
135
|
-
target?: string[];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Result of bundling
|
|
140
|
-
*/
|
|
141
|
-
export interface BundleResult {
|
|
142
|
-
/**
|
|
143
|
-
* The bundled JavaScript code
|
|
144
|
-
*/
|
|
145
|
-
code: string;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Any warnings from esbuild
|
|
149
|
-
*/
|
|
150
|
-
warnings: EsbuildTypes.Message[];
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* List of files that were included in the bundle
|
|
154
|
-
*/
|
|
155
|
-
includedFiles: string[];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* esbuild-wasm version - MUST match the version in package.json dependencies
|
|
160
|
-
*/
|
|
161
|
-
const ESBUILD_VERSION = "0.27.2";
|
|
162
|
-
|
|
163
|
-
// Track initialization state
|
|
164
|
-
let initialized = false;
|
|
165
|
-
let initPromise: Promise<void> | null = null;
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Get the esbuild-wasm binary URL based on the installed version
|
|
169
|
-
*/
|
|
170
|
-
function getWasmUrl(): string {
|
|
171
|
-
return `https://unpkg.com/esbuild-wasm@${ESBUILD_VERSION}/esbuild.wasm`;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Check if the browser environment supports cross-origin isolation.
|
|
176
|
-
* This is needed for SharedArrayBuffer which esbuild-wasm may use.
|
|
177
|
-
*/
|
|
178
|
-
function checkCrossOriginIsolation(): void {
|
|
179
|
-
if (typeof window === "undefined") return; // Not in browser
|
|
180
|
-
|
|
181
|
-
// crossOriginIsolated is true when COOP/COEP headers are set correctly
|
|
182
|
-
if (!window.crossOriginIsolated) {
|
|
183
|
-
console.warn(
|
|
184
|
-
"[sandlot] Cross-origin isolation is not enabled. " +
|
|
185
|
-
"esbuild-wasm may have reduced performance or fail on some browsers.\n" +
|
|
186
|
-
"To enable, add these headers to your dev server:\n" +
|
|
187
|
-
" Cross-Origin-Embedder-Policy: require-corp\n" +
|
|
188
|
-
" Cross-Origin-Opener-Policy: same-origin\n" +
|
|
189
|
-
"In Vite, add a plugin to configureServer. See sandlot README for details."
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Initialize esbuild. Called automatically on first bundle.
|
|
196
|
-
* Can be called explicitly to pre-warm.
|
|
197
|
-
*
|
|
198
|
-
* In browser environments, this loads and initializes esbuild-wasm.
|
|
199
|
-
* In server environments (Node.js, Bun, Deno), this loads native esbuild
|
|
200
|
-
* which doesn't require WASM initialization.
|
|
201
|
-
*/
|
|
202
|
-
export async function initBundler(): Promise<void> {
|
|
203
|
-
if (initialized) return;
|
|
204
|
-
|
|
205
|
-
if (initPromise) {
|
|
206
|
-
await initPromise;
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
initPromise = (async () => {
|
|
211
|
-
const es = await getEsbuild();
|
|
212
|
-
|
|
213
|
-
// Native esbuild doesn't need initialization, only esbuild-wasm does
|
|
214
|
-
if (!isServerEnvironment() && typeof es.initialize === "function") {
|
|
215
|
-
checkCrossOriginIsolation();
|
|
216
|
-
await es.initialize({
|
|
217
|
-
wasmURL: getWasmUrl(),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
})();
|
|
221
|
-
|
|
222
|
-
await initPromise;
|
|
223
|
-
initialized = true;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Check if a path is a bare import (not relative or absolute)
|
|
228
|
-
*/
|
|
229
|
-
function isBareImport(path: string): boolean {
|
|
230
|
-
return !path.startsWith(".") && !path.startsWith("/");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get the appropriate loader based on file extension
|
|
235
|
-
*/
|
|
236
|
-
function getLoader(path: string): EsbuildTypes.Loader {
|
|
237
|
-
const ext = path.split(".").pop()?.toLowerCase();
|
|
238
|
-
switch (ext) {
|
|
239
|
-
case "ts":
|
|
240
|
-
return "ts";
|
|
241
|
-
case "tsx":
|
|
242
|
-
return "tsx";
|
|
243
|
-
case "jsx":
|
|
244
|
-
return "jsx";
|
|
245
|
-
case "js":
|
|
246
|
-
case "mjs":
|
|
247
|
-
return "js";
|
|
248
|
-
case "json":
|
|
249
|
-
return "json";
|
|
250
|
-
case "css":
|
|
251
|
-
return "css";
|
|
252
|
-
case "txt":
|
|
253
|
-
return "text";
|
|
254
|
-
default:
|
|
255
|
-
return "js";
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Options for the VFS plugin
|
|
261
|
-
*/
|
|
262
|
-
interface VfsPluginOptions {
|
|
263
|
-
fs: IFileSystem;
|
|
264
|
-
entryPoint: string;
|
|
265
|
-
npmImports: NpmImportsMode;
|
|
266
|
-
installedPackages: Record<string, string>;
|
|
267
|
-
includedFiles: Set<string>;
|
|
268
|
-
/** Module IDs to resolve from SharedModuleRegistry */
|
|
269
|
-
sharedModuleIds: Set<string>;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Check if an import path matches a shared module ID.
|
|
274
|
-
* Handles both exact matches and subpath imports (e.g., 'react-dom/client').
|
|
275
|
-
*/
|
|
276
|
-
function matchesSharedModule(importPath: string, sharedModuleIds: Set<string>): string | null {
|
|
277
|
-
// Check exact match first
|
|
278
|
-
if (sharedModuleIds.has(importPath)) {
|
|
279
|
-
return importPath;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Check if any shared module is a prefix (for subpath imports)
|
|
283
|
-
// e.g., if 'react-dom' is registered, 'react-dom/client' should match
|
|
284
|
-
for (const moduleId of sharedModuleIds) {
|
|
285
|
-
if (importPath === moduleId || importPath.startsWith(moduleId + '/')) {
|
|
286
|
-
return importPath;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Create an esbuild plugin that reads from a virtual filesystem
|
|
295
|
-
*/
|
|
296
|
-
function createVfsPlugin(options: VfsPluginOptions): EsbuildTypes.Plugin {
|
|
297
|
-
const {
|
|
298
|
-
fs,
|
|
299
|
-
entryPoint,
|
|
300
|
-
npmImports,
|
|
301
|
-
installedPackages,
|
|
302
|
-
includedFiles,
|
|
303
|
-
sharedModuleIds,
|
|
304
|
-
} = options;
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
name: "virtual-fs",
|
|
308
|
-
setup(build) {
|
|
309
|
-
// Resolve all imports
|
|
310
|
-
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
311
|
-
// Handle the virtual entry point
|
|
312
|
-
if (args.kind === "entry-point") {
|
|
313
|
-
return { path: entryPoint, namespace: "vfs" };
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Handle bare imports
|
|
317
|
-
if (isBareImport(args.path)) {
|
|
318
|
-
// Check if this module should use the shared registry
|
|
319
|
-
const sharedMatch = matchesSharedModule(args.path, sharedModuleIds);
|
|
320
|
-
if (sharedMatch) {
|
|
321
|
-
return {
|
|
322
|
-
path: sharedMatch,
|
|
323
|
-
namespace: "sandlot-shared"
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Handle based on npmImports mode
|
|
328
|
-
switch (npmImports) {
|
|
329
|
-
case "cdn": {
|
|
330
|
-
// Try to rewrite to esm.sh URL if package is installed
|
|
331
|
-
const esmUrl = resolveToEsmUrl(args.path, installedPackages);
|
|
332
|
-
if (esmUrl) {
|
|
333
|
-
return { path: esmUrl, external: true };
|
|
334
|
-
}
|
|
335
|
-
// Fall back to external if not installed
|
|
336
|
-
return { path: args.path, external: true };
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
case "external":
|
|
340
|
-
// Mark as external, don't rewrite
|
|
341
|
-
return { path: args.path, external: true };
|
|
342
|
-
|
|
343
|
-
case "bundle": {
|
|
344
|
-
// Try to resolve from VFS node_modules
|
|
345
|
-
const resolved = fs.resolvePath(args.resolveDir, `node_modules/${args.path}`);
|
|
346
|
-
const exists = await fs.exists(resolved);
|
|
347
|
-
if (exists) {
|
|
348
|
-
return { path: resolved, namespace: "vfs" };
|
|
349
|
-
}
|
|
350
|
-
// Fall back to external if not found
|
|
351
|
-
return { path: args.path, external: true };
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Resolve relative/absolute paths
|
|
357
|
-
const resolved = fs.resolvePath(args.resolveDir, args.path);
|
|
358
|
-
|
|
359
|
-
// Try with extensions if no extension provided
|
|
360
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".json"];
|
|
361
|
-
const hasExtension = extensions.some((ext) => resolved.endsWith(ext));
|
|
362
|
-
|
|
363
|
-
if (hasExtension) {
|
|
364
|
-
const exists = await fs.exists(resolved);
|
|
365
|
-
if (exists) {
|
|
366
|
-
return { path: resolved, namespace: "vfs" };
|
|
367
|
-
}
|
|
368
|
-
return { errors: [{ text: `File not found: ${resolved}` }] };
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Try adding extensions
|
|
372
|
-
for (const ext of extensions) {
|
|
373
|
-
const withExt = resolved + ext;
|
|
374
|
-
if (await fs.exists(withExt)) {
|
|
375
|
-
return { path: withExt, namespace: "vfs" };
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Try index files
|
|
380
|
-
for (const ext of extensions) {
|
|
381
|
-
const indexPath = `${resolved}/index${ext}`;
|
|
382
|
-
if (await fs.exists(indexPath)) {
|
|
383
|
-
return { path: indexPath, namespace: "vfs" };
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return { errors: [{ text: `Cannot resolve: ${args.path} from ${args.resolveDir}` }] };
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
// Load shared modules from the registry
|
|
391
|
-
build.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
|
|
392
|
-
// Generate ESM code that re-exports from the shared module registry
|
|
393
|
-
const contents = `
|
|
394
|
-
const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
395
|
-
export default __sandlot_mod__.default ?? __sandlot_mod__;
|
|
396
|
-
${generateNamedExports(args.path)}
|
|
397
|
-
`;
|
|
398
|
-
return {
|
|
399
|
-
contents: contents.trim(),
|
|
400
|
-
loader: 'js'
|
|
401
|
-
};
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// Load files from VFS
|
|
405
|
-
build.onLoad({ filter: /.*/, namespace: "vfs" }, async (args) => {
|
|
406
|
-
try {
|
|
407
|
-
const contents = await fs.readFile(args.path);
|
|
408
|
-
includedFiles.add(args.path);
|
|
409
|
-
return {
|
|
410
|
-
contents,
|
|
411
|
-
loader: getLoader(args.path),
|
|
412
|
-
resolveDir: args.path.substring(0, args.path.lastIndexOf("/")),
|
|
413
|
-
};
|
|
414
|
-
} catch (err) {
|
|
415
|
-
return {
|
|
416
|
-
errors: [{ text: `Failed to read ${args.path}: ${err}` }],
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
},
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Generate named export statements for shared modules.
|
|
426
|
-
*
|
|
427
|
-
* Uses dynamically discovered exports from the SharedModuleRegistry,
|
|
428
|
-
* which are populated when registerSharedModules() is called.
|
|
429
|
-
*
|
|
430
|
-
* If the module wasn't registered (or has no enumerable exports),
|
|
431
|
-
* returns a comment - named imports won't work but default import will.
|
|
432
|
-
*/
|
|
433
|
-
function generateNamedExports(moduleId: string): string {
|
|
434
|
-
const exports = getSharedModuleExports(moduleId);
|
|
435
|
-
|
|
436
|
-
if (exports.length > 0) {
|
|
437
|
-
return exports
|
|
438
|
-
.map(name => `export const ${name} = __sandlot_mod__.${name};`)
|
|
439
|
-
.join('\n');
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Module not registered or has no enumerable exports
|
|
443
|
-
// Default import will still work: import foo from 'module'
|
|
444
|
-
return `// No exports discovered for "${moduleId}" - use default import or call registerSharedModules() first`;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Bundle TypeScript/JavaScript files from a virtual filesystem
|
|
449
|
-
*
|
|
450
|
-
* @example
|
|
451
|
-
* ```ts
|
|
452
|
-
* const fs = Filesystem.create({
|
|
453
|
-
* initialFiles: {
|
|
454
|
-
* "/src/index.ts": "export const hello = 'world';",
|
|
455
|
-
* "/src/utils.ts": "export function add(a: number, b: number) { return a + b; }",
|
|
456
|
-
* }
|
|
457
|
-
* });
|
|
458
|
-
*
|
|
459
|
-
* const result = await bundle({
|
|
460
|
-
* fs,
|
|
461
|
-
* entryPoint: "/src/index.ts",
|
|
462
|
-
* });
|
|
463
|
-
*
|
|
464
|
-
* console.log(result.code);
|
|
465
|
-
* ```
|
|
466
|
-
*/
|
|
467
|
-
export async function bundle(options: BundleOptions): Promise<BundleResult> {
|
|
468
|
-
await initBundler();
|
|
469
|
-
|
|
470
|
-
const {
|
|
471
|
-
fs,
|
|
472
|
-
entryPoint,
|
|
473
|
-
external = [],
|
|
474
|
-
npmImports = "cdn",
|
|
475
|
-
sharedModules = [],
|
|
476
|
-
format = "esm",
|
|
477
|
-
minify = false,
|
|
478
|
-
sourcemap = false,
|
|
479
|
-
globalName,
|
|
480
|
-
target = ["es2020"],
|
|
481
|
-
} = options;
|
|
482
|
-
|
|
483
|
-
// Normalize entry point
|
|
484
|
-
const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
|
|
485
|
-
|
|
486
|
-
// Verify entry point exists
|
|
487
|
-
if (!(await fs.exists(normalizedEntry))) {
|
|
488
|
-
throw new Error(`Entry point not found: ${normalizedEntry}`);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Get installed packages for ESM URL rewriting
|
|
492
|
-
const manifest = await getPackageManifest(fs);
|
|
493
|
-
const installedPackages = manifest.dependencies;
|
|
494
|
-
|
|
495
|
-
// Create set of shared module IDs for fast lookup
|
|
496
|
-
const sharedModuleIds = new Set(sharedModules);
|
|
497
|
-
|
|
498
|
-
const includedFiles = new Set<string>();
|
|
499
|
-
const plugin = createVfsPlugin({
|
|
500
|
-
fs,
|
|
501
|
-
entryPoint: normalizedEntry,
|
|
502
|
-
npmImports,
|
|
503
|
-
installedPackages,
|
|
504
|
-
includedFiles,
|
|
505
|
-
sharedModuleIds,
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
const es = await getEsbuild();
|
|
509
|
-
const result = await es.build({
|
|
510
|
-
entryPoints: [normalizedEntry],
|
|
511
|
-
bundle: true,
|
|
512
|
-
write: false,
|
|
513
|
-
format,
|
|
514
|
-
minify,
|
|
515
|
-
sourcemap: sourcemap ? "inline" : false,
|
|
516
|
-
globalName,
|
|
517
|
-
target,
|
|
518
|
-
external,
|
|
519
|
-
plugins: [plugin],
|
|
520
|
-
jsx: "automatic",
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
const code = result.outputFiles?.[0]?.text ?? "";
|
|
524
|
-
|
|
525
|
-
return {
|
|
526
|
-
code,
|
|
527
|
-
warnings: result.warnings,
|
|
528
|
-
includedFiles: Array.from(includedFiles),
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Bundle and return a blob URL for dynamic import
|
|
534
|
-
*
|
|
535
|
-
* @example
|
|
536
|
-
* ```ts
|
|
537
|
-
* const url = await bundleToUrl({
|
|
538
|
-
* fs,
|
|
539
|
-
* entryPoint: "/src/index.ts",
|
|
540
|
-
* });
|
|
541
|
-
*
|
|
542
|
-
* const module = await import(url);
|
|
543
|
-
* console.log(module.hello); // 'world'
|
|
544
|
-
*
|
|
545
|
-
* // Clean up when done
|
|
546
|
-
* URL.revokeObjectURL(url);
|
|
547
|
-
* ```
|
|
548
|
-
*/
|
|
549
|
-
export async function bundleToUrl(options: BundleOptions): Promise<string> {
|
|
550
|
-
const result = await bundle(options);
|
|
551
|
-
const blob = new Blob([result.code], { type: "application/javascript" });
|
|
552
|
-
return URL.createObjectURL(blob);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Bundle and immediately import the module
|
|
557
|
-
*
|
|
558
|
-
* @example
|
|
559
|
-
* ```ts
|
|
560
|
-
* const module = await bundleAndImport<{ hello: string }>({
|
|
561
|
-
* fs,
|
|
562
|
-
* entryPoint: "/src/index.ts",
|
|
563
|
-
* });
|
|
564
|
-
*
|
|
565
|
-
* console.log(module.hello); // 'world'
|
|
566
|
-
* ```
|
|
567
|
-
*/
|
|
568
|
-
export async function bundleAndImport<T = unknown>(options: BundleOptions): Promise<T> {
|
|
569
|
-
const url = await bundleToUrl(options);
|
|
570
|
-
try {
|
|
571
|
-
return await import(/* @vite-ignore */ url);
|
|
572
|
-
} finally {
|
|
573
|
-
URL.revokeObjectURL(url);
|
|
574
|
-
}
|
|
575
|
-
}
|