@memberjunction/react-runtime 4.0.0 → 4.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/.turbo/turbo-build.log +31 -33
- package/CHANGELOG.md +13 -0
- package/README.md +142 -691
- package/dist/324.runtime.umd.js +3 -3
- package/dist/runtime.umd.js +1 -1
- package/package.json +6 -6
- package/dist/runtime/component-wrapper.d.ts +0 -18
- package/dist/runtime/component-wrapper.d.ts.map +0 -1
- package/dist/runtime/component-wrapper.js +0 -108
- package/dist/utilities/runtime-utilities.d.ts +0 -10
- package/dist/utilities/runtime-utilities.d.ts.map +0 -1
- package/dist/utilities/runtime-utilities.js +0 -92
- package/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -1,21 +1,70 @@
|
|
|
1
1
|
# @memberjunction/react-runtime
|
|
2
2
|
|
|
3
|
-
Platform-agnostic React component runtime for MemberJunction.
|
|
3
|
+
Platform-agnostic React component runtime for MemberJunction. Provides core compilation, registry, dynamic library management, and execution capabilities for React components in any JavaScript environment.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
subgraph "@memberjunction/react-runtime"
|
|
10
|
+
A[createReactRuntime] --> B[ComponentCompiler]
|
|
11
|
+
A --> C[ComponentRegistry]
|
|
12
|
+
A --> D[ComponentResolver]
|
|
13
|
+
A --> E[ComponentManager]
|
|
14
|
+
|
|
15
|
+
B --> F["Babel Standalone<br/>(JSX Compilation)"]
|
|
16
|
+
|
|
17
|
+
C --> G[Component Store]
|
|
18
|
+
C --> H[Namespace Support]
|
|
19
|
+
|
|
20
|
+
D --> I[Component Resolution]
|
|
21
|
+
D --> J[Dependency Resolution]
|
|
22
|
+
|
|
23
|
+
E --> K[Load + Compile + Register]
|
|
24
|
+
|
|
25
|
+
subgraph "Utilities"
|
|
26
|
+
L[LibraryLoader]
|
|
27
|
+
M[LibraryRegistry]
|
|
28
|
+
N[CacheManager]
|
|
29
|
+
O[ResourceManager]
|
|
30
|
+
P[ErrorBoundary]
|
|
31
|
+
Q[PropBuilder]
|
|
32
|
+
R[ReactRootManager]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
subgraph "Runtime APIs"
|
|
37
|
+
S[ComponentHierarchyRegistrar]
|
|
38
|
+
T[StandardLibraries]
|
|
39
|
+
U[ComponentErrorAnalyzer]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
43
|
+
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
44
|
+
style C fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
45
|
+
style D fill:#7c5295,stroke:#563a6b,color:#fff
|
|
46
|
+
style E fill:#7c5295,stroke:#563a6b,color:#fff
|
|
47
|
+
style L fill:#b8762f,stroke:#8a5722,color:#fff
|
|
48
|
+
style N fill:#b8762f,stroke:#8a5722,color:#fff
|
|
49
|
+
style P fill:#b8762f,stroke:#8a5722,color:#fff
|
|
50
|
+
```
|
|
4
51
|
|
|
5
52
|
## Overview
|
|
6
53
|
|
|
7
|
-
|
|
54
|
+
This package enables dynamic compilation and execution of React components at runtime. Components can be loaded from MemberJunction's database, compiled with Babel, registered into a component registry, and rendered with full dependency resolution.
|
|
8
55
|
|
|
9
|
-
|
|
56
|
+
**Key capabilities:**
|
|
10
57
|
|
|
11
|
-
- **
|
|
12
|
-
- **Component Registry**:
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
58
|
+
- **Component Compilation**: Transpiles JSX/TSX source code using Babel standalone
|
|
59
|
+
- **Component Registry**: LRU-cached registry with namespace support for up to 1000 components
|
|
60
|
+
- **Component Resolution**: Resolves component specs against the registry with dependency tracking
|
|
61
|
+
- **Component Manager**: Unified API for loading, compiling, and registering components in one call
|
|
62
|
+
- **Library Management**: Dynamic library loading, registration, and dependency resolution
|
|
63
|
+
- **Error Boundaries**: Configurable error boundary creation with logging
|
|
64
|
+
- **Prop Building**: Type-safe prop construction with callback normalization and style processing
|
|
65
|
+
- **React Root Management**: Managed React root lifecycle for mounting/unmounting components
|
|
66
|
+
- **Caching**: LRU cache with configurable TTL for compiled components
|
|
67
|
+
- **UMD Build**: Ships both CommonJS and UMD bundles for browser environments
|
|
19
68
|
|
|
20
69
|
## Installation
|
|
21
70
|
|
|
@@ -23,737 +72,139 @@ The React Runtime package enables dynamic compilation and execution of React com
|
|
|
23
72
|
npm install @memberjunction/react-runtime
|
|
24
73
|
```
|
|
25
74
|
|
|
26
|
-
##
|
|
75
|
+
## Usage
|
|
27
76
|
|
|
28
|
-
###
|
|
77
|
+
### Quick Start
|
|
29
78
|
|
|
30
79
|
```typescript
|
|
31
80
|
import { createReactRuntime } from '@memberjunction/react-runtime';
|
|
32
81
|
import * as Babel from '@babel/standalone';
|
|
33
82
|
|
|
34
|
-
// Create runtime with Babel instance
|
|
35
|
-
const runtime = createReactRuntime(Babel);
|
|
36
|
-
|
|
37
|
-
// The runtime now includes the unified ComponentManager
|
|
38
|
-
const { compiler, registry, resolver, manager } = runtime;
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## NEW: Unified ComponentManager (Recommended)
|
|
42
|
-
|
|
43
|
-
The ComponentManager is a new unified API that simplifies component loading by handling fetching, compilation, registration, and caching in a single, efficient operation. It eliminates duplicate work and provides better performance.
|
|
44
|
-
|
|
45
|
-
### Why Use ComponentManager?
|
|
46
|
-
|
|
47
|
-
- **Single API**: One method handles everything - no need to coordinate multiple components
|
|
48
|
-
- **Efficient**: Automatically prevents duplicate fetching and compilation
|
|
49
|
-
- **Smart Caching**: Multi-level caching with automatic invalidation
|
|
50
|
-
- **Registry Tracking**: Built-in usage tracking for licensing compliance
|
|
51
|
-
- **Better Error Handling**: Comprehensive error reporting with phases
|
|
52
|
-
|
|
53
|
-
### Loading a Component Hierarchy
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import { ComponentSpec } from '@memberjunction/interactive-component-types';
|
|
57
|
-
|
|
58
|
-
const componentSpec: ComponentSpec = {
|
|
59
|
-
name: 'Dashboard',
|
|
60
|
-
location: 'registry',
|
|
61
|
-
registry: 'SkipAI',
|
|
62
|
-
namespace: 'analytics',
|
|
63
|
-
version: '1.0.0',
|
|
64
|
-
dependencies: [
|
|
65
|
-
{ name: 'Chart', location: 'registry', registry: 'SkipAI' },
|
|
66
|
-
{ name: 'Grid', location: 'embedded', code: '...' }
|
|
67
|
-
]
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Load the entire hierarchy with one call
|
|
71
|
-
const result = await runtime.manager.loadHierarchy(componentSpec, {
|
|
72
|
-
contextUser: currentUser,
|
|
73
|
-
defaultNamespace: 'Global',
|
|
74
|
-
defaultVersion: 'latest',
|
|
75
|
-
returnType: 'both'
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (result.success) {
|
|
79
|
-
// Everything is loaded and ready
|
|
80
|
-
const rootComponent = result.rootComponent;
|
|
81
|
-
const resolvedSpec = result.resolvedSpec;
|
|
82
|
-
|
|
83
|
-
console.log(`Loaded ${result.loadedComponents.length} components`);
|
|
84
|
-
console.log(`Stats:`, result.stats);
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Loading a Single Component
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
// For simple single component loading
|
|
92
|
-
const result = await runtime.manager.loadComponent(componentSpec, {
|
|
93
|
-
contextUser: currentUser,
|
|
94
|
-
forceRefresh: false, // Use cache if available
|
|
95
|
-
trackUsage: true // Track usage for licensing
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (result.success) {
|
|
99
|
-
const component = result.component;
|
|
100
|
-
const wasFromCache = result.fromCache;
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Configuration Options
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
83
|
const runtime = createReactRuntime(Babel, {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
maxCacheSize: 100, // Max cached specs
|
|
111
|
-
cacheTTL: 3600000, // 1 hour cache TTL
|
|
112
|
-
enableUsageTracking: true, // Track registry usage
|
|
113
|
-
dependencyBatchSize: 5, // Parallel dependency loading
|
|
114
|
-
fetchTimeout: 30000 // 30 second timeout
|
|
115
|
-
}
|
|
84
|
+
compiler: { minify: false, sourceMaps: true, cache: true },
|
|
85
|
+
registry: { maxComponents: 500 }
|
|
116
86
|
});
|
|
117
|
-
```
|
|
118
87
|
|
|
119
|
-
|
|
88
|
+
// Compile a component from source
|
|
89
|
+
const compiled = runtime.compiler.compile(`
|
|
90
|
+
function MyComponent({ name }) {
|
|
91
|
+
return <div>Hello, {name}!</div>;
|
|
92
|
+
}
|
|
93
|
+
`);
|
|
120
94
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
runtime.manager.clearCache();
|
|
95
|
+
// Register it
|
|
96
|
+
runtime.registry.register('MyComponent', compiled);
|
|
124
97
|
|
|
125
|
-
//
|
|
126
|
-
const
|
|
127
|
-
console.log(`Cached specs: ${stats.fetchCacheSize}`);
|
|
128
|
-
console.log(`Usage notifications: ${stats.notificationsCount}`);
|
|
98
|
+
// Resolve for rendering
|
|
99
|
+
const resolved = runtime.resolver.resolve({ name: 'MyComponent' });
|
|
129
100
|
```
|
|
130
101
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
### Compiling a Component (Old Way)
|
|
102
|
+
### Component Manager (Unified API)
|
|
134
103
|
|
|
135
104
|
```typescript
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<h1>Hello, {data.name}!</h1>
|
|
141
|
-
<button onClick={() => callbacks.RefreshData()}>
|
|
142
|
-
Refresh
|
|
143
|
-
</button>
|
|
144
|
-
</div>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
`;
|
|
148
|
-
|
|
149
|
-
// Compile the component
|
|
150
|
-
const result = await runtime.compiler.compile({
|
|
151
|
-
componentName: 'MyComponent',
|
|
152
|
-
componentCode: componentCode
|
|
105
|
+
const result = await runtime.manager.load({
|
|
106
|
+
componentSpec: { name: 'MyWidget', source: jsxSource },
|
|
107
|
+
autoRegister: true,
|
|
108
|
+
resolveLibraries: true
|
|
153
109
|
});
|
|
154
110
|
|
|
155
111
|
if (result.success) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
'MyComponent',
|
|
159
|
-
result.component.component,
|
|
160
|
-
'MyNamespace',
|
|
161
|
-
'v1'
|
|
162
|
-
);
|
|
112
|
+
// Component is compiled, registered, and ready to render
|
|
113
|
+
const Component = result.component;
|
|
163
114
|
}
|
|
164
115
|
```
|
|
165
116
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
### Loading Libraries with Configuration
|
|
117
|
+
### Library Management
|
|
169
118
|
|
|
170
119
|
```typescript
|
|
171
|
-
import {
|
|
172
|
-
|
|
173
|
-
// Define custom library configuration
|
|
174
|
-
const libraryConfig = {
|
|
175
|
-
libraries: [
|
|
176
|
-
{
|
|
177
|
-
id: 'lodash',
|
|
178
|
-
name: 'lodash',
|
|
179
|
-
displayName: 'Lodash',
|
|
180
|
-
category: 'utility',
|
|
181
|
-
globalVariable: '_',
|
|
182
|
-
version: '4.17.21',
|
|
183
|
-
cdnUrl: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
|
|
184
|
-
description: 'Utility library',
|
|
185
|
-
isEnabled: true,
|
|
186
|
-
isCore: false
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
id: 'chart-js',
|
|
190
|
-
name: 'Chart',
|
|
191
|
-
displayName: 'Chart.js',
|
|
192
|
-
category: 'charting',
|
|
193
|
-
globalVariable: 'Chart',
|
|
194
|
-
version: '4.4.0',
|
|
195
|
-
cdnUrl: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.js',
|
|
196
|
-
isEnabled: true,
|
|
197
|
-
isCore: false
|
|
198
|
-
}
|
|
199
|
-
// ... more libraries
|
|
200
|
-
],
|
|
201
|
-
metadata: {
|
|
202
|
-
version: '1.0.0',
|
|
203
|
-
lastUpdated: '2024-01-01'
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Load libraries with custom configuration
|
|
208
|
-
const result = await LibraryLoader.loadAllLibraries(libraryConfig);
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Managing Library Configurations
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { StandardLibraryManager } from '@memberjunction/react-runtime';
|
|
120
|
+
import { LibraryRegistry, LibraryLoader } from '@memberjunction/react-runtime';
|
|
215
121
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// Get libraries by category
|
|
223
|
-
const chartingLibs = StandardLibraryManager.getLibrariesByCategory('charting');
|
|
224
|
-
const uiLibs = StandardLibraryManager.getLibrariesByCategory('ui');
|
|
225
|
-
|
|
226
|
-
// Get component libraries (excludes runtime-only libraries)
|
|
227
|
-
const componentLibs = StandardLibraryManager.getComponentLibraries();
|
|
228
|
-
|
|
229
|
-
// Reset to default configuration
|
|
230
|
-
StandardLibraryManager.resetToDefault();
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Library Categories
|
|
234
|
-
|
|
235
|
-
Libraries are organized into categories:
|
|
236
|
-
- **`runtime`**: Core runtime libraries (React, ReactDOM, Babel) - not exposed to components
|
|
237
|
-
- **`ui`**: UI component libraries (Ant Design, React Bootstrap)
|
|
238
|
-
- **`charting`**: Data visualization libraries (Chart.js, D3.js)
|
|
239
|
-
- **`utility`**: Utility libraries (Lodash, Day.js)
|
|
240
|
-
|
|
241
|
-
### Runtime-Only Libraries
|
|
242
|
-
|
|
243
|
-
Libraries marked with `isRuntimeOnly: true` are used by the runtime infrastructure but not exposed to generated components. This includes React, ReactDOM, and Babel.
|
|
244
|
-
|
|
245
|
-
### Using the Component
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
// Get React context (provided by your environment)
|
|
249
|
-
const React = window.React; // or require('react')
|
|
250
|
-
const ReactDOM = window.ReactDOM; // or require('react-dom')
|
|
251
|
-
|
|
252
|
-
// Create runtime context with loaded libraries
|
|
253
|
-
const context = {
|
|
254
|
-
React,
|
|
255
|
-
ReactDOM,
|
|
256
|
-
libraries: result.libraries // From LibraryLoader
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
// Get the compiled component
|
|
260
|
-
const MyComponent = runtime.registry.get('MyComponent', 'MyNamespace');
|
|
261
|
-
|
|
262
|
-
// Execute the component factory
|
|
263
|
-
const componentObject = MyComponent(context);
|
|
264
|
-
|
|
265
|
-
// The componentObject contains the React component and method accessors
|
|
266
|
-
const { component, print, refresh, getCurrentDataState, isDirty } = componentObject;
|
|
267
|
-
|
|
268
|
-
// Render with props
|
|
269
|
-
const props = {
|
|
270
|
-
data: { name: 'World' },
|
|
271
|
-
userState: {},
|
|
272
|
-
callbacks: {
|
|
273
|
-
OpenEntityRecord: (entityName, key) => console.log('Open entity:', entityName),
|
|
274
|
-
RegisterMethod: (methodName, handler) => {
|
|
275
|
-
// Component will register its methods here
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
React.createElement(component, props);
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Component Methods System
|
|
284
|
-
|
|
285
|
-
### Overview
|
|
286
|
-
|
|
287
|
-
Components can expose methods that allow containers to interact with them beyond just passing props. This enables scenarios like:
|
|
288
|
-
- AI agents introspecting component state
|
|
289
|
-
- Containers checking if components have unsaved changes
|
|
290
|
-
- Programmatic validation and reset operations
|
|
291
|
-
- Custom business logic exposed by components
|
|
292
|
-
|
|
293
|
-
### How Components Register Methods
|
|
294
|
-
|
|
295
|
-
Components register their methods during initialization using the `RegisterMethod` callback:
|
|
296
|
-
|
|
297
|
-
```typescript
|
|
298
|
-
function MyComponent({ callbacks, data, userState }) {
|
|
299
|
-
const [currentData, setCurrentData] = React.useState(data);
|
|
300
|
-
const [isDirty, setIsDirty] = React.useState(false);
|
|
301
|
-
|
|
302
|
-
// Register methods on mount
|
|
303
|
-
React.useEffect(() => {
|
|
304
|
-
if (callbacks?.RegisterMethod) {
|
|
305
|
-
// Register standard methods
|
|
306
|
-
callbacks.RegisterMethod('getCurrentDataState', () => {
|
|
307
|
-
return currentData;
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
callbacks.RegisterMethod('isDirty', () => {
|
|
311
|
-
return isDirty;
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
callbacks.RegisterMethod('reset', () => {
|
|
315
|
-
setCurrentData(data);
|
|
316
|
-
setIsDirty(false);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
callbacks.RegisterMethod('validate', () => {
|
|
320
|
-
// Custom validation logic
|
|
321
|
-
if (!currentData.name) {
|
|
322
|
-
return { valid: false, errors: ['Name is required'] };
|
|
323
|
-
}
|
|
324
|
-
return true;
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Register custom methods
|
|
328
|
-
callbacks.RegisterMethod('exportToCSV', () => {
|
|
329
|
-
// Custom export logic
|
|
330
|
-
return convertToCSV(currentData);
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}, [callbacks, currentData, isDirty]);
|
|
334
|
-
|
|
335
|
-
return (
|
|
336
|
-
<div>
|
|
337
|
-
{/* Component UI */}
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Standard Methods
|
|
344
|
-
|
|
345
|
-
The ComponentObject interface defines standard methods that components can optionally implement:
|
|
346
|
-
|
|
347
|
-
- **`getCurrentDataState()`**: Returns the current data being displayed
|
|
348
|
-
- **`getDataStateHistory()`**: Returns an array of timestamped state changes
|
|
349
|
-
- **`validate()`**: Validates the component state
|
|
350
|
-
- **`isDirty()`**: Checks if there are unsaved changes
|
|
351
|
-
- **`reset()`**: Resets the component to initial state
|
|
352
|
-
- **`scrollTo(target)`**: Scrolls to a specific element
|
|
353
|
-
- **`focus(target)`**: Sets focus to an element
|
|
354
|
-
- **`print()`**: Prints the component content
|
|
355
|
-
- **`refresh()`**: Refreshes the component data
|
|
356
|
-
|
|
357
|
-
### Using Component Methods
|
|
358
|
-
|
|
359
|
-
After compilation, the ComponentObject provides typed access to standard methods:
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
// Compile the component
|
|
363
|
-
const result = await compiler.compile({
|
|
364
|
-
componentName: 'MyComponent',
|
|
365
|
-
componentCode: componentCode
|
|
122
|
+
// Register a library for dependency resolution
|
|
123
|
+
LibraryRegistry.register({
|
|
124
|
+
name: 'recharts',
|
|
125
|
+
module: rechartsModule,
|
|
126
|
+
version: '2.x'
|
|
366
127
|
});
|
|
367
128
|
|
|
368
|
-
//
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
// Call standard methods directly (type-safe)
|
|
372
|
-
const currentData = componentObject.getCurrentDataState();
|
|
373
|
-
const isDirty = componentObject.isDirty();
|
|
374
|
-
const validationResult = componentObject.validate();
|
|
375
|
-
|
|
376
|
-
if (isDirty) {
|
|
377
|
-
componentObject.reset();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Call custom methods via invokeMethod
|
|
381
|
-
if (componentObject.hasMethod('exportToCSV')) {
|
|
382
|
-
const csvData = componentObject.invokeMethod('exportToCSV');
|
|
383
|
-
}
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### Method Availability
|
|
387
|
-
|
|
388
|
-
All methods are optional. The runtime provides sensible defaults when methods aren't registered:
|
|
389
|
-
|
|
390
|
-
- `getCurrentDataState()` returns `undefined`
|
|
391
|
-
- `getDataStateHistory()` returns `[]`
|
|
392
|
-
- `isDirty()` returns `false`
|
|
393
|
-
- `validate()` returns `true`
|
|
394
|
-
- Other methods perform no operation if not implemented
|
|
395
|
-
|
|
396
|
-
### Integration with Angular
|
|
397
|
-
|
|
398
|
-
The Angular wrapper (`@memberjunction/ng-react`) provides strongly-typed access to all standard methods:
|
|
399
|
-
|
|
400
|
-
```typescript
|
|
401
|
-
export class MyDashboard {
|
|
402
|
-
@ViewChild(MJReactComponent) reactComponent!: MJReactComponent;
|
|
403
|
-
|
|
404
|
-
checkComponentState() {
|
|
405
|
-
// Standard methods have full TypeScript support
|
|
406
|
-
if (this.reactComponent.isDirty()) {
|
|
407
|
-
const data = this.reactComponent.getCurrentDataState();
|
|
408
|
-
console.log('Component has unsaved changes:', data);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Validate before saving
|
|
412
|
-
const validation = this.reactComponent.validate();
|
|
413
|
-
if (validation === true || validation.valid) {
|
|
414
|
-
// Save data...
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Custom methods
|
|
418
|
-
if (this.reactComponent.hasMethod('generateReport')) {
|
|
419
|
-
const report = this.reactComponent.invokeMethod('generateReport', options);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Method Declaration in Component Spec
|
|
426
|
-
|
|
427
|
-
Components can declare their supported methods in the ComponentSpec for discovery:
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
const componentSpec = {
|
|
431
|
-
name: 'MyComponent',
|
|
432
|
-
code: '...',
|
|
433
|
-
methods: [
|
|
434
|
-
{
|
|
435
|
-
name: 'getCurrentDataState',
|
|
436
|
-
category: 'standard',
|
|
437
|
-
description: 'Returns current component data',
|
|
438
|
-
returnType: 'DataState | undefined'
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: 'exportToExcel',
|
|
442
|
-
category: 'custom',
|
|
443
|
-
description: 'Exports data to Excel format',
|
|
444
|
-
parameters: [{
|
|
445
|
-
name: 'options',
|
|
446
|
-
type: '{includeHeaders?: boolean, sheetName?: string}',
|
|
447
|
-
required: false
|
|
448
|
-
}],
|
|
449
|
-
returnType: 'Promise<Blob>'
|
|
450
|
-
}
|
|
451
|
-
]
|
|
452
|
-
};
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
## Advanced Features
|
|
456
|
-
|
|
457
|
-
### Component Hierarchies
|
|
458
|
-
|
|
459
|
-
```typescript
|
|
460
|
-
const parentSpec = {
|
|
461
|
-
componentName: 'ParentComponent',
|
|
462
|
-
componentCode: '...',
|
|
463
|
-
childComponents: [
|
|
464
|
-
{
|
|
465
|
-
componentName: 'ChildComponent1',
|
|
466
|
-
componentCode: '...'
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
componentName: 'ChildComponent2',
|
|
470
|
-
componentCode: '...'
|
|
471
|
-
}
|
|
472
|
-
]
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
// Resolve all components in hierarchy
|
|
476
|
-
const components = runtime.resolver.resolveComponents(parentSpec);
|
|
129
|
+
// Load libraries dynamically
|
|
130
|
+
const loader = new LibraryLoader();
|
|
131
|
+
const result = await loader.load({ name: 'recharts', url: cdnUrl });
|
|
477
132
|
```
|
|
478
133
|
|
|
479
134
|
### Error Boundaries
|
|
480
135
|
|
|
481
136
|
```typescript
|
|
482
|
-
import { createErrorBoundary } from '@memberjunction/react-runtime';
|
|
483
|
-
|
|
484
|
-
const ErrorBoundary = createErrorBoundary(React, {
|
|
485
|
-
onError: (error, errorInfo) => {
|
|
486
|
-
console.error('Component error:', error);
|
|
487
|
-
},
|
|
488
|
-
fallback: <div>Something went wrong</div>,
|
|
489
|
-
recovery: 'retry'
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// Wrap your component
|
|
493
|
-
<ErrorBoundary>
|
|
494
|
-
<YourComponent />
|
|
495
|
-
</ErrorBoundary>
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
### Component Registry Management
|
|
499
|
-
|
|
500
|
-
```typescript
|
|
501
|
-
// Check if component exists
|
|
502
|
-
if (runtime.registry.has('MyComponent')) {
|
|
503
|
-
// Get component with reference counting
|
|
504
|
-
const component = runtime.registry.get('MyComponent');
|
|
505
|
-
|
|
506
|
-
// Release when done
|
|
507
|
-
runtime.registry.release('MyComponent');
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Get registry statistics
|
|
511
|
-
const stats = runtime.registry.getStats();
|
|
512
|
-
console.log(`Total components: ${stats.totalComponents}`);
|
|
513
|
-
|
|
514
|
-
// Clean up unused components
|
|
515
|
-
const removed = runtime.registry.cleanup();
|
|
516
|
-
console.log(`Removed ${removed} unused components`);
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### External Registry Components
|
|
520
|
-
|
|
521
|
-
The React Runtime supports loading components from external registries through the `ComponentRegistryService`:
|
|
522
|
-
|
|
523
|
-
```typescript
|
|
524
|
-
// Component specs can reference external registries
|
|
525
|
-
const componentSpec = {
|
|
526
|
-
name: 'DataGrid',
|
|
527
|
-
location: 'registry',
|
|
528
|
-
registry: 'MJ', // Registry name (globally unique)
|
|
529
|
-
namespace: 'core/ui',
|
|
530
|
-
version: 'latest',
|
|
531
|
-
// ... other spec fields
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
// The runtime will:
|
|
535
|
-
// 1. Look up the registry by name in ComponentRegistries
|
|
536
|
-
// 2. Fetch the component via GraphQL/MJServer
|
|
537
|
-
// 3. Calculate SHA-256 hash of the spec for cache validation
|
|
538
|
-
// 4. Compile and cache the component
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
#### GraphQL Client Configuration
|
|
542
|
-
|
|
543
|
-
The `ComponentRegistryService` requires a GraphQL client for fetching from external registries. It supports two configuration approaches:
|
|
544
|
-
|
|
545
|
-
1. **Automatic Fallback** (Recommended): If no client is explicitly provided, the service automatically creates a `GraphQLComponentRegistryClient` using `Metadata.Provider`
|
|
546
|
-
```typescript
|
|
547
|
-
// No explicit client needed - will create one from Metadata.Provider
|
|
548
|
-
const registryService = ComponentRegistryService.getInstance(compiler, context);
|
|
549
|
-
// The service will automatically:
|
|
550
|
-
// 1. Check if a client was provided
|
|
551
|
-
// 2. If not, dynamically import @memberjunction/graphql-dataprovider
|
|
552
|
-
// 3. Create a GraphQLComponentRegistryClient with Metadata.Provider
|
|
553
|
-
// 4. Cache and reuse this client for subsequent calls
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
2. **Explicit Client**: Provide a custom GraphQL client that implements `IComponentRegistryClient`
|
|
557
|
-
```typescript
|
|
558
|
-
// Custom client implementation
|
|
559
|
-
const customClient: IComponentRegistryClient = {
|
|
560
|
-
GetRegistryComponent: async (params) => { /* ... */ }
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
// Pass during creation
|
|
564
|
-
const registryService = ComponentRegistryService.getInstance(
|
|
565
|
-
compiler, context, debug, customClient
|
|
566
|
-
);
|
|
567
|
-
|
|
568
|
-
// Or set later
|
|
569
|
-
registryService.setGraphQLClient(customClient);
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
The automatic fallback ensures external registry fetching works out-of-the-box in MemberJunction environments where `Metadata.Provider` is configured. The dynamic import approach allows the React runtime to function even when `@memberjunction/graphql-dataprovider` is not available.
|
|
573
|
-
|
|
574
|
-
#### Component Caching with SHA-256 Validation
|
|
575
|
-
|
|
576
|
-
The runtime uses SHA-256 hashing to ensure cached components are up-to-date:
|
|
577
|
-
|
|
578
|
-
```typescript
|
|
579
|
-
// When fetching external components:
|
|
580
|
-
// 1. Fetch spec from registry
|
|
581
|
-
// 2. Calculate SHA-256 hash using Web Crypto API
|
|
582
|
-
// 3. Compare with cached component's hash
|
|
583
|
-
// 4. Recompile only if spec has changed
|
|
584
|
-
|
|
585
|
-
// Note: Requires secure context (HTTPS or localhost)
|
|
586
|
-
// Web Crypto API is used for consistent hashing across environments
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
#### Registry Types
|
|
590
|
-
|
|
591
|
-
- **Local Registry** (`registry` field undefined): Components stored in local database
|
|
592
|
-
- **External Registry** (`registry` field defined): Components fetched from remote registries via MJServer
|
|
593
|
-
|
|
594
|
-
## Debug Configuration
|
|
595
|
-
|
|
596
|
-
The React runtime includes comprehensive debug logging that can be controlled via environment configuration. This is useful for troubleshooting component loading, compilation, and runtime issues.
|
|
597
|
-
|
|
598
|
-
### Enabling Debug Mode
|
|
599
|
-
|
|
600
|
-
Debug mode controls verbose console logging throughout the React runtime. When enabled, you'll see detailed information about:
|
|
601
|
-
|
|
602
|
-
- Component compilation and registration
|
|
603
|
-
- Library loading and initialization
|
|
604
|
-
- Component lifecycle events
|
|
605
|
-
- Method registration and invocation
|
|
606
|
-
- Cache hits and misses
|
|
607
|
-
- Performance metrics
|
|
608
|
-
|
|
609
|
-
### Configuration Methods
|
|
610
|
-
|
|
611
|
-
#### Option 1: Angular Environment (Recommended for MJExplorer)
|
|
612
|
-
|
|
613
|
-
Set the `DEBUG` flag in your Angular environment files:
|
|
614
|
-
|
|
615
|
-
```typescript
|
|
616
|
-
// In environment.development.ts
|
|
617
|
-
export const environment = {
|
|
618
|
-
// ... other settings
|
|
619
|
-
DEBUG: true // Enable detailed debug logging
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
// In main.ts (before Angular bootstraps)
|
|
623
|
-
import { environment } from './environments/environment';
|
|
624
|
-
import { ReactDebugConfig } from '@memberjunction/ng-react';
|
|
137
|
+
import { createErrorBoundary, withErrorBoundary } from '@memberjunction/react-runtime';
|
|
625
138
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
Useful for temporarily enabling debug mode without changing code:
|
|
139
|
+
// Create an error boundary component
|
|
140
|
+
const ErrorBoundary = createErrorBoundary({
|
|
141
|
+
fallback: (error) => <div>Something went wrong: {error.message}</div>,
|
|
142
|
+
onError: (error) => logError(error)
|
|
143
|
+
});
|
|
632
144
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
(window as any).__MJ_REACT_DEBUG_MODE__ = true;
|
|
145
|
+
// Or wrap an existing component
|
|
146
|
+
const SafeComponent = withErrorBoundary(MyComponent, { fallback: ErrorFallback });
|
|
636
147
|
```
|
|
637
148
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
Call the API directly in your initialization code:
|
|
149
|
+
### Prop Building
|
|
641
150
|
|
|
642
151
|
```typescript
|
|
643
|
-
import {
|
|
152
|
+
import { buildComponentProps, normalizeCallbacks } from '@memberjunction/react-runtime';
|
|
644
153
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
154
|
+
const props = buildComponentProps(rawData, {
|
|
155
|
+
normalizeStyles: true,
|
|
156
|
+
normalizeCallbacks: true,
|
|
157
|
+
validateProps: true
|
|
158
|
+
});
|
|
650
159
|
```
|
|
651
160
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
The debug mode follows this priority order (highest to lowest):
|
|
655
|
-
|
|
656
|
-
1. **Window global override** (`__MJ_REACT_DEBUG_MODE__`) - Highest priority
|
|
657
|
-
2. **Static property** (set via `setDebugMode()` or environment) - Default
|
|
658
|
-
3. **Default** - `false` (debug disabled)
|
|
659
|
-
|
|
660
|
-
### When to Enable Debug Mode
|
|
161
|
+
## Exported Modules
|
|
661
162
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
❌ **Disable in these scenarios:**
|
|
670
|
-
- Production environments (cleaner console output)
|
|
671
|
-
- Staging environments (unless actively debugging)
|
|
672
|
-
- Performance profiling (reduces console overhead)
|
|
673
|
-
- Automated testing (reduces noise in logs)
|
|
674
|
-
|
|
675
|
-
### Example Debug Output
|
|
676
|
-
|
|
677
|
-
When debug mode is enabled, you'll see output like:
|
|
678
|
-
|
|
679
|
-
```
|
|
680
|
-
[ReactRuntime] Compiling component: Dashboard
|
|
681
|
-
[ReactRuntime] Component registered: Dashboard (namespace: analytics, version: 1.0.0)
|
|
682
|
-
[ReactRuntime] Loading library: lodash (version: 4.17.21)
|
|
683
|
-
[ReactRuntime] Library loaded successfully: lodash
|
|
684
|
-
[ReactRuntime] Cache hit for component: Chart@analytics:1.0.0
|
|
685
|
-
[ReactRuntime] Method registered: getCurrentDataState
|
|
686
|
-
[ReactRuntime] Component hierarchy loaded: 5 components in 234ms
|
|
687
|
-
```
|
|
163
|
+
| Module | Key Exports |
|
|
164
|
+
|--------|-------------|
|
|
165
|
+
| **Compiler** | `ComponentCompiler`, `getBabelConfig`, `getJSXConfig` |
|
|
166
|
+
| **Registry** | `ComponentRegistry`, `ComponentResolver`, `ComponentRegistryService` |
|
|
167
|
+
| **Manager** | `ComponentManager`, `LoadOptions`, `LoadResult` |
|
|
168
|
+
| **Runtime** | `createErrorBoundary`, `buildComponentProps`, `ComponentHierarchyRegistrar`, `ReactRootManager` |
|
|
169
|
+
| **Utilities** | `LibraryLoader`, `LibraryRegistry`, `CacheManager`, `ResourceManager`, `StandardLibraries` |
|
|
688
170
|
|
|
689
171
|
## Configuration
|
|
690
172
|
|
|
691
|
-
### Compiler Configuration
|
|
692
|
-
|
|
693
173
|
```typescript
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
174
|
+
const DEFAULT_CONFIGS = {
|
|
175
|
+
compiler: {
|
|
176
|
+
babel: { presets: ['react'], plugins: [] },
|
|
177
|
+
minify: false,
|
|
178
|
+
sourceMaps: false,
|
|
179
|
+
cache: true,
|
|
180
|
+
maxCacheSize: 100
|
|
699
181
|
},
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
### Registry Configuration
|
|
709
|
-
|
|
710
|
-
```typescript
|
|
711
|
-
const runtime = createReactRuntime(Babel, {
|
|
712
|
-
registry: {
|
|
713
|
-
maxComponents: 500,
|
|
714
|
-
cleanupInterval: 30000, // 30 seconds
|
|
715
|
-
useLRU: true,
|
|
716
|
-
enableNamespaces: true
|
|
717
|
-
}
|
|
718
|
-
});
|
|
182
|
+
registry: {
|
|
183
|
+
maxComponents: 1000,
|
|
184
|
+
cleanupInterval: 60000,
|
|
185
|
+
useLRU: true,
|
|
186
|
+
enableNamespaces: true
|
|
187
|
+
}
|
|
188
|
+
};
|
|
719
189
|
```
|
|
720
190
|
|
|
721
|
-
##
|
|
722
|
-
|
|
723
|
-
### Types
|
|
724
|
-
|
|
725
|
-
- `CompileOptions` - Options for compiling components
|
|
726
|
-
- `ComponentProps` - Standard props passed to components
|
|
727
|
-
- `ComponentCallbacks` - Callback functions available to components
|
|
728
|
-
- `RegistryEntry` - Registry entry with metadata
|
|
729
|
-
- `LibraryConfiguration` - Configuration for external libraries
|
|
730
|
-
- `ExternalLibraryConfig` - Individual library configuration
|
|
731
|
-
|
|
732
|
-
### Classes
|
|
733
|
-
|
|
734
|
-
- `ComponentCompiler` - Compiles React components from source
|
|
735
|
-
- `ComponentRegistry` - Manages compiled components
|
|
736
|
-
- `ComponentResolver` - Resolves component dependencies
|
|
737
|
-
- `StandardLibraryManager` - Manages library configurations
|
|
738
|
-
- `LibraryLoader` - Loads external libraries dynamically
|
|
739
|
-
|
|
740
|
-
### Utilities
|
|
191
|
+
## Build Outputs
|
|
741
192
|
|
|
742
|
-
- `
|
|
743
|
-
- `
|
|
744
|
-
- `wrapComponent()` - Wraps components with additional functionality
|
|
745
|
-
- `createStandardLibraries()` - Creates standard library object from globals
|
|
193
|
+
- **CommonJS**: `dist/index.js` -- for Node.js and bundler environments
|
|
194
|
+
- **UMD**: `dist/umd/` -- for direct browser usage via script tags
|
|
746
195
|
|
|
747
|
-
##
|
|
196
|
+
## Dependencies
|
|
748
197
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
198
|
+
| Package | Purpose |
|
|
199
|
+
|---------|---------|
|
|
200
|
+
| `@memberjunction/core` | Core MJ functionality |
|
|
201
|
+
| `@memberjunction/global` | MJGlobal utilities |
|
|
202
|
+
| `@memberjunction/interactive-component-types` | Component type definitions |
|
|
203
|
+
| `@memberjunction/core-entities` | Entity types |
|
|
204
|
+
| `@memberjunction/graphql-dataprovider` | GraphQL data access |
|
|
205
|
+
| `@babel/standalone` | Runtime JSX compilation |
|
|
206
|
+
| `rxjs` | Observable patterns |
|
|
756
207
|
|
|
757
208
|
## License
|
|
758
209
|
|
|
759
|
-
|
|
210
|
+
ISC
|