@memberjunction/react-runtime 2.75.0 → 2.77.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 +0 -1
- package/dist/compiler/component-compiler.d.ts.map +1 -1
- package/dist/compiler/component-compiler.js +34 -25
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/registry/component-registry.d.ts +1 -0
- package/dist/registry/component-registry.d.ts.map +1 -1
- package/dist/registry/component-registry.js +6 -3
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/prop-builder.d.ts +1 -2
- package/dist/runtime/prop-builder.d.ts.map +1 -1
- package/dist/runtime/prop-builder.js +4 -76
- package/dist/runtime/react-root-manager.d.ts +26 -0
- package/dist/runtime/react-root-manager.d.ts.map +1 -0
- package/dist/runtime/react-root-manager.js +122 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utilities/cache-manager.d.ts +38 -0
- package/dist/utilities/cache-manager.d.ts.map +1 -0
- package/dist/utilities/cache-manager.js +156 -0
- package/dist/utilities/core-libraries.d.ts +5 -0
- package/dist/utilities/core-libraries.d.ts.map +1 -0
- package/dist/utilities/core-libraries.js +52 -0
- package/dist/utilities/index.d.ts +2 -0
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/index.js +2 -0
- package/dist/utilities/library-loader.d.ts +2 -2
- package/dist/utilities/library-loader.d.ts.map +1 -1
- package/dist/utilities/library-loader.js +52 -24
- package/dist/utilities/resource-manager.d.ts +34 -0
- package/dist/utilities/resource-manager.d.ts.map +1 -0
- package/dist/utilities/resource-manager.js +174 -0
- package/package.json +4 -4
- package/samples/entities-1.js +493 -0
- package/src/compiler/component-compiler.ts +64 -35
- package/src/index.ts +18 -1
- package/src/registry/component-registry.ts +14 -4
- package/src/runtime/index.ts +7 -2
- package/src/runtime/prop-builder.ts +5 -112
- package/src/runtime/react-root-manager.ts +218 -0
- package/src/types/index.ts +2 -0
- package/src/utilities/cache-manager.ts +253 -0
- package/src/utilities/core-libraries.ts +61 -0
- package/src/utilities/index.ts +3 -1
- package/src/utilities/library-loader.ts +111 -47
- package/src/utilities/resource-manager.ts +305 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Cache management with TTL and size limits
|
|
3
|
+
* @module @memberjunction/react-runtime/utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface CacheEntry<T> {
|
|
7
|
+
value: T;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
size?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CacheOptions {
|
|
13
|
+
maxSize?: number; // Maximum number of entries
|
|
14
|
+
maxMemory?: number; // Maximum memory in bytes (estimated)
|
|
15
|
+
defaultTTL?: number; // Default TTL in milliseconds
|
|
16
|
+
cleanupInterval?: number; // Cleanup interval in milliseconds
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cache manager with TTL and size limits.
|
|
21
|
+
* Provides automatic cleanup and memory management.
|
|
22
|
+
*/
|
|
23
|
+
export class CacheManager<T = any> {
|
|
24
|
+
private cache = new Map<string, CacheEntry<T>>();
|
|
25
|
+
private memoryUsage = 0;
|
|
26
|
+
private cleanupTimer?: number;
|
|
27
|
+
private readonly options: Required<CacheOptions>;
|
|
28
|
+
|
|
29
|
+
constructor(options: CacheOptions = {}) {
|
|
30
|
+
this.options = {
|
|
31
|
+
maxSize: options.maxSize || 1000,
|
|
32
|
+
maxMemory: options.maxMemory || 50 * 1024 * 1024, // 50MB default
|
|
33
|
+
defaultTTL: options.defaultTTL || 5 * 60 * 1000, // 5 minutes default
|
|
34
|
+
cleanupInterval: options.cleanupInterval || 60 * 1000 // 1 minute default
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (this.options.cleanupInterval > 0) {
|
|
38
|
+
this.startCleanupTimer();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set a value in the cache
|
|
44
|
+
*/
|
|
45
|
+
set(key: string, value: T, ttl?: number): void {
|
|
46
|
+
const size = this.estimateSize(value);
|
|
47
|
+
const entry: CacheEntry<T> = {
|
|
48
|
+
value,
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
size
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Check if we need to evict entries
|
|
54
|
+
if (this.cache.size >= this.options.maxSize) {
|
|
55
|
+
this.evictLRU();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check memory usage
|
|
59
|
+
if (this.memoryUsage + size > this.options.maxMemory) {
|
|
60
|
+
this.evictByMemory(size);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Remove old entry if exists
|
|
64
|
+
const oldEntry = this.cache.get(key);
|
|
65
|
+
if (oldEntry) {
|
|
66
|
+
this.memoryUsage -= oldEntry.size || 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.cache.set(key, entry);
|
|
70
|
+
this.memoryUsage += size;
|
|
71
|
+
|
|
72
|
+
// Schedule removal if TTL is set
|
|
73
|
+
if (ttl || this.options.defaultTTL) {
|
|
74
|
+
const timeout = ttl || this.options.defaultTTL;
|
|
75
|
+
setTimeout(() => this.delete(key), timeout);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get a value from the cache
|
|
81
|
+
*/
|
|
82
|
+
get(key: string): T | undefined {
|
|
83
|
+
const entry = this.cache.get(key);
|
|
84
|
+
if (!entry) return undefined;
|
|
85
|
+
|
|
86
|
+
// Check if expired
|
|
87
|
+
if (this.isExpired(entry)) {
|
|
88
|
+
this.delete(key);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Update timestamp for LRU
|
|
93
|
+
entry.timestamp = Date.now();
|
|
94
|
+
return entry.value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if a key exists and is not expired
|
|
99
|
+
*/
|
|
100
|
+
has(key: string): boolean {
|
|
101
|
+
const entry = this.cache.get(key);
|
|
102
|
+
if (!entry) return false;
|
|
103
|
+
|
|
104
|
+
if (this.isExpired(entry)) {
|
|
105
|
+
this.delete(key);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Delete a key from the cache
|
|
114
|
+
*/
|
|
115
|
+
delete(key: string): boolean {
|
|
116
|
+
const entry = this.cache.get(key);
|
|
117
|
+
if (entry) {
|
|
118
|
+
this.memoryUsage -= entry.size || 0;
|
|
119
|
+
return this.cache.delete(key);
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Clear all entries
|
|
126
|
+
*/
|
|
127
|
+
clear(): void {
|
|
128
|
+
this.cache.clear();
|
|
129
|
+
this.memoryUsage = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get cache statistics
|
|
134
|
+
*/
|
|
135
|
+
getStats(): {
|
|
136
|
+
size: number;
|
|
137
|
+
memoryUsage: number;
|
|
138
|
+
maxSize: number;
|
|
139
|
+
maxMemory: number;
|
|
140
|
+
} {
|
|
141
|
+
return {
|
|
142
|
+
size: this.cache.size,
|
|
143
|
+
memoryUsage: this.memoryUsage,
|
|
144
|
+
maxSize: this.options.maxSize,
|
|
145
|
+
maxMemory: this.options.maxMemory
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Manually trigger cleanup
|
|
151
|
+
*/
|
|
152
|
+
cleanup(): number {
|
|
153
|
+
let removed = 0;
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
|
|
156
|
+
for (const [key, entry] of this.cache) {
|
|
157
|
+
if (this.isExpired(entry, now)) {
|
|
158
|
+
this.delete(key);
|
|
159
|
+
removed++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return removed;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Destroy the cache and stop cleanup timer
|
|
168
|
+
*/
|
|
169
|
+
destroy(): void {
|
|
170
|
+
this.stopCleanupTimer();
|
|
171
|
+
this.clear();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if an entry is expired
|
|
176
|
+
*/
|
|
177
|
+
private isExpired(entry: CacheEntry<T>, now?: number): boolean {
|
|
178
|
+
if (!this.options.defaultTTL) return false;
|
|
179
|
+
const currentTime = now || Date.now();
|
|
180
|
+
return currentTime - entry.timestamp > this.options.defaultTTL;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Evict least recently used entry
|
|
185
|
+
*/
|
|
186
|
+
private evictLRU(): void {
|
|
187
|
+
let lruKey: string | undefined;
|
|
188
|
+
let lruTime = Infinity;
|
|
189
|
+
|
|
190
|
+
for (const [key, entry] of this.cache) {
|
|
191
|
+
if (entry.timestamp < lruTime) {
|
|
192
|
+
lruTime = entry.timestamp;
|
|
193
|
+
lruKey = key;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (lruKey) {
|
|
198
|
+
this.delete(lruKey);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Evict entries to make room for new memory
|
|
204
|
+
*/
|
|
205
|
+
private evictByMemory(requiredSize: number): void {
|
|
206
|
+
const entries = Array.from(this.cache.entries())
|
|
207
|
+
.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
208
|
+
|
|
209
|
+
let freedMemory = 0;
|
|
210
|
+
for (const [key, entry] of entries) {
|
|
211
|
+
if (freedMemory >= requiredSize) break;
|
|
212
|
+
freedMemory += entry.size || 0;
|
|
213
|
+
this.delete(key);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Estimate size of a value
|
|
219
|
+
*/
|
|
220
|
+
private estimateSize(value: T): number {
|
|
221
|
+
if (typeof value === 'string') {
|
|
222
|
+
return value.length * 2; // 2 bytes per character
|
|
223
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
224
|
+
// Rough estimation for objects
|
|
225
|
+
try {
|
|
226
|
+
return JSON.stringify(value).length * 2;
|
|
227
|
+
} catch {
|
|
228
|
+
return 1024; // Default 1KB for objects that can't be stringified
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
return 8; // Default for primitives
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Start the cleanup timer
|
|
237
|
+
*/
|
|
238
|
+
private startCleanupTimer(): void {
|
|
239
|
+
this.cleanupTimer = window.setInterval(() => {
|
|
240
|
+
this.cleanup();
|
|
241
|
+
}, this.options.cleanupInterval);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Stop the cleanup timer
|
|
246
|
+
*/
|
|
247
|
+
private stopCleanupTimer(): void {
|
|
248
|
+
if (this.cleanupTimer) {
|
|
249
|
+
window.clearInterval(this.cleanupTimer);
|
|
250
|
+
this.cleanupTimer = undefined;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ExternalLibraryConfig } from '../types/library-config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core runtime libraries required for the React runtime to function.
|
|
5
|
+
* These are not plugin libraries and are always loaded.
|
|
6
|
+
*/
|
|
7
|
+
export const CORE_RUNTIME_LIBRARIES: ExternalLibraryConfig[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 'react',
|
|
10
|
+
name: 'react',
|
|
11
|
+
displayName: 'React',
|
|
12
|
+
category: 'runtime',
|
|
13
|
+
globalVariable: 'React',
|
|
14
|
+
version: '18.2.0',
|
|
15
|
+
cdnUrl: 'https://unpkg.com/react@18.2.0/umd/react.production.min.js',
|
|
16
|
+
description: 'React core library',
|
|
17
|
+
isEnabled: true,
|
|
18
|
+
isCore: true,
|
|
19
|
+
isRuntimeOnly: true
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'react-dom',
|
|
23
|
+
name: 'react-dom',
|
|
24
|
+
displayName: 'ReactDOM',
|
|
25
|
+
category: 'runtime',
|
|
26
|
+
globalVariable: 'ReactDOM',
|
|
27
|
+
version: '18.2.0',
|
|
28
|
+
cdnUrl: 'https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js',
|
|
29
|
+
description: 'React DOM library',
|
|
30
|
+
isEnabled: true,
|
|
31
|
+
isCore: true,
|
|
32
|
+
isRuntimeOnly: true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'babel-standalone',
|
|
36
|
+
name: '@babel/standalone',
|
|
37
|
+
displayName: 'Babel Standalone',
|
|
38
|
+
category: 'runtime',
|
|
39
|
+
globalVariable: 'Babel',
|
|
40
|
+
version: '7.24.4',
|
|
41
|
+
cdnUrl: 'https://unpkg.com/@babel/standalone@7.24.4/babel.min.js',
|
|
42
|
+
description: 'Babel compiler for JSX transformation',
|
|
43
|
+
isEnabled: true,
|
|
44
|
+
isCore: true,
|
|
45
|
+
isRuntimeOnly: true
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the core runtime libraries configuration
|
|
51
|
+
*/
|
|
52
|
+
export function getCoreRuntimeLibraries(): ExternalLibraryConfig[] {
|
|
53
|
+
return CORE_RUNTIME_LIBRARIES;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if a library ID is a core runtime library
|
|
58
|
+
*/
|
|
59
|
+
export function isCoreRuntimeLibrary(libraryId: string): boolean {
|
|
60
|
+
return CORE_RUNTIME_LIBRARIES.some(lib => lib.id === libraryId);
|
|
61
|
+
}
|
package/src/utilities/index.ts
CHANGED
|
@@ -7,4 +7,6 @@ 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 './component-error-analyzer';
|
|
10
|
+
export * from './component-error-analyzer';
|
|
11
|
+
export * from './resource-manager';
|
|
12
|
+
export * from './cache-manager';
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
StandardLibraryManager
|
|
10
10
|
} from './standard-libraries';
|
|
11
11
|
import { LibraryConfiguration, ExternalLibraryConfig, LibraryLoadOptions as ConfigLoadOptions } from '../types/library-config';
|
|
12
|
+
import { getCoreRuntimeLibraries, isCoreRuntimeLibrary } from './core-libraries';
|
|
13
|
+
import { resourceManager } from './resource-manager';
|
|
14
|
+
|
|
15
|
+
// Unique component ID for resource tracking
|
|
16
|
+
const LIBRARY_LOADER_COMPONENT_ID = 'mj-react-runtime-library-loader-singleton';
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* Represents a loaded script or CSS resource
|
|
@@ -52,12 +57,30 @@ export class LibraryLoader {
|
|
|
52
57
|
/**
|
|
53
58
|
* Load all standard libraries (core + UI + CSS)
|
|
54
59
|
* This is the main method that should be used by test harness and Angular wrapper
|
|
60
|
+
* @param config Optional full library configuration to replace the default
|
|
61
|
+
* @param additionalLibraries Optional additional libraries to merge with the configuration
|
|
55
62
|
*/
|
|
56
|
-
static async loadAllLibraries(
|
|
63
|
+
static async loadAllLibraries(
|
|
64
|
+
config?: LibraryConfiguration,
|
|
65
|
+
additionalLibraries?: ExternalLibraryConfig[]
|
|
66
|
+
): Promise<LibraryLoadResult> {
|
|
57
67
|
if (config) {
|
|
58
68
|
StandardLibraryManager.setConfiguration(config);
|
|
59
69
|
}
|
|
60
70
|
|
|
71
|
+
// If additional libraries are provided, merge them with the current configuration
|
|
72
|
+
if (additionalLibraries && additionalLibraries.length > 0) {
|
|
73
|
+
const currentConfig = StandardLibraryManager.getConfiguration();
|
|
74
|
+
const mergedConfig: LibraryConfiguration = {
|
|
75
|
+
libraries: [...currentConfig.libraries, ...additionalLibraries],
|
|
76
|
+
metadata: {
|
|
77
|
+
...currentConfig.metadata,
|
|
78
|
+
lastUpdated: new Date().toISOString()
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
StandardLibraryManager.setConfiguration(mergedConfig);
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
return this.loadLibrariesFromConfig();
|
|
62
85
|
}
|
|
63
86
|
|
|
@@ -65,55 +88,55 @@ export class LibraryLoader {
|
|
|
65
88
|
* Load libraries based on the current configuration
|
|
66
89
|
*/
|
|
67
90
|
static async loadLibrariesFromConfig(options?: ConfigLoadOptions): Promise<LibraryLoadResult> {
|
|
91
|
+
// Always load core runtime libraries first
|
|
92
|
+
const coreLibraries = getCoreRuntimeLibraries();
|
|
93
|
+
const corePromises = coreLibraries.map(lib =>
|
|
94
|
+
this.loadScript(lib.cdnUrl, lib.globalVariable)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const coreResults = await Promise.all(corePromises);
|
|
98
|
+
const React = coreResults.find((_, i) => coreLibraries[i].globalVariable === 'React');
|
|
99
|
+
const ReactDOM = coreResults.find((_, i) => coreLibraries[i].globalVariable === 'ReactDOM');
|
|
100
|
+
const Babel = coreResults.find((_, i) => coreLibraries[i].globalVariable === 'Babel');
|
|
101
|
+
|
|
102
|
+
// Now load plugin libraries from configuration
|
|
68
103
|
const config = StandardLibraryManager.getConfiguration();
|
|
69
104
|
const enabledLibraries = StandardLibraryManager.getEnabledLibraries();
|
|
70
105
|
|
|
106
|
+
// Filter out any core runtime libraries from plugin configuration
|
|
107
|
+
let pluginLibraries = enabledLibraries.filter(lib => !isCoreRuntimeLibrary(lib.id));
|
|
108
|
+
|
|
71
109
|
// Apply options filters if provided
|
|
72
|
-
let librariesToLoad = enabledLibraries;
|
|
73
110
|
if (options) {
|
|
74
111
|
if (options.categories) {
|
|
75
|
-
|
|
112
|
+
pluginLibraries = pluginLibraries.filter(lib =>
|
|
76
113
|
options.categories!.includes(lib.category)
|
|
77
114
|
);
|
|
78
115
|
}
|
|
79
116
|
if (options.excludeRuntimeOnly) {
|
|
80
|
-
|
|
117
|
+
pluginLibraries = pluginLibraries.filter(lib => !lib.isRuntimeOnly);
|
|
81
118
|
}
|
|
82
119
|
}
|
|
83
120
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
const componentLibs = librariesToLoad.filter(lib => lib.category !== 'runtime');
|
|
87
|
-
|
|
88
|
-
// Load runtime libraries first (React, ReactDOM, Babel)
|
|
89
|
-
const runtimePromises = runtimeLibs.map(lib =>
|
|
90
|
-
this.loadScript(lib.cdnUrl, lib.globalVariable)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const runtimeResults = await Promise.all(runtimePromises);
|
|
94
|
-
const React = runtimeResults.find((_, i) => runtimeLibs[i].globalVariable === 'React');
|
|
95
|
-
const ReactDOM = runtimeResults.find((_, i) => runtimeLibs[i].globalVariable === 'ReactDOM');
|
|
96
|
-
const Babel = runtimeResults.find((_, i) => runtimeLibs[i].globalVariable === 'Babel');
|
|
97
|
-
|
|
98
|
-
// Load CSS files (non-blocking)
|
|
99
|
-
componentLibs.forEach(lib => {
|
|
121
|
+
// Load CSS files for plugin libraries (non-blocking)
|
|
122
|
+
pluginLibraries.forEach(lib => {
|
|
100
123
|
if (lib.cdnCssUrl) {
|
|
101
124
|
this.loadCSS(lib.cdnCssUrl);
|
|
102
125
|
}
|
|
103
126
|
});
|
|
104
127
|
|
|
105
|
-
// Load
|
|
106
|
-
const
|
|
128
|
+
// Load plugin libraries
|
|
129
|
+
const pluginPromises = pluginLibraries.map(lib =>
|
|
107
130
|
this.loadScript(lib.cdnUrl, lib.globalVariable)
|
|
108
131
|
);
|
|
109
132
|
|
|
110
|
-
const
|
|
133
|
+
const pluginResults = await Promise.all(pluginPromises);
|
|
111
134
|
|
|
112
|
-
// Build libraries object
|
|
135
|
+
// Build libraries object (only contains plugin libraries)
|
|
113
136
|
const libraries: StandardLibraries = {};
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
libraries[lib.globalVariable] =
|
|
138
|
+
pluginLibraries.forEach((lib, index) => {
|
|
139
|
+
libraries[lib.globalVariable] = pluginResults[index];
|
|
117
140
|
});
|
|
118
141
|
|
|
119
142
|
return {
|
|
@@ -195,28 +218,46 @@ export class LibraryLoader {
|
|
|
195
218
|
script.async = true;
|
|
196
219
|
script.crossOrigin = 'anonymous';
|
|
197
220
|
|
|
198
|
-
|
|
221
|
+
const cleanup = () => {
|
|
222
|
+
script.removeEventListener('load', onLoad);
|
|
223
|
+
script.removeEventListener('error', onError);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const onLoad = () => {
|
|
227
|
+
cleanup();
|
|
199
228
|
const global = (window as any)[globalName];
|
|
200
229
|
if (global) {
|
|
201
230
|
resolve(global);
|
|
202
231
|
} else {
|
|
203
232
|
// Some libraries may take a moment to initialize
|
|
204
|
-
setTimeout(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
233
|
+
const timeoutId = resourceManager.setTimeout(
|
|
234
|
+
LIBRARY_LOADER_COMPONENT_ID,
|
|
235
|
+
() => {
|
|
236
|
+
const delayedGlobal = (window as any)[globalName];
|
|
237
|
+
if (delayedGlobal) {
|
|
238
|
+
resolve(delayedGlobal);
|
|
239
|
+
} else {
|
|
240
|
+
reject(new Error(`${globalName} not found after script load`));
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
100,
|
|
244
|
+
{ url, globalName }
|
|
245
|
+
);
|
|
212
246
|
}
|
|
213
247
|
};
|
|
214
248
|
|
|
215
|
-
|
|
249
|
+
const onError = () => {
|
|
250
|
+
cleanup();
|
|
216
251
|
reject(new Error(`Failed to load script: ${url}`));
|
|
217
252
|
};
|
|
218
253
|
|
|
254
|
+
script.addEventListener('load', onLoad);
|
|
255
|
+
script.addEventListener('error', onError);
|
|
256
|
+
|
|
219
257
|
document.head.appendChild(script);
|
|
258
|
+
|
|
259
|
+
// Register the script element for cleanup
|
|
260
|
+
resourceManager.registerDOMElement(LIBRARY_LOADER_COMPONENT_ID, script);
|
|
220
261
|
});
|
|
221
262
|
|
|
222
263
|
this.loadedResources.set(url, {
|
|
@@ -244,6 +285,9 @@ export class LibraryLoader {
|
|
|
244
285
|
link.rel = 'stylesheet';
|
|
245
286
|
link.href = url;
|
|
246
287
|
document.head.appendChild(link);
|
|
288
|
+
|
|
289
|
+
// Register the link element for cleanup
|
|
290
|
+
resourceManager.registerDOMElement(LIBRARY_LOADER_COMPONENT_ID, link);
|
|
247
291
|
|
|
248
292
|
this.loadedResources.set(url, {
|
|
249
293
|
element: link,
|
|
@@ -266,14 +310,19 @@ export class LibraryLoader {
|
|
|
266
310
|
resolve(global);
|
|
267
311
|
} else {
|
|
268
312
|
// Retry after a short delay
|
|
269
|
-
setTimeout(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
313
|
+
resourceManager.setTimeout(
|
|
314
|
+
LIBRARY_LOADER_COMPONENT_ID,
|
|
315
|
+
() => {
|
|
316
|
+
const delayedGlobal = (window as any)[globalName];
|
|
317
|
+
if (delayedGlobal) {
|
|
318
|
+
resolve(delayedGlobal);
|
|
319
|
+
} else {
|
|
320
|
+
reject(new Error(`${globalName} not found after script load`));
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
100,
|
|
324
|
+
{ context: 'waitForScriptLoad', globalName }
|
|
325
|
+
);
|
|
277
326
|
}
|
|
278
327
|
};
|
|
279
328
|
|
|
@@ -285,10 +334,15 @@ export class LibraryLoader {
|
|
|
285
334
|
|
|
286
335
|
// Wait for load
|
|
287
336
|
const loadHandler = () => {
|
|
288
|
-
script.removeEventListener('load', loadHandler);
|
|
289
337
|
checkGlobal();
|
|
290
338
|
};
|
|
291
|
-
|
|
339
|
+
resourceManager.addEventListener(
|
|
340
|
+
LIBRARY_LOADER_COMPONENT_ID,
|
|
341
|
+
script,
|
|
342
|
+
'load',
|
|
343
|
+
loadHandler,
|
|
344
|
+
{ once: true }
|
|
345
|
+
);
|
|
292
346
|
}
|
|
293
347
|
|
|
294
348
|
|
|
@@ -300,9 +354,19 @@ export class LibraryLoader {
|
|
|
300
354
|
}
|
|
301
355
|
|
|
302
356
|
/**
|
|
303
|
-
* Clear loaded resources cache
|
|
357
|
+
* Clear loaded resources cache and cleanup DOM elements
|
|
304
358
|
*/
|
|
305
359
|
static clearCache(): void {
|
|
360
|
+
// Remove all script and link elements we added
|
|
361
|
+
this.loadedResources.forEach((resource, url) => {
|
|
362
|
+
if (resource.element && resource.element.parentNode) {
|
|
363
|
+
resource.element.parentNode.removeChild(resource.element);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
306
367
|
this.loadedResources.clear();
|
|
368
|
+
|
|
369
|
+
// Clean up any resources managed by resource manager
|
|
370
|
+
resourceManager.cleanupComponent(LIBRARY_LOADER_COMPONENT_ID);
|
|
307
371
|
}
|
|
308
372
|
}
|