sandlot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +616 -0
- package/dist/bundler.d.ts +148 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/commands.d.ts +179 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/fs.d.ts +125 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2920 -0
- package/dist/internal.d.ts +74 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +1897 -0
- package/dist/loader.d.ts +164 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/packages.d.ts +199 -0
- package/dist/packages.d.ts.map +1 -0
- package/dist/react.d.ts +159 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +149 -0
- package/dist/sandbox-manager.d.ts +249 -0
- package/dist/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox.d.ts +193 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/shared-modules.d.ts +129 -0
- package/dist/shared-modules.d.ts.map +1 -0
- package/dist/shared-resources.d.ts +105 -0
- package/dist/shared-resources.d.ts.map +1 -0
- package/dist/ts-libs.d.ts +98 -0
- package/dist/ts-libs.d.ts.map +1 -0
- package/dist/typechecker.d.ts +127 -0
- package/dist/typechecker.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/bundler.ts +513 -0
- package/src/commands.ts +733 -0
- package/src/fs.ts +935 -0
- package/src/index.ts +149 -0
- package/src/internal.ts +116 -0
- package/src/loader.ts +229 -0
- package/src/packages.ts +936 -0
- package/src/react.tsx +331 -0
- package/src/sandbox-manager.ts +490 -0
- package/src/sandbox.ts +402 -0
- package/src/shared-modules.ts +210 -0
- package/src/shared-resources.ts +169 -0
- package/src/ts-libs.ts +320 -0
- package/src/typechecker.ts +635 -0
package/src/bundler.ts
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import type { IFileSystem } from "just-bash/browser";
|
|
2
|
+
import * as esbuild from "esbuild-wasm";
|
|
3
|
+
import { getPackageManifest, resolveToEsmUrl } from "./packages";
|
|
4
|
+
import { getSharedModuleRuntimeCode } from "./shared-modules";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* How to handle npm package imports (bare imports like "react").
|
|
8
|
+
*
|
|
9
|
+
* - "cdn" (default): Rewrite to esm.sh CDN URLs using installed package versions.
|
|
10
|
+
* Requires packages to be installed via the `install` command.
|
|
11
|
+
* - "external": Mark as external, don't rewrite. The consumer must handle
|
|
12
|
+
* module resolution (useful for SSR or custom bundling).
|
|
13
|
+
* - "bundle": Attempt to bundle from node_modules. Rarely useful in browser
|
|
14
|
+
* since node_modules typically doesn't exist in the virtual filesystem.
|
|
15
|
+
*/
|
|
16
|
+
export type NpmImportsMode = "cdn" | "external" | "bundle";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for bundling
|
|
20
|
+
*/
|
|
21
|
+
export interface BundleOptions {
|
|
22
|
+
/**
|
|
23
|
+
* The virtual filesystem to read source files from
|
|
24
|
+
*/
|
|
25
|
+
fs: IFileSystem;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Entry point path (absolute path in the virtual filesystem)
|
|
29
|
+
*/
|
|
30
|
+
entryPoint: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Module names to mark as external (won't be bundled).
|
|
34
|
+
* These are in addition to bare imports when using npmImports: "external".
|
|
35
|
+
*/
|
|
36
|
+
external?: string[];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* How to handle npm package imports (bare imports like "react").
|
|
40
|
+
*
|
|
41
|
+
* - "cdn" (default): Rewrite to esm.sh CDN URLs using installed package versions
|
|
42
|
+
* - "external": Mark as external, don't rewrite (consumer must handle)
|
|
43
|
+
* - "bundle": Attempt to bundle from node_modules (rarely useful in browser)
|
|
44
|
+
*/
|
|
45
|
+
npmImports?: NpmImportsMode;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Module IDs that should be resolved from the host's SharedModuleRegistry
|
|
49
|
+
* instead of esm.sh CDN. The host must have registered these modules.
|
|
50
|
+
*
|
|
51
|
+
* Example: ['react', 'react-dom/client']
|
|
52
|
+
*
|
|
53
|
+
* When specified, imports of these modules will use the host's instances,
|
|
54
|
+
* allowing dynamic components to share React context, hooks, etc.
|
|
55
|
+
*/
|
|
56
|
+
sharedModules?: string[];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Output format: 'esm' (default), 'iife', or 'cjs'
|
|
60
|
+
*/
|
|
61
|
+
format?: "esm" | "iife" | "cjs";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Enable minification
|
|
65
|
+
* Default: false
|
|
66
|
+
*/
|
|
67
|
+
minify?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Enable source maps (inline)
|
|
71
|
+
* Default: false
|
|
72
|
+
*/
|
|
73
|
+
sourcemap?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Global name for IIFE format
|
|
77
|
+
*/
|
|
78
|
+
globalName?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Target environment(s)
|
|
82
|
+
* Default: ['es2020']
|
|
83
|
+
*/
|
|
84
|
+
target?: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Result of bundling
|
|
89
|
+
*/
|
|
90
|
+
export interface BundleResult {
|
|
91
|
+
/**
|
|
92
|
+
* The bundled JavaScript code
|
|
93
|
+
*/
|
|
94
|
+
code: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Any warnings from esbuild
|
|
98
|
+
*/
|
|
99
|
+
warnings: esbuild.Message[];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* List of files that were included in the bundle
|
|
103
|
+
*/
|
|
104
|
+
includedFiles: string[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Track initialization state
|
|
108
|
+
let initialized = false;
|
|
109
|
+
let initPromise: Promise<void> | null = null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the esbuild-wasm binary URL based on the installed version
|
|
113
|
+
*/
|
|
114
|
+
function getWasmUrl(): string {
|
|
115
|
+
// Use unpkg CDN to fetch the WASM binary matching our installed version
|
|
116
|
+
const version = "0.27.2"; // Match installed version
|
|
117
|
+
return `https://unpkg.com/esbuild-wasm@${version}/esbuild.wasm`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Initialize esbuild-wasm. Called automatically on first bundle.
|
|
122
|
+
* Can be called explicitly to pre-warm.
|
|
123
|
+
*/
|
|
124
|
+
export async function initBundler(): Promise<void> {
|
|
125
|
+
if (initialized) return;
|
|
126
|
+
|
|
127
|
+
if (initPromise) {
|
|
128
|
+
await initPromise;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
initPromise = esbuild.initialize({
|
|
133
|
+
wasmURL: getWasmUrl(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await initPromise;
|
|
137
|
+
initialized = true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if a path is a bare import (not relative or absolute)
|
|
142
|
+
*/
|
|
143
|
+
function isBareImport(path: string): boolean {
|
|
144
|
+
return !path.startsWith(".") && !path.startsWith("/");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the appropriate loader based on file extension
|
|
149
|
+
*/
|
|
150
|
+
function getLoader(path: string): esbuild.Loader {
|
|
151
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
152
|
+
switch (ext) {
|
|
153
|
+
case "ts":
|
|
154
|
+
return "ts";
|
|
155
|
+
case "tsx":
|
|
156
|
+
return "tsx";
|
|
157
|
+
case "jsx":
|
|
158
|
+
return "jsx";
|
|
159
|
+
case "js":
|
|
160
|
+
case "mjs":
|
|
161
|
+
return "js";
|
|
162
|
+
case "json":
|
|
163
|
+
return "json";
|
|
164
|
+
case "css":
|
|
165
|
+
return "css";
|
|
166
|
+
case "txt":
|
|
167
|
+
return "text";
|
|
168
|
+
default:
|
|
169
|
+
return "js";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Options for the VFS plugin
|
|
175
|
+
*/
|
|
176
|
+
interface VfsPluginOptions {
|
|
177
|
+
fs: IFileSystem;
|
|
178
|
+
entryPoint: string;
|
|
179
|
+
npmImports: NpmImportsMode;
|
|
180
|
+
installedPackages: Record<string, string>;
|
|
181
|
+
includedFiles: Set<string>;
|
|
182
|
+
/** Module IDs to resolve from SharedModuleRegistry */
|
|
183
|
+
sharedModuleIds: Set<string>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Check if an import path matches a shared module ID.
|
|
188
|
+
* Handles both exact matches and subpath imports (e.g., 'react-dom/client').
|
|
189
|
+
*/
|
|
190
|
+
function matchesSharedModule(importPath: string, sharedModuleIds: Set<string>): string | null {
|
|
191
|
+
// Check exact match first
|
|
192
|
+
if (sharedModuleIds.has(importPath)) {
|
|
193
|
+
return importPath;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if any shared module is a prefix (for subpath imports)
|
|
197
|
+
// e.g., if 'react-dom' is registered, 'react-dom/client' should match
|
|
198
|
+
for (const moduleId of sharedModuleIds) {
|
|
199
|
+
if (importPath === moduleId || importPath.startsWith(moduleId + '/')) {
|
|
200
|
+
return importPath;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create an esbuild plugin that reads from a virtual filesystem
|
|
209
|
+
*/
|
|
210
|
+
function createVfsPlugin(options: VfsPluginOptions): esbuild.Plugin {
|
|
211
|
+
const {
|
|
212
|
+
fs,
|
|
213
|
+
entryPoint,
|
|
214
|
+
npmImports,
|
|
215
|
+
installedPackages,
|
|
216
|
+
includedFiles,
|
|
217
|
+
sharedModuleIds,
|
|
218
|
+
} = options;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
name: "virtual-fs",
|
|
222
|
+
setup(build) {
|
|
223
|
+
// Resolve all imports
|
|
224
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
225
|
+
// Handle the virtual entry point
|
|
226
|
+
if (args.kind === "entry-point") {
|
|
227
|
+
return { path: entryPoint, namespace: "vfs" };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Handle bare imports
|
|
231
|
+
if (isBareImport(args.path)) {
|
|
232
|
+
// Check if this module should use the shared registry
|
|
233
|
+
const sharedMatch = matchesSharedModule(args.path, sharedModuleIds);
|
|
234
|
+
if (sharedMatch) {
|
|
235
|
+
return {
|
|
236
|
+
path: sharedMatch,
|
|
237
|
+
namespace: "sandlot-shared"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Handle based on npmImports mode
|
|
242
|
+
switch (npmImports) {
|
|
243
|
+
case "cdn": {
|
|
244
|
+
// Try to rewrite to esm.sh URL if package is installed
|
|
245
|
+
const esmUrl = resolveToEsmUrl(args.path, installedPackages);
|
|
246
|
+
if (esmUrl) {
|
|
247
|
+
return { path: esmUrl, external: true };
|
|
248
|
+
}
|
|
249
|
+
// Fall back to external if not installed
|
|
250
|
+
return { path: args.path, external: true };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case "external":
|
|
254
|
+
// Mark as external, don't rewrite
|
|
255
|
+
return { path: args.path, external: true };
|
|
256
|
+
|
|
257
|
+
case "bundle": {
|
|
258
|
+
// Try to resolve from VFS node_modules
|
|
259
|
+
const resolved = fs.resolvePath(args.resolveDir, `node_modules/${args.path}`);
|
|
260
|
+
const exists = await fs.exists(resolved);
|
|
261
|
+
if (exists) {
|
|
262
|
+
return { path: resolved, namespace: "vfs" };
|
|
263
|
+
}
|
|
264
|
+
// Fall back to external if not found
|
|
265
|
+
return { path: args.path, external: true };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Resolve relative/absolute paths
|
|
271
|
+
const resolved = fs.resolvePath(args.resolveDir, args.path);
|
|
272
|
+
|
|
273
|
+
// Try with extensions if no extension provided
|
|
274
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".json"];
|
|
275
|
+
const hasExtension = extensions.some((ext) => resolved.endsWith(ext));
|
|
276
|
+
|
|
277
|
+
if (hasExtension) {
|
|
278
|
+
const exists = await fs.exists(resolved);
|
|
279
|
+
if (exists) {
|
|
280
|
+
return { path: resolved, namespace: "vfs" };
|
|
281
|
+
}
|
|
282
|
+
return { errors: [{ text: `File not found: ${resolved}` }] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Try adding extensions
|
|
286
|
+
for (const ext of extensions) {
|
|
287
|
+
const withExt = resolved + ext;
|
|
288
|
+
if (await fs.exists(withExt)) {
|
|
289
|
+
return { path: withExt, namespace: "vfs" };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Try index files
|
|
294
|
+
for (const ext of extensions) {
|
|
295
|
+
const indexPath = `${resolved}/index${ext}`;
|
|
296
|
+
if (await fs.exists(indexPath)) {
|
|
297
|
+
return { path: indexPath, namespace: "vfs" };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { errors: [{ text: `Cannot resolve: ${args.path} from ${args.resolveDir}` }] };
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Load shared modules from the registry
|
|
305
|
+
build.onLoad({ filter: /.*/, namespace: "sandlot-shared" }, (args) => {
|
|
306
|
+
// Generate code that looks up the module from the global registry
|
|
307
|
+
const contents = `export default ${getSharedModuleRuntimeCode(args.path)};
|
|
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 = `
|
|
321
|
+
const __sandlot_mod__ = ${getSharedModuleRuntimeCode(args.path)};
|
|
322
|
+
export default __sandlot_mod__.default ?? __sandlot_mod__;
|
|
323
|
+
${generateNamedExports(args.path)}
|
|
324
|
+
`;
|
|
325
|
+
return {
|
|
326
|
+
contents: esmContents.trim(),
|
|
327
|
+
loader: 'js'
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Load files from VFS
|
|
332
|
+
build.onLoad({ filter: /.*/, namespace: "vfs" }, async (args) => {
|
|
333
|
+
try {
|
|
334
|
+
const contents = await fs.readFile(args.path);
|
|
335
|
+
includedFiles.add(args.path);
|
|
336
|
+
return {
|
|
337
|
+
contents,
|
|
338
|
+
loader: getLoader(args.path),
|
|
339
|
+
resolveDir: args.path.substring(0, args.path.lastIndexOf("/")),
|
|
340
|
+
};
|
|
341
|
+
} catch (err) {
|
|
342
|
+
return {
|
|
343
|
+
errors: [{ text: `Failed to read ${args.path}: ${err}` }],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Generate named export statements for common React exports.
|
|
353
|
+
* This is a pragmatic approach - for full generality, we'd need to
|
|
354
|
+
* introspect the actual module, but that's not possible at build time.
|
|
355
|
+
*/
|
|
356
|
+
function generateNamedExports(moduleId: string): string {
|
|
357
|
+
// Common exports for well-known modules
|
|
358
|
+
const knownExports: Record<string, string[]> = {
|
|
359
|
+
'react': [
|
|
360
|
+
'useState', 'useEffect', 'useContext', 'useReducer', 'useCallback',
|
|
361
|
+
'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect',
|
|
362
|
+
'useDebugValue', 'useDeferredValue', 'useTransition', 'useId',
|
|
363
|
+
'useSyncExternalStore', 'useInsertionEffect', 'useOptimistic', 'useActionState',
|
|
364
|
+
'createElement', 'cloneElement', 'createContext', 'forwardRef', 'lazy', 'memo',
|
|
365
|
+
'startTransition', 'Children', 'Component', 'PureComponent', 'Fragment',
|
|
366
|
+
'Profiler', 'StrictMode', 'Suspense', 'version', 'isValidElement',
|
|
367
|
+
],
|
|
368
|
+
'react-dom': ['createPortal', 'flushSync', 'version'],
|
|
369
|
+
'react-dom/client': ['createRoot', 'hydrateRoot'],
|
|
370
|
+
'react-dom/server': ['renderToString', 'renderToStaticMarkup', 'renderToPipeableStream'],
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const exports = knownExports[moduleId];
|
|
374
|
+
if (!exports) {
|
|
375
|
+
// For unknown modules, generate a proxy that re-exports everything
|
|
376
|
+
return `
|
|
377
|
+
// Dynamic re-export for unknown module
|
|
378
|
+
export const __moduleProxy__ = __sandlot_mod__;
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return exports
|
|
383
|
+
.map(name => `export const ${name} = __sandlot_mod__.${name};`)
|
|
384
|
+
.join('\n');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Bundle TypeScript/JavaScript files from a virtual filesystem
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```ts
|
|
392
|
+
* const fs = IndexedDbFs.createInMemory({
|
|
393
|
+
* initialFiles: {
|
|
394
|
+
* "/src/index.ts": "export const hello = 'world';",
|
|
395
|
+
* "/src/utils.ts": "export function add(a: number, b: number) { return a + b; }",
|
|
396
|
+
* }
|
|
397
|
+
* });
|
|
398
|
+
*
|
|
399
|
+
* const result = await bundle({
|
|
400
|
+
* fs,
|
|
401
|
+
* entryPoint: "/src/index.ts",
|
|
402
|
+
* });
|
|
403
|
+
*
|
|
404
|
+
* console.log(result.code);
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
export async function bundle(options: BundleOptions): Promise<BundleResult> {
|
|
408
|
+
await initBundler();
|
|
409
|
+
|
|
410
|
+
const {
|
|
411
|
+
fs,
|
|
412
|
+
entryPoint,
|
|
413
|
+
external = [],
|
|
414
|
+
npmImports = "cdn",
|
|
415
|
+
sharedModules = [],
|
|
416
|
+
format = "esm",
|
|
417
|
+
minify = false,
|
|
418
|
+
sourcemap = false,
|
|
419
|
+
globalName,
|
|
420
|
+
target = ["es2020"],
|
|
421
|
+
} = options;
|
|
422
|
+
|
|
423
|
+
// Normalize entry point
|
|
424
|
+
const normalizedEntry = entryPoint.startsWith("/") ? entryPoint : `/${entryPoint}`;
|
|
425
|
+
|
|
426
|
+
// Verify entry point exists
|
|
427
|
+
if (!(await fs.exists(normalizedEntry))) {
|
|
428
|
+
throw new Error(`Entry point not found: ${normalizedEntry}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Get installed packages for ESM URL rewriting
|
|
432
|
+
const manifest = await getPackageManifest(fs);
|
|
433
|
+
const installedPackages = manifest.dependencies;
|
|
434
|
+
|
|
435
|
+
// Create set of shared module IDs for fast lookup
|
|
436
|
+
const sharedModuleIds = new Set(sharedModules);
|
|
437
|
+
|
|
438
|
+
const includedFiles = new Set<string>();
|
|
439
|
+
const plugin = createVfsPlugin({
|
|
440
|
+
fs,
|
|
441
|
+
entryPoint: normalizedEntry,
|
|
442
|
+
npmImports,
|
|
443
|
+
installedPackages,
|
|
444
|
+
includedFiles,
|
|
445
|
+
sharedModuleIds,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const result = await esbuild.build({
|
|
449
|
+
entryPoints: [normalizedEntry],
|
|
450
|
+
bundle: true,
|
|
451
|
+
write: false,
|
|
452
|
+
format,
|
|
453
|
+
minify,
|
|
454
|
+
sourcemap: sourcemap ? "inline" : false,
|
|
455
|
+
globalName,
|
|
456
|
+
target,
|
|
457
|
+
external,
|
|
458
|
+
plugins: [plugin],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const code = result.outputFiles?.[0]?.text ?? "";
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
code,
|
|
465
|
+
warnings: result.warnings,
|
|
466
|
+
includedFiles: Array.from(includedFiles),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Bundle and return a blob URL for dynamic import
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```ts
|
|
475
|
+
* const url = await bundleToUrl({
|
|
476
|
+
* fs,
|
|
477
|
+
* entryPoint: "/src/index.ts",
|
|
478
|
+
* });
|
|
479
|
+
*
|
|
480
|
+
* const module = await import(url);
|
|
481
|
+
* console.log(module.hello); // 'world'
|
|
482
|
+
*
|
|
483
|
+
* // Clean up when done
|
|
484
|
+
* URL.revokeObjectURL(url);
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
export async function bundleToUrl(options: BundleOptions): Promise<string> {
|
|
488
|
+
const result = await bundle(options);
|
|
489
|
+
const blob = new Blob([result.code], { type: "application/javascript" });
|
|
490
|
+
return URL.createObjectURL(blob);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Bundle and immediately import the module
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```ts
|
|
498
|
+
* const module = await bundleAndImport<{ hello: string }>({
|
|
499
|
+
* fs,
|
|
500
|
+
* entryPoint: "/src/index.ts",
|
|
501
|
+
* });
|
|
502
|
+
*
|
|
503
|
+
* console.log(module.hello); // 'world'
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
export async function bundleAndImport<T = unknown>(options: BundleOptions): Promise<T> {
|
|
507
|
+
const url = await bundleToUrl(options);
|
|
508
|
+
try {
|
|
509
|
+
return await import(/* @vite-ignore */ url);
|
|
510
|
+
} finally {
|
|
511
|
+
URL.revokeObjectURL(url);
|
|
512
|
+
}
|
|
513
|
+
}
|