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.
Files changed (47) hide show
  1. package/README.md +616 -0
  2. package/dist/bundler.d.ts +148 -0
  3. package/dist/bundler.d.ts.map +1 -0
  4. package/dist/commands.d.ts +179 -0
  5. package/dist/commands.d.ts.map +1 -0
  6. package/dist/fs.d.ts +125 -0
  7. package/dist/fs.d.ts.map +1 -0
  8. package/dist/index.d.ts +16 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +2920 -0
  11. package/dist/internal.d.ts +74 -0
  12. package/dist/internal.d.ts.map +1 -0
  13. package/dist/internal.js +1897 -0
  14. package/dist/loader.d.ts +164 -0
  15. package/dist/loader.d.ts.map +1 -0
  16. package/dist/packages.d.ts +199 -0
  17. package/dist/packages.d.ts.map +1 -0
  18. package/dist/react.d.ts +159 -0
  19. package/dist/react.d.ts.map +1 -0
  20. package/dist/react.js +149 -0
  21. package/dist/sandbox-manager.d.ts +249 -0
  22. package/dist/sandbox-manager.d.ts.map +1 -0
  23. package/dist/sandbox.d.ts +193 -0
  24. package/dist/sandbox.d.ts.map +1 -0
  25. package/dist/shared-modules.d.ts +129 -0
  26. package/dist/shared-modules.d.ts.map +1 -0
  27. package/dist/shared-resources.d.ts +105 -0
  28. package/dist/shared-resources.d.ts.map +1 -0
  29. package/dist/ts-libs.d.ts +98 -0
  30. package/dist/ts-libs.d.ts.map +1 -0
  31. package/dist/typechecker.d.ts +127 -0
  32. package/dist/typechecker.d.ts.map +1 -0
  33. package/package.json +64 -0
  34. package/src/bundler.ts +513 -0
  35. package/src/commands.ts +733 -0
  36. package/src/fs.ts +935 -0
  37. package/src/index.ts +149 -0
  38. package/src/internal.ts +116 -0
  39. package/src/loader.ts +229 -0
  40. package/src/packages.ts +936 -0
  41. package/src/react.tsx +331 -0
  42. package/src/sandbox-manager.ts +490 -0
  43. package/src/sandbox.ts +402 -0
  44. package/src/shared-modules.ts +210 -0
  45. package/src/shared-resources.ts +169 -0
  46. package/src/ts-libs.ts +320 -0
  47. package/src/typechecker.ts +635 -0
@@ -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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"}
@@ -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
+ };