@memberjunction/react-runtime 2.88.0 → 2.90.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 +1 -1
- package/CHANGELOG.md +23 -0
- package/dist/compiler/component-compiler.d.ts +3 -0
- package/dist/compiler/component-compiler.d.ts.map +1 -1
- package/dist/compiler/component-compiler.js +114 -8
- package/dist/runtime/component-hierarchy.d.ts.map +1 -1
- package/dist/runtime/component-hierarchy.js +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utilities/index.d.ts +1 -0
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/index.js +1 -0
- package/dist/utilities/library-registry.d.ts +22 -0
- package/dist/utilities/library-registry.d.ts.map +1 -0
- package/dist/utilities/library-registry.js +549 -0
- package/package.json +4 -4
- package/src/compiler/component-compiler.ts +180 -9
- package/src/runtime/component-hierarchy.ts +2 -1
- package/src/types/index.ts +2 -0
- package/src/utilities/index.ts +1 -0
- package/src/utilities/library-registry.ts +644 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ComponentError,
|
|
13
13
|
RuntimeContext
|
|
14
14
|
} from '../types';
|
|
15
|
+
import { LibraryRegistry } from '../utilities/library-registry';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Default compiler configuration
|
|
@@ -78,6 +79,9 @@ export class ComponentCompiler {
|
|
|
78
79
|
// Validate inputs
|
|
79
80
|
this.validateCompileOptions(options);
|
|
80
81
|
|
|
82
|
+
// Load required libraries if specified
|
|
83
|
+
const loadedLibraries = await this.loadRequiredLibraries(options.libraries);
|
|
84
|
+
|
|
81
85
|
// Transpile the component code
|
|
82
86
|
const transpiledCode = this.transpileComponent(
|
|
83
87
|
options.componentCode,
|
|
@@ -85,10 +89,11 @@ export class ComponentCompiler {
|
|
|
85
89
|
options
|
|
86
90
|
);
|
|
87
91
|
|
|
88
|
-
// Create the component factory
|
|
92
|
+
// Create the component factory with loaded libraries
|
|
89
93
|
const componentFactory = this.createComponentFactory(
|
|
90
94
|
transpiledCode,
|
|
91
|
-
options.componentName
|
|
95
|
+
options.componentName,
|
|
96
|
+
loadedLibraries
|
|
92
97
|
);
|
|
93
98
|
|
|
94
99
|
// Build the compiled component
|
|
@@ -137,7 +142,7 @@ export class ComponentCompiler {
|
|
|
137
142
|
throw new Error('Babel instance not set. Call setBabelInstance() first.');
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
const wrappedCode = this.wrapComponentCode(code, componentName);
|
|
145
|
+
const wrappedCode = this.wrapComponentCode(code, componentName, options.libraries);
|
|
141
146
|
|
|
142
147
|
try {
|
|
143
148
|
const result = this.babelInstance.transform(wrappedCode, {
|
|
@@ -158,16 +163,25 @@ export class ComponentCompiler {
|
|
|
158
163
|
* Wraps component code in a factory function for execution
|
|
159
164
|
* @param componentCode - Raw component code
|
|
160
165
|
* @param componentName - Name of the component
|
|
166
|
+
* @param libraries - Optional library dependencies
|
|
161
167
|
* @returns Wrapped component code
|
|
162
168
|
*/
|
|
163
|
-
private wrapComponentCode(componentCode: string, componentName: string): string {
|
|
169
|
+
private wrapComponentCode(componentCode: string, componentName: string, libraries?: any[]): string {
|
|
170
|
+
// Generate library declarations if libraries are provided
|
|
171
|
+
const libraryDeclarations = libraries && libraries.length > 0
|
|
172
|
+
? libraries
|
|
173
|
+
.filter(lib => lib.globalVariable) // Only include libraries with globalVariable
|
|
174
|
+
.map(lib => `const ${lib.globalVariable} = libraries['${lib.globalVariable}'];`)
|
|
175
|
+
.join('\n ')
|
|
176
|
+
: '';
|
|
177
|
+
|
|
164
178
|
return `
|
|
165
179
|
function createComponent(
|
|
166
180
|
React, ReactDOM,
|
|
167
181
|
useState, useEffect, useCallback, useMemo, useRef, useContext, useReducer, useLayoutEffect,
|
|
168
182
|
libraries, styles, console
|
|
169
183
|
) {
|
|
170
|
-
${componentCode}
|
|
184
|
+
${libraryDeclarations ? libraryDeclarations + '\n ' : ''}${componentCode}
|
|
171
185
|
|
|
172
186
|
// Ensure the component exists
|
|
173
187
|
if (typeof ${componentName} === 'undefined') {
|
|
@@ -190,13 +204,163 @@ export class ComponentCompiler {
|
|
|
190
204
|
`;
|
|
191
205
|
}
|
|
192
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Load required libraries from the registry
|
|
209
|
+
* @param libraries - Array of library dependencies
|
|
210
|
+
* @returns Map of loaded libraries
|
|
211
|
+
*/
|
|
212
|
+
private async loadRequiredLibraries(libraries?: any[]): Promise<Map<string, any>> {
|
|
213
|
+
const loadedLibraries = new Map<string, any>();
|
|
214
|
+
|
|
215
|
+
if (!libraries || libraries.length === 0) {
|
|
216
|
+
return loadedLibraries;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Only works in browser environment
|
|
220
|
+
if (typeof window === 'undefined') {
|
|
221
|
+
console.warn('Library loading is only supported in browser environments');
|
|
222
|
+
return loadedLibraries;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const loadPromises = libraries.map(async (lib) => {
|
|
226
|
+
// Check if library is approved
|
|
227
|
+
if (!LibraryRegistry.isApproved(lib.name)) {
|
|
228
|
+
throw new Error(`Library '${lib.name}' is not approved. Only approved libraries can be used.`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Get library definition for complete info
|
|
232
|
+
const libraryDef = LibraryRegistry.getLibrary(lib.name);
|
|
233
|
+
if (!libraryDef) {
|
|
234
|
+
throw new Error(`Library '${lib.name}' not found in registry`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Get CDN URL for the library
|
|
238
|
+
const resolvedVersion = LibraryRegistry.resolveVersion(lib.name, lib.version);
|
|
239
|
+
const cdnUrl = LibraryRegistry.getCdnUrl(lib.name, resolvedVersion);
|
|
240
|
+
|
|
241
|
+
if (!cdnUrl) {
|
|
242
|
+
throw new Error(`No CDN URL found for library '${lib.name}' version '${lib.version || 'default'}'`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check if already loaded
|
|
246
|
+
if ((window as any)[lib.globalVariable]) {
|
|
247
|
+
loadedLibraries.set(lib.globalVariable, (window as any)[lib.globalVariable]);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Load CSS files if the library requires them
|
|
252
|
+
const versionInfo = libraryDef.versions[resolvedVersion || libraryDef.defaultVersion];
|
|
253
|
+
if (versionInfo?.cssUrls) {
|
|
254
|
+
await this.loadStyles(versionInfo.cssUrls);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Load the library dynamically (cdnUrl is guaranteed to be non-null here due to check above)
|
|
258
|
+
await this.loadScript(cdnUrl!, lib.globalVariable);
|
|
259
|
+
|
|
260
|
+
// Capture the library value from global scope
|
|
261
|
+
// Note: Libraries loaded from CDN typically attach to window automatically
|
|
262
|
+
// We capture them here to pass through the component's closure
|
|
263
|
+
const libraryValue = (window as any)[lib.globalVariable];
|
|
264
|
+
if (libraryValue) {
|
|
265
|
+
loadedLibraries.set(lib.globalVariable, libraryValue);
|
|
266
|
+
} else {
|
|
267
|
+
throw new Error(`Library '${lib.name}' failed to load or did not expose '${lib.globalVariable}'`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await Promise.all(loadPromises);
|
|
272
|
+
return loadedLibraries;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Load CSS stylesheets dynamically
|
|
277
|
+
* @param urls - Array of CSS URLs to load
|
|
278
|
+
* @returns Promise that resolves when all stylesheets are loaded
|
|
279
|
+
*/
|
|
280
|
+
private async loadStyles(urls: string[]): Promise<void> {
|
|
281
|
+
const loadPromises = urls.map(url => {
|
|
282
|
+
return new Promise<void>((resolve) => {
|
|
283
|
+
// Check if stylesheet already exists
|
|
284
|
+
const existingLink = document.querySelector(`link[href="${url}"]`);
|
|
285
|
+
if (existingLink) {
|
|
286
|
+
resolve();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Create new link element
|
|
291
|
+
const link = document.createElement('link');
|
|
292
|
+
link.rel = 'stylesheet';
|
|
293
|
+
link.href = url;
|
|
294
|
+
|
|
295
|
+
// CSS load events are not reliable cross-browser, so resolve immediately
|
|
296
|
+
// The CSS will load asynchronously but won't block component rendering
|
|
297
|
+
document.head.appendChild(link);
|
|
298
|
+
resolve();
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
await Promise.all(loadPromises);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Load a script dynamically
|
|
307
|
+
* @param url - Script URL
|
|
308
|
+
* @param globalName - Expected global variable name
|
|
309
|
+
* @returns Promise that resolves when script is loaded
|
|
310
|
+
*/
|
|
311
|
+
private loadScript(url: string, globalName: string): Promise<void> {
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
// Check if script already exists
|
|
314
|
+
const existingScript = document.querySelector(`script[src="${url}"]`);
|
|
315
|
+
if (existingScript) {
|
|
316
|
+
// Wait for it to finish loading
|
|
317
|
+
const checkLoaded = () => {
|
|
318
|
+
if ((window as any)[globalName]) {
|
|
319
|
+
resolve();
|
|
320
|
+
} else {
|
|
321
|
+
setTimeout(checkLoaded, 100);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
checkLoaded();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Create new script element
|
|
329
|
+
const script = document.createElement('script');
|
|
330
|
+
script.src = url;
|
|
331
|
+
script.async = true;
|
|
332
|
+
|
|
333
|
+
script.onload = () => {
|
|
334
|
+
// Give the library a moment to initialize
|
|
335
|
+
setTimeout(() => {
|
|
336
|
+
if ((window as any)[globalName]) {
|
|
337
|
+
resolve();
|
|
338
|
+
} else {
|
|
339
|
+
reject(new Error(`${globalName} not found after loading script`));
|
|
340
|
+
}
|
|
341
|
+
}, 100);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
script.onerror = () => {
|
|
345
|
+
reject(new Error(`Failed to load script: ${url}`));
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
document.head.appendChild(script);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
193
352
|
/**
|
|
194
353
|
* Creates a component factory function from transpiled code
|
|
195
354
|
* @param transpiledCode - Transpiled JavaScript code
|
|
196
355
|
* @param componentName - Name of the component
|
|
356
|
+
* @param loadedLibraries - Map of loaded libraries
|
|
197
357
|
* @returns Component factory function
|
|
198
358
|
*/
|
|
199
|
-
private createComponentFactory(
|
|
359
|
+
private createComponentFactory(
|
|
360
|
+
transpiledCode: string,
|
|
361
|
+
componentName: string,
|
|
362
|
+
loadedLibraries: Map<string, any>
|
|
363
|
+
): Function {
|
|
200
364
|
try {
|
|
201
365
|
// Create the factory function with all React hooks
|
|
202
366
|
const factoryCreator = new Function(
|
|
@@ -209,6 +373,12 @@ export class ComponentCompiler {
|
|
|
209
373
|
// Return a function that executes the factory with runtime context
|
|
210
374
|
return (context: RuntimeContext, styles: any = {}) => {
|
|
211
375
|
const { React, ReactDOM, libraries = {} } = context;
|
|
376
|
+
|
|
377
|
+
// Merge loaded libraries with context libraries
|
|
378
|
+
const mergedLibraries = { ...libraries };
|
|
379
|
+
loadedLibraries.forEach((value, key) => {
|
|
380
|
+
mergedLibraries[key] = value;
|
|
381
|
+
});
|
|
212
382
|
|
|
213
383
|
// Execute the factory creator to get the createComponent function
|
|
214
384
|
const createComponentFn = factoryCreator(
|
|
@@ -222,7 +392,7 @@ export class ComponentCompiler {
|
|
|
222
392
|
React.useContext,
|
|
223
393
|
React.useReducer,
|
|
224
394
|
React.useLayoutEffect,
|
|
225
|
-
|
|
395
|
+
mergedLibraries,
|
|
226
396
|
styles,
|
|
227
397
|
console
|
|
228
398
|
);
|
|
@@ -239,7 +409,7 @@ export class ComponentCompiler {
|
|
|
239
409
|
React.useContext,
|
|
240
410
|
React.useReducer,
|
|
241
411
|
React.useLayoutEffect,
|
|
242
|
-
|
|
412
|
+
mergedLibraries,
|
|
243
413
|
styles,
|
|
244
414
|
console
|
|
245
415
|
);
|
|
@@ -366,7 +536,8 @@ export class ComponentCompiler {
|
|
|
366
536
|
if (this.compilationCache.size >= this.config.maxCacheSize) {
|
|
367
537
|
// Remove oldest entry (first in map)
|
|
368
538
|
const firstKey = this.compilationCache.keys().next().value;
|
|
369
|
-
|
|
539
|
+
if (firstKey)
|
|
540
|
+
this.compilationCache.delete(firstKey);
|
|
370
541
|
}
|
|
371
542
|
|
|
372
543
|
const cacheKey = this.createCacheKey(component.name, code);
|
|
@@ -160,7 +160,8 @@ export class ComponentHierarchyRegistrar {
|
|
|
160
160
|
const compileOptions: CompileOptions = {
|
|
161
161
|
componentName: spec.name,
|
|
162
162
|
componentCode: spec.code,
|
|
163
|
-
styles
|
|
163
|
+
styles,
|
|
164
|
+
libraries: spec.libraries // Pass along library dependencies from the spec
|
|
164
165
|
};
|
|
165
166
|
|
|
166
167
|
const compilationResult = await this.compiler.compile(compileOptions);
|
package/src/types/index.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface CompileOptions {
|
|
|
36
36
|
babelPlugins?: string[];
|
|
37
37
|
/** Custom Babel presets to use */
|
|
38
38
|
babelPresets?: string[];
|
|
39
|
+
/** Library dependencies that the component requires */
|
|
40
|
+
libraries?: any[]; // Using any[] to avoid circular dependency with InteractiveComponents
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
package/src/utilities/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './runtime-utilities';
|
|
|
7
7
|
export * from './component-styles';
|
|
8
8
|
export * from './standard-libraries';
|
|
9
9
|
export * from './library-loader';
|
|
10
|
+
export * from './library-registry';
|
|
10
11
|
export * from './component-error-analyzer';
|
|
11
12
|
export * from './resource-manager';
|
|
12
13
|
export * from './cache-manager';
|