@memberjunction/react-runtime 5.22.0 → 5.24.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.
@@ -4,6 +4,19 @@ export class ReactRootManager {
4
4
  this.roots = new Map();
5
5
  this.renderingRoots = new Set();
6
6
  this.unmountQueue = new Map();
7
+ this.hooks = [];
8
+ this.firstRootCreated = false;
9
+ }
10
+ RegisterHook(hook) {
11
+ this.hooks.push(hook);
12
+ }
13
+ RemoveHook(hookName) {
14
+ const index = this.hooks.findIndex(h => h.name === hookName);
15
+ if (index >= 0) {
16
+ this.hooks.splice(index, 1);
17
+ return true;
18
+ }
19
+ return false;
7
20
  }
8
21
  createRoot(container, createRootFn, componentId) {
9
22
  const rootId = `react-root-${Date.now()}-${Math.random()}`;
@@ -19,6 +32,12 @@ export class ReactRootManager {
19
32
  if (componentId) {
20
33
  resourceManager.registerReactRoot(componentId, root, () => this.unmountRoot(rootId));
21
34
  }
35
+ const hookContext = { rootId, container, componentId };
36
+ if (!this.firstRootCreated) {
37
+ this.firstRootCreated = true;
38
+ this.invokeHooks('OnFirstRootCreated', hookContext);
39
+ }
40
+ this.invokeHooks('OnRootCreated', hookContext);
22
41
  return rootId;
23
42
  }
24
43
  render(rootId, element, onComplete) {
@@ -71,6 +90,7 @@ export class ReactRootManager {
71
90
  delete managedRoot.container._reactRootContainer;
72
91
  managedRoot.container.innerHTML = '';
73
92
  }
93
+ this.invokeHooks('OnRootUnmounted', rootId);
74
94
  resolve();
75
95
  }
76
96
  catch (error) {
@@ -110,8 +130,23 @@ export class ReactRootManager {
110
130
  };
111
131
  }
112
132
  async cleanup() {
133
+ this.invokeHooks('OnCleanup');
113
134
  const allRootIds = Array.from(this.roots.keys());
114
135
  await Promise.all(allRootIds.map(id => this.unmountRoot(id, true)));
136
+ this.firstRootCreated = false;
137
+ }
138
+ invokeHooks(method, arg) {
139
+ for (const hook of this.hooks) {
140
+ const fn = hook[method];
141
+ if (typeof fn === 'function') {
142
+ try {
143
+ fn.call(hook, arg);
144
+ }
145
+ catch (error) {
146
+ console.error(`Error in runtime hook "${hook.name}" during ${method}:`, error);
147
+ }
148
+ }
149
+ }
115
150
  }
116
151
  }
117
152
  export const reactRootManager = new ReactRootManager();
@@ -1 +1 @@
1
- {"version":3,"file":"react-root-manager.js","sourceRoot":"","sources":["../../src/runtime/react-root-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview React root lifecycle management for preventing memory leaks\n * @module @memberjunction/react-runtime/runtime\n */\n\nimport { resourceManager } from '../utilities/resource-manager';\n\nexport interface ManagedReactRoot {\n id: string;\n root: any; // React root instance\n container: HTMLElement;\n isRendering: boolean;\n lastRenderTime?: Date;\n componentId?: string;\n}\n\n/**\n * Manages React root instances to prevent memory leaks and ensure proper cleanup.\n * Handles safe rendering and unmounting with protection against concurrent operations.\n */\nexport class ReactRootManager {\n private roots = new Map<string, ManagedReactRoot>();\n private renderingRoots = new Set<string>();\n private unmountQueue = new Map<string, () => void>();\n \n /**\n * Create a new managed React root\n * @param container - The DOM container element\n * @param createRootFn - Function to create the React root (e.g., ReactDOM.createRoot)\n * @param componentId - Optional component ID for resource tracking\n * @returns The root ID for future operations\n */\n createRoot(\n container: HTMLElement,\n createRootFn: (container: HTMLElement) => any,\n componentId?: string\n ): string {\n const rootId = `react-root-${Date.now()}-${Math.random()}`;\n const root = createRootFn(container);\n \n const managedRoot: ManagedReactRoot = {\n id: rootId,\n root,\n container,\n isRendering: false,\n componentId\n };\n \n this.roots.set(rootId, managedRoot);\n \n // Register with resource manager if component ID provided\n if (componentId) {\n resourceManager.registerReactRoot(\n componentId,\n root,\n () => this.unmountRoot(rootId)\n );\n }\n \n return rootId;\n }\n \n /**\n * Safely render content to a React root\n * @param rootId - The root ID\n * @param element - React element to render\n * @param onComplete - Optional callback when render completes\n */\n render(\n rootId: string,\n element: any,\n onComplete?: () => void\n ): void {\n const managedRoot = this.roots.get(rootId);\n if (!managedRoot) {\n console.warn(`React root ${rootId} not found`);\n return;\n }\n \n // Don't render if we're already unmounting\n if (this.unmountQueue.has(rootId)) {\n console.warn(`React root ${rootId} is being unmounted, skipping render`);\n return;\n }\n \n // Mark as rendering\n managedRoot.isRendering = true;\n this.renderingRoots.add(rootId);\n \n try {\n managedRoot.root.render(element);\n managedRoot.lastRenderTime = new Date();\n \n // Use microtask to ensure React has completed its work\n Promise.resolve().then(() => {\n managedRoot.isRendering = false;\n this.renderingRoots.delete(rootId);\n \n // Process any pending unmount\n const pendingUnmount = this.unmountQueue.get(rootId);\n if (pendingUnmount) {\n this.unmountQueue.delete(rootId);\n pendingUnmount();\n }\n \n if (onComplete) {\n onComplete();\n }\n });\n } catch (error) {\n // Clean up rendering state on error\n managedRoot.isRendering = false;\n this.renderingRoots.delete(rootId);\n throw error;\n }\n }\n \n /**\n * Safely unmount a React root\n * @param rootId - The root ID\n * @param force - Force unmount even if rendering\n */\n unmountRoot(rootId: string, force: boolean = false): Promise<void> {\n return new Promise((resolve) => {\n const managedRoot = this.roots.get(rootId);\n if (!managedRoot) {\n resolve();\n return;\n }\n \n const performUnmount = () => {\n try {\n managedRoot.root.unmount();\n this.roots.delete(rootId);\n this.renderingRoots.delete(rootId);\n \n // Clean up container to ensure no dangling references\n if (managedRoot.container) {\n // Clear React's internal references\n delete (managedRoot.container as any)._reactRootContainer;\n // Clear the container content without replacing the node\n managedRoot.container.innerHTML = '';\n }\n \n resolve();\n } catch (error) {\n console.error(`Error unmounting React root ${rootId}:`, error);\n // Still clean up our tracking even if unmount failed\n this.roots.delete(rootId);\n this.renderingRoots.delete(rootId);\n resolve();\n }\n };\n \n // If not rendering or force unmount, unmount immediately\n if (!managedRoot.isRendering || force) {\n performUnmount();\n } else {\n // Queue unmount for after rendering completes\n this.unmountQueue.set(rootId, () => {\n performUnmount();\n });\n }\n });\n }\n \n /**\n * Unmount all roots associated with a component\n * @param componentId - The component ID\n */\n async unmountComponentRoots(componentId: string): Promise<void> {\n const rootIds: string[] = [];\n \n for (const [rootId, managedRoot] of this.roots) {\n if (managedRoot.componentId === componentId) {\n rootIds.push(rootId);\n }\n }\n \n await Promise.all(rootIds.map(id => this.unmountRoot(id)));\n }\n \n /**\n * Check if a root is currently rendering\n * @param rootId - The root ID\n * @returns true if rendering\n */\n isRendering(rootId: string): boolean {\n return this.renderingRoots.has(rootId);\n }\n \n /**\n * Get statistics about managed roots\n */\n getStats(): {\n totalRoots: number;\n renderingRoots: number;\n pendingUnmounts: number;\n } {\n return {\n totalRoots: this.roots.size,\n renderingRoots: this.renderingRoots.size,\n pendingUnmounts: this.unmountQueue.size\n };\n }\n \n /**\n * Clean up all roots (for testing or shutdown)\n */\n async cleanup(): Promise<void> {\n const allRootIds = Array.from(this.roots.keys());\n await Promise.all(allRootIds.map(id => this.unmountRoot(id, true)));\n }\n}\n\n// Singleton instance\nexport const reactRootManager = new ReactRootManager();"]}
1
+ {"version":3,"file":"react-root-manager.js","sourceRoot":"","sources":["../../src/runtime/react-root-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview React root lifecycle management for preventing memory leaks\n * @module @memberjunction/react-runtime/runtime\n */\n\nimport { resourceManager } from '../utilities/resource-manager';\nimport { RuntimeHook, RootHookContext } from './runtime-hooks';\n\nexport interface ManagedReactRoot {\n id: string;\n root: any; // React root instance\n container: HTMLElement;\n isRendering: boolean;\n lastRenderTime?: Date;\n componentId?: string;\n}\n\n/**\n * Manages React root instances to prevent memory leaks and ensure proper cleanup.\n * Handles safe rendering and unmounting with protection against concurrent operations.\n *\n * Supports a pluggable hook system so host environments (Angular, Vue, etc.) and\n * library integrations can inject behavior at key lifecycle points without coupling\n * the generic runtime to specific libraries or frameworks.\n */\nexport class ReactRootManager {\n private roots = new Map<string, ManagedReactRoot>();\n private renderingRoots = new Set<string>();\n private unmountQueue = new Map<string, () => void>();\n private hooks: RuntimeHook[] = [];\n private firstRootCreated = false;\n\n /**\n * Register a runtime hook to be called at lifecycle points.\n * Hooks execute in registration order.\n * @param hook - The hook to register\n */\n RegisterHook(hook: RuntimeHook): void {\n this.hooks.push(hook);\n }\n\n /**\n * Remove a previously registered hook by name.\n * @param hookName - The name of the hook to remove\n * @returns true if a hook was found and removed\n */\n RemoveHook(hookName: string): boolean {\n const index = this.hooks.findIndex(h => h.name === hookName);\n if (index >= 0) {\n this.hooks.splice(index, 1);\n return true;\n }\n return false;\n }\n \n /**\n * Create a new managed React root\n * @param container - The DOM container element\n * @param createRootFn - Function to create the React root (e.g., ReactDOM.createRoot)\n * @param componentId - Optional component ID for resource tracking\n * @returns The root ID for future operations\n */\n createRoot(\n container: HTMLElement,\n createRootFn: (container: HTMLElement) => any,\n componentId?: string\n ): string {\n const rootId = `react-root-${Date.now()}-${Math.random()}`;\n const root = createRootFn(container);\n \n const managedRoot: ManagedReactRoot = {\n id: rootId,\n root,\n container,\n isRendering: false,\n componentId\n };\n \n this.roots.set(rootId, managedRoot);\n \n // Register with resource manager if component ID provided\n if (componentId) {\n resourceManager.registerReactRoot(\n componentId,\n root,\n () => this.unmountRoot(rootId)\n );\n }\n\n // Invoke hooks\n const hookContext: RootHookContext = { rootId, container, componentId };\n\n if (!this.firstRootCreated) {\n this.firstRootCreated = true;\n this.invokeHooks('OnFirstRootCreated', hookContext);\n }\n\n this.invokeHooks('OnRootCreated', hookContext);\n \n return rootId;\n }\n \n /**\n * Safely render content to a React root\n * @param rootId - The root ID\n * @param element - React element to render\n * @param onComplete - Optional callback when render completes\n */\n render(\n rootId: string,\n element: any,\n onComplete?: () => void\n ): void {\n const managedRoot = this.roots.get(rootId);\n if (!managedRoot) {\n console.warn(`React root ${rootId} not found`);\n return;\n }\n \n // Don't render if we're already unmounting\n if (this.unmountQueue.has(rootId)) {\n console.warn(`React root ${rootId} is being unmounted, skipping render`);\n return;\n }\n \n // Mark as rendering\n managedRoot.isRendering = true;\n this.renderingRoots.add(rootId);\n \n try {\n managedRoot.root.render(element);\n managedRoot.lastRenderTime = new Date();\n \n // Use microtask to ensure React has completed its work\n Promise.resolve().then(() => {\n managedRoot.isRendering = false;\n this.renderingRoots.delete(rootId);\n \n // Process any pending unmount\n const pendingUnmount = this.unmountQueue.get(rootId);\n if (pendingUnmount) {\n this.unmountQueue.delete(rootId);\n pendingUnmount();\n }\n \n if (onComplete) {\n onComplete();\n }\n });\n } catch (error) {\n // Clean up rendering state on error\n managedRoot.isRendering = false;\n this.renderingRoots.delete(rootId);\n throw error;\n }\n }\n \n /**\n * Safely unmount a React root\n * @param rootId - The root ID\n * @param force - Force unmount even if rendering\n */\n unmountRoot(rootId: string, force: boolean = false): Promise<void> {\n return new Promise((resolve) => {\n const managedRoot = this.roots.get(rootId);\n if (!managedRoot) {\n resolve();\n return;\n }\n \n const performUnmount = () => {\n try {\n managedRoot.root.unmount();\n this.roots.delete(rootId);\n this.renderingRoots.delete(rootId);\n \n // Clean up container to ensure no dangling references\n if (managedRoot.container) {\n // Clear React's internal references\n delete (managedRoot.container as any)._reactRootContainer;\n // Clear the container content without replacing the node\n managedRoot.container.innerHTML = '';\n }\n \n this.invokeHooks('OnRootUnmounted', rootId);\n \n resolve();\n } catch (error) {\n console.error(`Error unmounting React root ${rootId}:`, error);\n // Still clean up our tracking even if unmount failed\n this.roots.delete(rootId);\n this.renderingRoots.delete(rootId);\n resolve();\n }\n };\n \n // If not rendering or force unmount, unmount immediately\n if (!managedRoot.isRendering || force) {\n performUnmount();\n } else {\n // Queue unmount for after rendering completes\n this.unmountQueue.set(rootId, () => {\n performUnmount();\n });\n }\n });\n }\n \n /**\n * Unmount all roots associated with a component\n * @param componentId - The component ID\n */\n async unmountComponentRoots(componentId: string): Promise<void> {\n const rootIds: string[] = [];\n \n for (const [rootId, managedRoot] of this.roots) {\n if (managedRoot.componentId === componentId) {\n rootIds.push(rootId);\n }\n }\n \n await Promise.all(rootIds.map(id => this.unmountRoot(id)));\n }\n \n /**\n * Check if a root is currently rendering\n * @param rootId - The root ID\n * @returns true if rendering\n */\n isRendering(rootId: string): boolean {\n return this.renderingRoots.has(rootId);\n }\n \n /**\n * Get statistics about managed roots\n */\n getStats(): {\n totalRoots: number;\n renderingRoots: number;\n pendingUnmounts: number;\n } {\n return {\n totalRoots: this.roots.size,\n renderingRoots: this.renderingRoots.size,\n pendingUnmounts: this.unmountQueue.size\n };\n }\n\n /**\n * Clean up all roots (for testing or shutdown)\n */\n async cleanup(): Promise<void> {\n // Invoke cleanup hooks before unmounting roots\n this.invokeHooks('OnCleanup');\n\n const allRootIds = Array.from(this.roots.keys());\n await Promise.all(allRootIds.map(id => this.unmountRoot(id, true)));\n\n // Reset state so hooks fire again if runtime is re-initialized\n this.firstRootCreated = false;\n }\n\n /**\n * Safely invoke a lifecycle method on all registered hooks.\n * Errors in individual hooks are caught and logged so one\n * misbehaving hook cannot break the runtime.\n */\n private invokeHooks(method: keyof RuntimeHook, arg?: RootHookContext | string): void {\n for (const hook of this.hooks) {\n const fn = hook[method];\n if (typeof fn === 'function') {\n try {\n (fn as Function).call(hook, arg);\n } catch (error) {\n console.error(`Error in runtime hook \"${hook.name}\" during ${method}:`, error);\n }\n }\n }\n }\n}\n\n// Singleton instance\nexport const reactRootManager = new ReactRootManager();\n"]}
@@ -0,0 +1,13 @@
1
+ export interface RootHookContext {
2
+ rootId: string;
3
+ container: HTMLElement;
4
+ componentId?: string;
5
+ }
6
+ export interface RuntimeHook {
7
+ name: string;
8
+ OnFirstRootCreated?: (context: RootHookContext) => void;
9
+ OnRootCreated?: (context: RootHookContext) => void;
10
+ OnRootUnmounted?: (rootId: string) => void;
11
+ OnCleanup?: () => void;
12
+ }
13
+ //# sourceMappingURL=runtime-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-hooks.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime-hooks.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,eAAe;IAE9B,MAAM,EAAE,MAAM,CAAC;IAEf,SAAS,EAAE,WAAW,CAAC;IAEvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD,MAAM,WAAW,WAAW;IAE1B,IAAI,EAAE,MAAM,CAAC;IAOb,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAKxD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAKnD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAO3C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-hooks.js","sourceRoot":"","sources":["../../src/runtime/runtime-hooks.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @fileoverview Pluggable runtime hook system for ReactRootManager.\n * Allows host environments and library integrations to inject behavior\n * at key lifecycle points without coupling the generic runtime to\n * specific libraries or frameworks.\n * @module @memberjunction/react-runtime/runtime\n */\n\n/**\n * Context provided to hook callbacks, giving hooks access to\n * the root and container information they need.\n */\nexport interface RootHookContext {\n /** The root ID assigned by ReactRootManager */\n rootId: string;\n /** The DOM container element for this root */\n container: HTMLElement;\n /** Optional component ID for resource tracking */\n componentId?: string;\n}\n\n/**\n * A runtime hook that can be registered to execute at key lifecycle points\n * in the React root manager. All methods are optional — implement only\n * the lifecycle points you need.\n */\nexport interface RuntimeHook {\n /** Human-readable name for debugging/logging */\n name: string;\n\n /**\n * Called once, when the very first React root is created.\n * Use this for one-time global setup (e.g., injecting global styles,\n * creating MutationObservers, etc.).\n */\n OnFirstRootCreated?: (context: RootHookContext) => void;\n\n /**\n * Called after every root is created (including the first).\n */\n OnRootCreated?: (context: RootHookContext) => void;\n\n /**\n * Called after a root is unmounted.\n */\n OnRootUnmounted?: (rootId: string) => void;\n\n /**\n * Called during runtime shutdown (ReactRootManager.cleanup()).\n * Use this to disconnect observers, remove injected DOM elements,\n * clear timers, etc.\n */\n OnCleanup?: () => void;\n}\n"]}