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/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module loader utilities for Sandlot bundles.
|
|
3
|
+
*
|
|
4
|
+
* Takes a BundleResult and turns it into usable JavaScript exports.
|
|
5
|
+
* No external dependencies - just the basics for loading compiled code.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // Capture the bundle via onBuild callback
|
|
10
|
+
* let buildResult: BundleResult | null = null;
|
|
11
|
+
* const unsubscribe = sandbox.onBuild((result) => {
|
|
12
|
+
* buildResult = result;
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* const cmd = await sandbox.bash.exec("build /src/index.ts");
|
|
16
|
+
* if (cmd.exitCode === 0 && buildResult) {
|
|
17
|
+
* // Load all exports
|
|
18
|
+
* const module = await loadModule<{ add: (a: number, b: number) => number }>(buildResult);
|
|
19
|
+
* console.log(module.add(1, 2)); // 3
|
|
20
|
+
*
|
|
21
|
+
* // Load a specific export
|
|
22
|
+
* const add = await loadExport<(a: number, b: number) => number>(buildResult, "add");
|
|
23
|
+
* console.log(add(1, 2)); // 3
|
|
24
|
+
* }
|
|
25
|
+
* unsubscribe();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import type { BundleResult } from "./bundler";
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when loading a module fails
|
|
31
|
+
*/
|
|
32
|
+
export declare class ModuleLoadError extends Error {
|
|
33
|
+
constructor(message: string, cause?: unknown);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Error thrown when an expected export is not found
|
|
37
|
+
*/
|
|
38
|
+
export declare class ExportNotFoundError extends Error {
|
|
39
|
+
readonly exportName: string;
|
|
40
|
+
readonly availableExports: string[];
|
|
41
|
+
constructor(exportName: string, availableExports: string[]);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a blob URL for a bundle that can be dynamically imported.
|
|
45
|
+
* Remember to call `revokeModuleUrl()` when done to free memory.
|
|
46
|
+
*
|
|
47
|
+
* @param result - The bundle result from a successful build
|
|
48
|
+
* @returns A blob URL that can be passed to `import()`
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const url = createModuleUrl(buildResult);
|
|
53
|
+
* try {
|
|
54
|
+
* const module = await import(url);
|
|
55
|
+
* console.log(module.default);
|
|
56
|
+
* } finally {
|
|
57
|
+
* revokeModuleUrl(url);
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function createModuleUrl(result: BundleResult): string;
|
|
62
|
+
/**
|
|
63
|
+
* Revoke a blob URL created by `createModuleUrl()`.
|
|
64
|
+
* This frees the memory associated with the blob.
|
|
65
|
+
*
|
|
66
|
+
* @param url - The blob URL to revoke
|
|
67
|
+
*/
|
|
68
|
+
export declare function revokeModuleUrl(url: string): void;
|
|
69
|
+
/**
|
|
70
|
+
* Load all exports from a bundle result.
|
|
71
|
+
*
|
|
72
|
+
* @typeParam T - The expected shape of the module's exports
|
|
73
|
+
* @param result - The bundle result from a successful build
|
|
74
|
+
* @returns A promise that resolves to the module's exports
|
|
75
|
+
* @throws {ModuleLoadError} If the module fails to load
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* interface MyModule {
|
|
80
|
+
* add: (a: number, b: number) => number;
|
|
81
|
+
* multiply: (a: number, b: number) => number;
|
|
82
|
+
* }
|
|
83
|
+
*
|
|
84
|
+
* const module = await loadModule<MyModule>(buildResult);
|
|
85
|
+
* console.log(module.add(2, 3)); // 5
|
|
86
|
+
* console.log(module.multiply(2, 3)); // 6
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare function loadModule<T = Record<string, unknown>>(result: BundleResult): Promise<T>;
|
|
90
|
+
/**
|
|
91
|
+
* Load a specific named export from a bundle result.
|
|
92
|
+
*
|
|
93
|
+
* @typeParam T - The expected type of the export
|
|
94
|
+
* @param result - The bundle result from a successful build
|
|
95
|
+
* @param exportName - The name of the export to retrieve (use "default" for default export)
|
|
96
|
+
* @returns A promise that resolves to the export's value
|
|
97
|
+
* @throws {ModuleLoadError} If the module fails to load
|
|
98
|
+
* @throws {ExportNotFoundError} If the export doesn't exist
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* // Load a named export
|
|
103
|
+
* const add = await loadExport<(a: number, b: number) => number>(
|
|
104
|
+
* buildResult,
|
|
105
|
+
* "add"
|
|
106
|
+
* );
|
|
107
|
+
* console.log(add(2, 3)); // 5
|
|
108
|
+
*
|
|
109
|
+
* // Load the default export
|
|
110
|
+
* const Calculator = await loadExport<typeof Calculator>(
|
|
111
|
+
* buildResult,
|
|
112
|
+
* "default"
|
|
113
|
+
* );
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare function loadExport<T = unknown>(result: BundleResult, exportName?: string): Promise<T>;
|
|
117
|
+
/**
|
|
118
|
+
* Load the default export from a bundle result.
|
|
119
|
+
* Convenience wrapper around `loadExport(result, "default")`.
|
|
120
|
+
*
|
|
121
|
+
* @typeParam T - The expected type of the default export
|
|
122
|
+
* @param result - The bundle result from a successful build
|
|
123
|
+
* @returns A promise that resolves to the default export
|
|
124
|
+
* @throws {ModuleLoadError} If the module fails to load
|
|
125
|
+
* @throws {ExportNotFoundError} If there is no default export
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* // For a module that does: export default function add(a, b) { return a + b; }
|
|
130
|
+
* const add = await loadDefault<(a: number, b: number) => number>(buildResult);
|
|
131
|
+
* console.log(add(2, 3)); // 5
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function loadDefault<T = unknown>(result: BundleResult): Promise<T>;
|
|
135
|
+
/**
|
|
136
|
+
* Get a list of export names from a bundle result.
|
|
137
|
+
* Useful for introspection or debugging.
|
|
138
|
+
*
|
|
139
|
+
* @param result - The bundle result from a successful build
|
|
140
|
+
* @returns A promise that resolves to an array of export names
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const exports = await getExportNames(buildResult);
|
|
145
|
+
* console.log(exports); // ["add", "multiply", "default"]
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export declare function getExportNames(result: BundleResult): Promise<string[]>;
|
|
149
|
+
/**
|
|
150
|
+
* Check if a bundle has a specific export.
|
|
151
|
+
*
|
|
152
|
+
* @param result - The bundle result from a successful build
|
|
153
|
+
* @param exportName - The name of the export to check for
|
|
154
|
+
* @returns A promise that resolves to true if the export exists
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* if (await hasExport(buildResult, "add")) {
|
|
159
|
+
* const add = await loadExport(buildResult, "add");
|
|
160
|
+
* }
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export declare function hasExport(result: BundleResult, exportName: string): Promise<boolean>;
|
|
164
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C;;GAEG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,UAAU,EAAE,MAAM;aAClB,gBAAgB,EAAE,MAAM,EAAE;gBAD1B,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,MAAM,EAAE;CAQ7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAG5D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1D,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1C,MAAM,EAAE,YAAY,EACpB,UAAU,GAAE,MAAkB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAE/E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG5E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAGlB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package management for sandbox environments.
|
|
3
|
+
*
|
|
4
|
+
* Provides npm-like package installation using esm.sh CDN:
|
|
5
|
+
* - Fetches TypeScript type definitions for editor/typecheck support
|
|
6
|
+
* - Stores installed versions in package.json
|
|
7
|
+
* - Resolves bare imports to CDN URLs at bundle time
|
|
8
|
+
* - Supports @types/* packages (fetches package content as types)
|
|
9
|
+
* - Supports subpath exports (react-dom/client, react/jsx-runtime)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // Install a package
|
|
14
|
+
* const result = await installPackage(fs, "react");
|
|
15
|
+
* // result: { name: "react", version: "18.2.0", typesInstalled: true }
|
|
16
|
+
*
|
|
17
|
+
* // Get installed packages
|
|
18
|
+
* const manifest = await getPackageManifest(fs);
|
|
19
|
+
* // manifest.dependencies: { "react": "18.2.0" }
|
|
20
|
+
*
|
|
21
|
+
* // Resolve import to CDN URL
|
|
22
|
+
* const url = resolveToEsmUrl("react", "18.2.0");
|
|
23
|
+
* // url: "https://esm.sh/react@18.2.0"
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import type { IFileSystem } from "just-bash/browser";
|
|
27
|
+
/**
|
|
28
|
+
* Package manifest (subset of package.json)
|
|
29
|
+
*/
|
|
30
|
+
export interface PackageManifest {
|
|
31
|
+
dependencies: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Cache for storing fetched type definitions.
|
|
35
|
+
* Used to avoid redundant network fetches when multiple sandboxes
|
|
36
|
+
* install the same packages.
|
|
37
|
+
*/
|
|
38
|
+
export interface TypesCache {
|
|
39
|
+
/**
|
|
40
|
+
* Get cached type definitions for a package version.
|
|
41
|
+
* Returns null if not cached.
|
|
42
|
+
*/
|
|
43
|
+
get(name: string, version: string): Map<string, string> | null;
|
|
44
|
+
/**
|
|
45
|
+
* Store type definitions in the cache.
|
|
46
|
+
*/
|
|
47
|
+
set(name: string, version: string, types: Map<string, string>): void;
|
|
48
|
+
/**
|
|
49
|
+
* Check if a package version is cached.
|
|
50
|
+
*/
|
|
51
|
+
has(name: string, version: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Remove a package version from the cache.
|
|
54
|
+
*/
|
|
55
|
+
delete(name: string, version: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all cached entries.
|
|
58
|
+
*/
|
|
59
|
+
clear(): void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* In-memory implementation of TypesCache.
|
|
63
|
+
* Suitable for sharing across multiple sandboxes within a session.
|
|
64
|
+
*/
|
|
65
|
+
export declare class InMemoryTypesCache implements TypesCache {
|
|
66
|
+
private cache;
|
|
67
|
+
private key;
|
|
68
|
+
get(name: string, version: string): Map<string, string> | null;
|
|
69
|
+
set(name: string, version: string, types: Map<string, string>): void;
|
|
70
|
+
has(name: string, version: string): boolean;
|
|
71
|
+
delete(name: string, version: string): boolean;
|
|
72
|
+
clear(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Get the number of cached packages (for diagnostics).
|
|
75
|
+
*/
|
|
76
|
+
get size(): number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Result of installing a package
|
|
80
|
+
*/
|
|
81
|
+
export interface InstallResult {
|
|
82
|
+
/** Package name */
|
|
83
|
+
name: string;
|
|
84
|
+
/** Resolved version */
|
|
85
|
+
version: string;
|
|
86
|
+
/** Whether type definitions were installed */
|
|
87
|
+
typesInstalled: boolean;
|
|
88
|
+
/** Number of type definition files installed */
|
|
89
|
+
typeFilesCount: number;
|
|
90
|
+
/** Error message if types failed (but package still usable) */
|
|
91
|
+
typesError?: string;
|
|
92
|
+
/** Whether types were loaded from cache */
|
|
93
|
+
fromCache?: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Options for installing a package
|
|
97
|
+
*/
|
|
98
|
+
export interface InstallOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Cache to use for storing/retrieving type definitions.
|
|
101
|
+
* When provided, avoids redundant network fetches for packages
|
|
102
|
+
* that have already been installed in other sandboxes.
|
|
103
|
+
*/
|
|
104
|
+
cache?: TypesCache;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parse package specifier into name and version
|
|
108
|
+
* Examples:
|
|
109
|
+
* "react" -> { name: "react", version: undefined }
|
|
110
|
+
* "react@18" -> { name: "react", version: "18" }
|
|
111
|
+
* "@tanstack/react-query@5" -> { name: "@tanstack/react-query", version: "5" }
|
|
112
|
+
*/
|
|
113
|
+
export declare function parsePackageSpec(spec: string): {
|
|
114
|
+
name: string;
|
|
115
|
+
version?: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Read the package manifest from the filesystem
|
|
119
|
+
*/
|
|
120
|
+
export declare function getPackageManifest(fs: IFileSystem): Promise<PackageManifest>;
|
|
121
|
+
/**
|
|
122
|
+
* Install a package from npm via esm.sh
|
|
123
|
+
*
|
|
124
|
+
* This fetches type definitions and stores them in the virtual filesystem,
|
|
125
|
+
* then updates package.json with the installed version.
|
|
126
|
+
*
|
|
127
|
+
* Special handling:
|
|
128
|
+
* - @types/* packages: Fetches package content directly as types
|
|
129
|
+
* - Known packages (react, react-dom): Auto-fetches subpath types
|
|
130
|
+
*
|
|
131
|
+
* @param fs - The virtual filesystem
|
|
132
|
+
* @param packageSpec - Package name with optional version (e.g., "react", "lodash@4")
|
|
133
|
+
* @returns Install result with version and type info
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // Install latest version
|
|
138
|
+
* await installPackage(fs, "react");
|
|
139
|
+
*
|
|
140
|
+
* // Install specific version
|
|
141
|
+
* await installPackage(fs, "lodash@4.17.21");
|
|
142
|
+
*
|
|
143
|
+
* // Install scoped package
|
|
144
|
+
* await installPackage(fs, "@tanstack/react-query@5");
|
|
145
|
+
*
|
|
146
|
+
* // Install @types package
|
|
147
|
+
* await installPackage(fs, "@types/lodash");
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export declare function installPackage(fs: IFileSystem, packageSpec: string, options?: InstallOptions): Promise<InstallResult>;
|
|
151
|
+
/**
|
|
152
|
+
* Uninstall a package
|
|
153
|
+
*
|
|
154
|
+
* Removes the package from package.json and deletes type definitions.
|
|
155
|
+
*/
|
|
156
|
+
export declare function uninstallPackage(fs: IFileSystem, packageName: string): Promise<boolean>;
|
|
157
|
+
/**
|
|
158
|
+
* Resolve a bare import to an esm.sh URL
|
|
159
|
+
*
|
|
160
|
+
* @param importPath - The import path (e.g., "react", "lodash/debounce")
|
|
161
|
+
* @param installedPackages - Map of package name to version
|
|
162
|
+
* @returns The CDN URL, or null if package not installed
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const packages = { "react": "18.2.0", "lodash-es": "4.17.21" };
|
|
167
|
+
*
|
|
168
|
+
* resolveToEsmUrl("react", packages);
|
|
169
|
+
* // "https://esm.sh/react@18.2.0"
|
|
170
|
+
*
|
|
171
|
+
* resolveToEsmUrl("lodash-es/debounce", packages);
|
|
172
|
+
* // "https://esm.sh/lodash-es@4.17.21/debounce"
|
|
173
|
+
*
|
|
174
|
+
* resolveToEsmUrl("unknown", packages);
|
|
175
|
+
* // null
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export declare function resolveToEsmUrl(importPath: string, installedPackages: Record<string, string>): string | null;
|
|
179
|
+
/**
|
|
180
|
+
* Parse an import path into package name and subpath
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* parseImportPath("react") // { packageName: "react", subpath: undefined }
|
|
184
|
+
* parseImportPath("lodash/debounce") // { packageName: "lodash", subpath: "debounce" }
|
|
185
|
+
* parseImportPath("@tanstack/react-query") // { packageName: "@tanstack/react-query", subpath: undefined }
|
|
186
|
+
* parseImportPath("@tanstack/react-query/devtools") // { packageName: "@tanstack/react-query", subpath: "devtools" }
|
|
187
|
+
*/
|
|
188
|
+
export declare function parseImportPath(importPath: string): {
|
|
189
|
+
packageName: string;
|
|
190
|
+
subpath?: string;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* List all installed packages
|
|
194
|
+
*/
|
|
195
|
+
export declare function listPackages(fs: IFileSystem): Promise<Array<{
|
|
196
|
+
name: string;
|
|
197
|
+
version: string;
|
|
198
|
+
}>>;
|
|
199
|
+
//# sourceMappingURL=packages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packages.d.ts","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-specific helpers for Sandlot
|
|
3
|
+
*
|
|
4
|
+
* These helpers simplify working with dynamically loaded React components,
|
|
5
|
+
* particularly when using the render function pattern for isolation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { DynamicMount } from 'sandlot/react';
|
|
10
|
+
*
|
|
11
|
+
* function App() {
|
|
12
|
+
* const [module, setModule] = useState(null);
|
|
13
|
+
*
|
|
14
|
+
* return (
|
|
15
|
+
* <DynamicMount
|
|
16
|
+
* module={module}
|
|
17
|
+
* props={{ name: "World" }}
|
|
18
|
+
* fallback={<div>Loading...</div>}
|
|
19
|
+
* />
|
|
20
|
+
* );
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import { type ReactElement, type RefObject, type CSSProperties } from "react";
|
|
25
|
+
/**
|
|
26
|
+
* Interface for dynamic modules that use the render function pattern.
|
|
27
|
+
* Dynamic components should export a `render` function that mounts
|
|
28
|
+
* the component into a container and returns a cleanup function.
|
|
29
|
+
*/
|
|
30
|
+
export interface DynamicRenderModule<P = Record<string, unknown>> {
|
|
31
|
+
/**
|
|
32
|
+
* Mount the component into a container element
|
|
33
|
+
*
|
|
34
|
+
* @param container - The DOM element to render into
|
|
35
|
+
* @param props - Props to pass to the component
|
|
36
|
+
* @returns A cleanup function to unmount the component
|
|
37
|
+
*/
|
|
38
|
+
render: (container: HTMLElement, props?: P) => (() => void) | void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Props for the DynamicMount component
|
|
42
|
+
*/
|
|
43
|
+
export interface DynamicMountProps<P = Record<string, unknown>> {
|
|
44
|
+
/** The loaded dynamic module with a render function */
|
|
45
|
+
module: DynamicRenderModule<P> | null | undefined;
|
|
46
|
+
/** Props to pass to the dynamic component */
|
|
47
|
+
props?: P;
|
|
48
|
+
/** Optional className for the container div */
|
|
49
|
+
className?: string;
|
|
50
|
+
/** Optional style for the container div */
|
|
51
|
+
style?: CSSProperties;
|
|
52
|
+
/** Optional id for the container div */
|
|
53
|
+
id?: string;
|
|
54
|
+
/** Content to render while module is loading (null/undefined) */
|
|
55
|
+
fallback?: ReactElement | null;
|
|
56
|
+
/** Called when the dynamic component mounts */
|
|
57
|
+
onMount?: () => void;
|
|
58
|
+
/** Called when the dynamic component unmounts */
|
|
59
|
+
onUnmount?: () => void;
|
|
60
|
+
/** Called if rendering fails */
|
|
61
|
+
onError?: (error: Error) => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Component that mounts a dynamic module's render function into a container.
|
|
65
|
+
* Handles cleanup automatically when the module changes or unmounts.
|
|
66
|
+
*
|
|
67
|
+
* This is the recommended way to render dynamically loaded components
|
|
68
|
+
* that use the render function pattern for React instance isolation.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```tsx
|
|
72
|
+
* import { DynamicMount } from 'sandlot/react';
|
|
73
|
+
* import { loadModule } from 'sandlot';
|
|
74
|
+
*
|
|
75
|
+
* function App() {
|
|
76
|
+
* const [module, setModule] = useState(null);
|
|
77
|
+
*
|
|
78
|
+
* const loadComponent = async (buildResult: BundleResult) => {
|
|
79
|
+
* const mod = await loadModule(buildResult);
|
|
80
|
+
* setModule(mod);
|
|
81
|
+
* };
|
|
82
|
+
*
|
|
83
|
+
* return (
|
|
84
|
+
* <div>
|
|
85
|
+
* <button onClick={loadComponent}>Load</button>
|
|
86
|
+
* <DynamicMount
|
|
87
|
+
* module={module}
|
|
88
|
+
* props={{ count: 5 }}
|
|
89
|
+
* fallback={<div>Click to load...</div>}
|
|
90
|
+
* className="dynamic-container"
|
|
91
|
+
* />
|
|
92
|
+
* </div>
|
|
93
|
+
* );
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function DynamicMount<P = Record<string, unknown>>({ module, props, className, style, id, fallback, onMount, onUnmount, onError, }: DynamicMountProps<P>): ReactElement | null;
|
|
98
|
+
/**
|
|
99
|
+
* Result returned by useDynamicComponent hook
|
|
100
|
+
*/
|
|
101
|
+
export interface UseDynamicComponentResult {
|
|
102
|
+
/** Ref to attach to the container element */
|
|
103
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
104
|
+
/** Whether the component is currently mounted */
|
|
105
|
+
isMounted: boolean;
|
|
106
|
+
/** Any error that occurred during mounting */
|
|
107
|
+
error: Error | null;
|
|
108
|
+
/** Manually trigger a re-render with new props */
|
|
109
|
+
update: (props?: Record<string, unknown>) => void;
|
|
110
|
+
/** Manually unmount the component */
|
|
111
|
+
unmount: () => void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Hook for mounting dynamic modules with more control than DynamicMount.
|
|
115
|
+
* Provides access to mount state and manual control over mounting/unmounting.
|
|
116
|
+
*
|
|
117
|
+
* @param module - The dynamic module to mount
|
|
118
|
+
* @param props - Props to pass to the component
|
|
119
|
+
* @returns Object with containerRef, state, and control functions
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* import { useDynamicComponent } from 'sandlot/react';
|
|
124
|
+
*
|
|
125
|
+
* function App() {
|
|
126
|
+
* const { containerRef, isMounted, error, unmount } = useDynamicComponent(
|
|
127
|
+
* module,
|
|
128
|
+
* { initialCount: 0 }
|
|
129
|
+
* );
|
|
130
|
+
*
|
|
131
|
+
* return (
|
|
132
|
+
* <div>
|
|
133
|
+
* <div ref={containerRef} />
|
|
134
|
+
* {isMounted && <button onClick={unmount}>Remove</button>}
|
|
135
|
+
* {error && <div>Error: {error.message}</div>}
|
|
136
|
+
* </div>
|
|
137
|
+
* );
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function useDynamicComponent<P = Record<string, unknown>>(module: DynamicRenderModule<P> | null | undefined, props?: P): UseDynamicComponentResult;
|
|
142
|
+
/**
|
|
143
|
+
* Code template for a React component's render function.
|
|
144
|
+
* This is the pattern dynamic components should follow when using
|
|
145
|
+
* the render function pattern for React instance isolation.
|
|
146
|
+
*
|
|
147
|
+
* Dynamic components import React from esm.sh and use their own
|
|
148
|
+
* ReactDOM.createRoot to mount, avoiding conflicts with the host's React.
|
|
149
|
+
*/
|
|
150
|
+
export declare const REACT_RENDER_TEMPLATE: string;
|
|
151
|
+
/**
|
|
152
|
+
* Generate render function code for a given component name.
|
|
153
|
+
* Useful for code generation tools or agents.
|
|
154
|
+
*
|
|
155
|
+
* @param componentName - The name of the component to wrap
|
|
156
|
+
* @param hasProps - Whether the component accepts props
|
|
157
|
+
*/
|
|
158
|
+
export declare function generateRenderFunction(componentName: string, hasProps?: boolean): string;
|
|
159
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAEf;;;;GAIG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC9D;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5D,uDAAuD;IACvD,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAClD,6CAA6C;IAC7C,KAAK,CAAC,EAAE,CAAC,CAAC;IACV,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,wCAAwC;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,QAAQ,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,gCAAgC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACxD,MAAM,EACN,KAAK,EACL,SAAS,EACT,KAAK,EACL,EAAE,EACF,QAAe,EACf,OAAO,EACP,SAAS,EACT,OAAO,GACR,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,IAAI,CA4D5C;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,6CAA6C;IAC7C,YAAY,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC/C,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,kDAAkD;IAClD,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClD,qCAAqC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EACjD,KAAK,CAAC,EAAE,CAAC,GACR,yBAAyB,CA8C3B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QAe1B,CAAC;AAET;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,EACrB,QAAQ,UAAO,GACd,MAAM,CAoBR"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/react.tsx
|
|
2
|
+
import {
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useCallback,
|
|
6
|
+
useState
|
|
7
|
+
} from "react";
|
|
8
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
9
|
+
function DynamicMount({
|
|
10
|
+
module,
|
|
11
|
+
props,
|
|
12
|
+
className,
|
|
13
|
+
style,
|
|
14
|
+
id,
|
|
15
|
+
fallback = null,
|
|
16
|
+
onMount,
|
|
17
|
+
onUnmount,
|
|
18
|
+
onError
|
|
19
|
+
}) {
|
|
20
|
+
const containerRef = useRef(null);
|
|
21
|
+
const cleanupRef = useRef(null);
|
|
22
|
+
const [error, setError] = useState(null);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!module || !containerRef.current)
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
if (cleanupRef.current) {
|
|
28
|
+
cleanupRef.current();
|
|
29
|
+
cleanupRef.current = null;
|
|
30
|
+
onUnmount?.();
|
|
31
|
+
}
|
|
32
|
+
setError(null);
|
|
33
|
+
const cleanup = module.render(containerRef.current, props);
|
|
34
|
+
cleanupRef.current = cleanup ?? null;
|
|
35
|
+
onMount?.();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
38
|
+
setError(error2);
|
|
39
|
+
onError?.(error2);
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
if (cleanupRef.current) {
|
|
43
|
+
cleanupRef.current();
|
|
44
|
+
cleanupRef.current = null;
|
|
45
|
+
onUnmount?.();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}, [module, props, onMount, onUnmount, onError]);
|
|
49
|
+
if (!module) {
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
if (error) {
|
|
53
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
54
|
+
style: {
|
|
55
|
+
color: "red",
|
|
56
|
+
padding: "8px",
|
|
57
|
+
border: "1px solid red",
|
|
58
|
+
borderRadius: "4px"
|
|
59
|
+
},
|
|
60
|
+
children: [
|
|
61
|
+
"Failed to render dynamic component: ",
|
|
62
|
+
error.message
|
|
63
|
+
]
|
|
64
|
+
}, undefined, true, undefined, this);
|
|
65
|
+
}
|
|
66
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
67
|
+
ref: containerRef,
|
|
68
|
+
className,
|
|
69
|
+
style,
|
|
70
|
+
id
|
|
71
|
+
}, undefined, false, undefined, this);
|
|
72
|
+
}
|
|
73
|
+
function useDynamicComponent(module, props) {
|
|
74
|
+
const containerRef = useRef(null);
|
|
75
|
+
const cleanupRef = useRef(null);
|
|
76
|
+
const propsRef = useRef(props);
|
|
77
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
78
|
+
const [error, setError] = useState(null);
|
|
79
|
+
propsRef.current = props;
|
|
80
|
+
const unmount = useCallback(() => {
|
|
81
|
+
if (cleanupRef.current) {
|
|
82
|
+
cleanupRef.current();
|
|
83
|
+
cleanupRef.current = null;
|
|
84
|
+
}
|
|
85
|
+
setIsMounted(false);
|
|
86
|
+
}, []);
|
|
87
|
+
const update = useCallback((newProps) => {
|
|
88
|
+
if (!module || !containerRef.current)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
unmount();
|
|
92
|
+
setError(null);
|
|
93
|
+
const cleanup = module.render(containerRef.current, newProps ?? propsRef.current);
|
|
94
|
+
cleanupRef.current = cleanup ?? null;
|
|
95
|
+
setIsMounted(true);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
98
|
+
}
|
|
99
|
+
}, [module, unmount]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (module && containerRef.current) {
|
|
102
|
+
update(props);
|
|
103
|
+
}
|
|
104
|
+
return unmount;
|
|
105
|
+
}, [module, props, update, unmount]);
|
|
106
|
+
return { containerRef, isMounted, error, update, unmount };
|
|
107
|
+
}
|
|
108
|
+
var REACT_RENDER_TEMPLATE = `
|
|
109
|
+
import React from "react";
|
|
110
|
+
import { createRoot } from "react-dom/client";
|
|
111
|
+
|
|
112
|
+
// Your component here
|
|
113
|
+
function MyComponent(props) {
|
|
114
|
+
return <div>Hello {props.name}</div>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Export a render function that mounts using esm.sh's ReactDOM
|
|
118
|
+
export function render(container, props) {
|
|
119
|
+
const root = createRoot(container);
|
|
120
|
+
root.render(<MyComponent {...props} />);
|
|
121
|
+
return () => root.unmount();
|
|
122
|
+
}
|
|
123
|
+
`.trim();
|
|
124
|
+
function generateRenderFunction(componentName, hasProps = true) {
|
|
125
|
+
if (hasProps) {
|
|
126
|
+
return `
|
|
127
|
+
// Export a render function that mounts using esm.sh's ReactDOM
|
|
128
|
+
export function render(container: HTMLElement, props?: Parameters<typeof ${componentName}>[0]) {
|
|
129
|
+
const root = createRoot(container);
|
|
130
|
+
root.render(<${componentName} {...props} />);
|
|
131
|
+
return () => root.unmount();
|
|
132
|
+
}
|
|
133
|
+
`.trim();
|
|
134
|
+
}
|
|
135
|
+
return `
|
|
136
|
+
// Export a render function that mounts using esm.sh's ReactDOM
|
|
137
|
+
export function render(container: HTMLElement) {
|
|
138
|
+
const root = createRoot(container);
|
|
139
|
+
root.render(<${componentName} />);
|
|
140
|
+
return () => root.unmount();
|
|
141
|
+
}
|
|
142
|
+
`.trim();
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
useDynamicComponent,
|
|
146
|
+
generateRenderFunction,
|
|
147
|
+
REACT_RENDER_TEMPLATE,
|
|
148
|
+
DynamicMount
|
|
149
|
+
};
|