@memberjunction/react-runtime 5.49.0 → 5.51.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 +11 -11
- package/CHANGELOG.md +35 -0
- package/dist/324.runtime.umd.js +2 -2
- package/dist/runtime.umd.js +2335 -1586
- package/dist/utilities/library-loader.d.ts.map +1 -1
- package/dist/utilities/library-loader.js +20 -5
- package/dist/utilities/library-loader.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/library-loader-load-order.test.ts +367 -0
- package/src/utilities/library-loader.ts +32 -9
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for library load-order guarantees in LibraryLoader.
|
|
3
|
+
*
|
|
4
|
+
* The critical invariant: React MUST execute before ReactDOM because ReactDOM's
|
|
5
|
+
* UMD factory captures `window.React` at execution time. If ReactDOM executes
|
|
6
|
+
* first, it gets `undefined` for React and `createRoot` is permanently broken.
|
|
7
|
+
*
|
|
8
|
+
* These tests mock the script-loading layer to:
|
|
9
|
+
* 1. Prove the current (fixed) code always loads React before ReactDOM.
|
|
10
|
+
* 2. Simulate the old parallel-loading race condition and show it can fail.
|
|
11
|
+
* 3. Validate that post-load assertions catch broken ReactDOM objects.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Window / document stubs (we're in a Node environment)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const fakeWindow: Record<string, unknown> = {};
|
|
19
|
+
|
|
20
|
+
vi.stubGlobal('window', fakeWindow);
|
|
21
|
+
vi.stubGlobal('document', {
|
|
22
|
+
createElement: vi.fn().mockReturnValue({
|
|
23
|
+
addEventListener: vi.fn(),
|
|
24
|
+
removeEventListener: vi.fn(),
|
|
25
|
+
parentNode: null,
|
|
26
|
+
}),
|
|
27
|
+
head: {
|
|
28
|
+
appendChild: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
querySelector: vi.fn().mockReturnValue(null),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Mock dependencies that LibraryLoader imports
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
vi.mock('@memberjunction/core-entities', () => ({
|
|
37
|
+
MJComponentLibraryEntity: class {},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
vi.mock('../utilities/resource-manager', () => ({
|
|
41
|
+
resourceManager: {
|
|
42
|
+
setTimeout: vi.fn((_id: string, fn: () => void, _ms: number) => { fn(); return 1; }),
|
|
43
|
+
registerDOMElement: vi.fn(),
|
|
44
|
+
addEventListener: vi.fn(),
|
|
45
|
+
cleanupComponent: vi.fn(),
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
vi.mock('../utilities/standard-libraries', () => ({
|
|
50
|
+
StandardLibraryManager: {
|
|
51
|
+
setConfiguration: vi.fn(),
|
|
52
|
+
getConfiguration: vi.fn().mockReturnValue({ libraries: [], metadata: {} }),
|
|
53
|
+
getEnabledLibraries: vi.fn().mockReturnValue([]),
|
|
54
|
+
},
|
|
55
|
+
// Re-export the type so the import doesn't break
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock('../utilities/library-registry', () => ({
|
|
59
|
+
LibraryRegistry: class {},
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Helpers
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tracks the order in which "scripts" resolve, simulating async CDN downloads.
|
|
68
|
+
* Each call to `createResolver(name)` returns a promise + a `resolve` function
|
|
69
|
+
* the test can call to simulate the script finishing download and execution.
|
|
70
|
+
*/
|
|
71
|
+
function createLoadOrderTracker() {
|
|
72
|
+
const order: string[] = [];
|
|
73
|
+
const resolvers = new Map<string, () => void>();
|
|
74
|
+
|
|
75
|
+
function createResolver(name: string): Promise<Record<string, unknown>> {
|
|
76
|
+
return new Promise<Record<string, unknown>>(resolve => {
|
|
77
|
+
resolvers.set(name, () => {
|
|
78
|
+
order.push(name);
|
|
79
|
+
const fakeGlobal: Record<string, unknown> = { __name: name };
|
|
80
|
+
if (name === 'ReactDOM') {
|
|
81
|
+
// Simulate UMD behavior: createRoot only works if React was loaded first
|
|
82
|
+
if (fakeWindow.React) {
|
|
83
|
+
fakeGlobal.createRoot = function mockCreateRoot() {
|
|
84
|
+
return { unmount: vi.fn() };
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// If React isn't on window yet, createRoot is missing — the real bug
|
|
88
|
+
}
|
|
89
|
+
fakeWindow[name] = fakeGlobal;
|
|
90
|
+
resolve(fakeGlobal);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { order, resolvers, createResolver };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Import the module under test AFTER mocks are set up
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
import { LibraryLoader } from '../utilities/library-loader';
|
|
102
|
+
|
|
103
|
+
describe('LibraryLoader — load order guarantees', () => {
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
// Clean globals between tests
|
|
106
|
+
delete fakeWindow.React;
|
|
107
|
+
delete fakeWindow.ReactDOM;
|
|
108
|
+
delete fakeWindow.Babel;
|
|
109
|
+
delete fakeWindow.PropTypes;
|
|
110
|
+
|
|
111
|
+
// Reset the static loadedResources cache so each test starts clean
|
|
112
|
+
LibraryLoader.getLoadedResources().clear();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
vi.restoreAllMocks();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// -----------------------------------------------------------------------
|
|
120
|
+
// Test 1: React resolves before ReactDOM in the fixed code
|
|
121
|
+
// -----------------------------------------------------------------------
|
|
122
|
+
it('should load React before ReactDOM (sequential phase 1 → phase 2)', async () => {
|
|
123
|
+
const tracker = createLoadOrderTracker();
|
|
124
|
+
|
|
125
|
+
// Spy on the private static loadScript to intercept calls and control
|
|
126
|
+
// resolution order. We use `spyOn` + `mockImplementation` so we can
|
|
127
|
+
// see WHEN each library's loadScript is first called.
|
|
128
|
+
const callOrder: string[] = [];
|
|
129
|
+
|
|
130
|
+
const loadScriptSpy = vi.spyOn(LibraryLoader as unknown as { loadScript: (...args: unknown[]) => Promise<unknown> }, 'loadScript' as never)
|
|
131
|
+
.mockImplementation((_url: unknown, globalName: unknown) => {
|
|
132
|
+
const name = globalName as string;
|
|
133
|
+
callOrder.push(name);
|
|
134
|
+
const p = tracker.createResolver(name);
|
|
135
|
+
// Simulate immediate resolution (CDN is fast) in call order.
|
|
136
|
+
// The key assertion is about WHEN loadScript is called, not when
|
|
137
|
+
// it resolves — if React's loadScript is awaited before ReactDOM's
|
|
138
|
+
// loadScript is even called, the ordering is guaranteed.
|
|
139
|
+
tracker.resolvers.get(name)!();
|
|
140
|
+
return p;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await LibraryLoader.loadLibrariesFromConfig(undefined, false);
|
|
144
|
+
|
|
145
|
+
// React must be the FIRST loadScript call
|
|
146
|
+
expect(callOrder[0]).toBe('React');
|
|
147
|
+
|
|
148
|
+
// ReactDOM must come AFTER React
|
|
149
|
+
const reactIndex = callOrder.indexOf('React');
|
|
150
|
+
const reactDOMIndex = callOrder.indexOf('ReactDOM');
|
|
151
|
+
expect(reactIndex).toBeLessThan(reactDOMIndex);
|
|
152
|
+
|
|
153
|
+
// Execution order (tracker.order) must also have React first
|
|
154
|
+
expect(tracker.order[0]).toBe('React');
|
|
155
|
+
const reactExecIdx = tracker.order.indexOf('React');
|
|
156
|
+
const reactDOMExecIdx = tracker.order.indexOf('ReactDOM');
|
|
157
|
+
expect(reactExecIdx).toBeLessThan(reactDOMExecIdx);
|
|
158
|
+
|
|
159
|
+
loadScriptSpy.mockRestore();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// -----------------------------------------------------------------------
|
|
163
|
+
// Test 2: ReactDOM gets a working createRoot when React loads first
|
|
164
|
+
// -----------------------------------------------------------------------
|
|
165
|
+
it('should produce a ReactDOM with createRoot when load order is correct', async () => {
|
|
166
|
+
const tracker = createLoadOrderTracker();
|
|
167
|
+
|
|
168
|
+
vi.spyOn(LibraryLoader as unknown as { loadScript: (...args: unknown[]) => Promise<unknown> }, 'loadScript' as never)
|
|
169
|
+
.mockImplementation((_url: unknown, globalName: unknown) => {
|
|
170
|
+
const name = globalName as string;
|
|
171
|
+
const p = tracker.createResolver(name);
|
|
172
|
+
// Resolve immediately — React first because of sequential await
|
|
173
|
+
tracker.resolvers.get(name)!();
|
|
174
|
+
return p;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const result = await LibraryLoader.loadLibrariesFromConfig(undefined, false);
|
|
178
|
+
|
|
179
|
+
// ReactDOM should have createRoot because React was available when it "executed"
|
|
180
|
+
expect(result.ReactDOM).toBeDefined();
|
|
181
|
+
expect((result.ReactDOM as Record<string, unknown>).createRoot).toBeDefined();
|
|
182
|
+
expect(typeof (result.ReactDOM as Record<string, unknown>).createRoot).toBe('function');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// -----------------------------------------------------------------------
|
|
186
|
+
// Test 3: Simulating the OLD race condition — ReactDOM executes first
|
|
187
|
+
// -----------------------------------------------------------------------
|
|
188
|
+
it('should demonstrate that ReactDOM lacks createRoot when it executes before React', async () => {
|
|
189
|
+
// This test does NOT use loadLibrariesFromConfig — it directly simulates
|
|
190
|
+
// the broken parallel behavior to prove the race condition is real.
|
|
191
|
+
const tracker = createLoadOrderTracker();
|
|
192
|
+
|
|
193
|
+
// Create promises for both
|
|
194
|
+
const reactPromise = tracker.createResolver('React');
|
|
195
|
+
const reactDOMPromise = tracker.createResolver('ReactDOM');
|
|
196
|
+
|
|
197
|
+
// Simulate the race: resolve ReactDOM FIRST (before React)
|
|
198
|
+
tracker.resolvers.get('ReactDOM')!();
|
|
199
|
+
tracker.resolvers.get('React')!();
|
|
200
|
+
|
|
201
|
+
const [, reactDOM] = await Promise.all([reactPromise, reactDOMPromise]);
|
|
202
|
+
|
|
203
|
+
// ReactDOM executed before React, so createRoot should be MISSING
|
|
204
|
+
expect((reactDOM as Record<string, unknown>).createRoot).toBeUndefined();
|
|
205
|
+
|
|
206
|
+
// Execution order confirms ReactDOM came first
|
|
207
|
+
expect(tracker.order[0]).toBe('ReactDOM');
|
|
208
|
+
expect(tracker.order[1]).toBe('React');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// -----------------------------------------------------------------------
|
|
212
|
+
// Test 4: Simulating correct order — ReactDOM executes after React
|
|
213
|
+
// -----------------------------------------------------------------------
|
|
214
|
+
it('should demonstrate that ReactDOM has createRoot when it executes after React', async () => {
|
|
215
|
+
const tracker = createLoadOrderTracker();
|
|
216
|
+
|
|
217
|
+
const reactPromise = tracker.createResolver('React');
|
|
218
|
+
const reactDOMPromise = tracker.createResolver('ReactDOM');
|
|
219
|
+
|
|
220
|
+
// Correct order: React first, then ReactDOM
|
|
221
|
+
tracker.resolvers.get('React')!();
|
|
222
|
+
tracker.resolvers.get('ReactDOM')!();
|
|
223
|
+
|
|
224
|
+
const [, reactDOM] = await Promise.all([reactPromise, reactDOMPromise]);
|
|
225
|
+
|
|
226
|
+
// ReactDOM executed after React, so createRoot should be present
|
|
227
|
+
expect((reactDOM as Record<string, unknown>).createRoot).toBeDefined();
|
|
228
|
+
expect(typeof (reactDOM as Record<string, unknown>).createRoot).toBe('function');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// -----------------------------------------------------------------------
|
|
232
|
+
// Test 5: ReactDOM and Babel load in parallel (phase 2), both after React
|
|
233
|
+
// -----------------------------------------------------------------------
|
|
234
|
+
it('should load ReactDOM and Babel in parallel after React completes', async () => {
|
|
235
|
+
const callTimestamps: { name: string; time: number }[] = [];
|
|
236
|
+
const startTime = Date.now();
|
|
237
|
+
|
|
238
|
+
vi.spyOn(LibraryLoader as unknown as { loadScript: (...args: unknown[]) => Promise<unknown> }, 'loadScript' as never)
|
|
239
|
+
.mockImplementation((_url: unknown, globalName: unknown) => {
|
|
240
|
+
const name = globalName as string;
|
|
241
|
+
callTimestamps.push({ name, time: Date.now() - startTime });
|
|
242
|
+
|
|
243
|
+
// Simulate globals
|
|
244
|
+
const fakeGlobal: Record<string, unknown> = { __name: name };
|
|
245
|
+
if (name === 'ReactDOM') {
|
|
246
|
+
fakeGlobal.createRoot = vi.fn();
|
|
247
|
+
}
|
|
248
|
+
fakeWindow[name] = fakeGlobal;
|
|
249
|
+
return Promise.resolve(fakeGlobal);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await LibraryLoader.loadLibrariesFromConfig(undefined, false);
|
|
253
|
+
|
|
254
|
+
// React is called first
|
|
255
|
+
expect(callTimestamps[0].name).toBe('React');
|
|
256
|
+
|
|
257
|
+
// ReactDOM and Babel are both called after React, and they can be in either order
|
|
258
|
+
const phase2Names = callTimestamps.slice(1).map(t => t.name);
|
|
259
|
+
expect(phase2Names).toContain('ReactDOM');
|
|
260
|
+
expect(phase2Names).toContain('Babel');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// -----------------------------------------------------------------------
|
|
264
|
+
// Test 6: Post-load validation catches missing createRoot
|
|
265
|
+
// -----------------------------------------------------------------------
|
|
266
|
+
it('should detect when ReactDOM.createRoot is missing (validation check)', () => {
|
|
267
|
+
// Simulate a broken ReactDOM object (loaded before React)
|
|
268
|
+
const brokenReactDOM = { __name: 'ReactDOM' }; // no createRoot
|
|
269
|
+
|
|
270
|
+
// The validation check used by ReactBridgeService
|
|
271
|
+
const hasCreateRoot = brokenReactDOM != null &&
|
|
272
|
+
'createRoot' in brokenReactDOM &&
|
|
273
|
+
typeof (brokenReactDOM as Record<string, unknown>).createRoot === 'function';
|
|
274
|
+
|
|
275
|
+
expect(hasCreateRoot).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should detect when ReactDOM.createRoot is present (validation check)', () => {
|
|
279
|
+
// Simulate a working ReactDOM object (loaded after React)
|
|
280
|
+
const workingReactDOM = {
|
|
281
|
+
__name: 'ReactDOM',
|
|
282
|
+
createRoot: function mockCreateRoot() { return { unmount: vi.fn() }; }
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const hasCreateRoot = workingReactDOM != null &&
|
|
286
|
+
'createRoot' in workingReactDOM &&
|
|
287
|
+
typeof (workingReactDOM as Record<string, unknown>).createRoot === 'function';
|
|
288
|
+
|
|
289
|
+
expect(hasCreateRoot).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// -----------------------------------------------------------------------
|
|
293
|
+
// Test 7: Retry after destroy resets adapter properly
|
|
294
|
+
// -----------------------------------------------------------------------
|
|
295
|
+
it('should demonstrate that clearing initializationPromise allows re-initialization', async () => {
|
|
296
|
+
// Simulates the AngularAdapterService pattern
|
|
297
|
+
let initCount = 0;
|
|
298
|
+
let initPromise: Promise<void> | undefined;
|
|
299
|
+
let runtime: { version: string } | undefined;
|
|
300
|
+
|
|
301
|
+
async function doInit(): Promise<void> {
|
|
302
|
+
initCount++;
|
|
303
|
+
runtime = { version: `v${initCount}` };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function initialize(): Promise<void> {
|
|
307
|
+
if (runtime) return;
|
|
308
|
+
if (initPromise) return initPromise;
|
|
309
|
+
initPromise = doInit();
|
|
310
|
+
await initPromise;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function destroy(): void {
|
|
314
|
+
runtime = undefined;
|
|
315
|
+
initPromise = undefined; // THE FIX — without this, re-init doesn't run
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// First init
|
|
319
|
+
await initialize();
|
|
320
|
+
expect(initCount).toBe(1);
|
|
321
|
+
expect(runtime?.version).toBe('v1');
|
|
322
|
+
|
|
323
|
+
// Destroy
|
|
324
|
+
destroy();
|
|
325
|
+
expect(runtime).toBeUndefined();
|
|
326
|
+
|
|
327
|
+
// Re-init should actually run doInit again
|
|
328
|
+
await initialize();
|
|
329
|
+
expect(initCount).toBe(2);
|
|
330
|
+
expect(runtime?.version).toBe('v2');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should demonstrate the BUG when initializationPromise is NOT cleared', async () => {
|
|
334
|
+
let initCount = 0;
|
|
335
|
+
let initPromise: Promise<void> | undefined;
|
|
336
|
+
let runtime: { version: string } | undefined;
|
|
337
|
+
|
|
338
|
+
async function doInit(): Promise<void> {
|
|
339
|
+
initCount++;
|
|
340
|
+
runtime = { version: `v${initCount}` };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function initialize(): Promise<void> {
|
|
344
|
+
if (runtime) return;
|
|
345
|
+
if (initPromise) return initPromise; // BUG: returns stale resolved promise
|
|
346
|
+
initPromise = doInit();
|
|
347
|
+
await initPromise;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function destroyBuggy(): void {
|
|
351
|
+
runtime = undefined;
|
|
352
|
+
// BUG: initPromise is NOT cleared
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// First init
|
|
356
|
+
await initialize();
|
|
357
|
+
expect(initCount).toBe(1);
|
|
358
|
+
|
|
359
|
+
// Destroy (buggy version)
|
|
360
|
+
destroyBuggy();
|
|
361
|
+
|
|
362
|
+
// Re-init — this silently does nothing because initPromise is still set
|
|
363
|
+
await initialize();
|
|
364
|
+
expect(initCount).toBe(1); // Still 1! doInit never ran again
|
|
365
|
+
expect(runtime).toBeUndefined(); // runtime is still undefined — broken state
|
|
366
|
+
});
|
|
367
|
+
});
|
|
@@ -133,16 +133,39 @@ export class LibraryLoader {
|
|
|
133
133
|
* Load libraries based on the current configuration
|
|
134
134
|
*/
|
|
135
135
|
static async loadLibrariesFromConfig(options?: ConfigLoadOptions, debug?: boolean): Promise<LibraryLoadResult> {
|
|
136
|
-
//
|
|
136
|
+
// Load core runtime libraries in dependency order.
|
|
137
|
+
// ReactDOM's UMD factory captures `window.React` at execution time,
|
|
138
|
+
// so React MUST execute before ReactDOM. Loading them in parallel with
|
|
139
|
+
// async scripts causes an intermittent race condition where ReactDOM
|
|
140
|
+
// executes first and gets `undefined` for React, permanently breaking
|
|
141
|
+
// `createRoot` on that object instance.
|
|
137
142
|
const coreLibraries = getCoreRuntimeLibraries(debug);
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const reactLib = coreLibraries.find(lib => lib.globalVariable === 'React');
|
|
144
|
+
const reactDOMLib = coreLibraries.find(lib => lib.globalVariable === 'ReactDOM');
|
|
145
|
+
const babelLib = coreLibraries.find(lib => lib.globalVariable === 'Babel');
|
|
146
|
+
|
|
147
|
+
// Phase 1: React must load first (ReactDOM depends on it)
|
|
148
|
+
let React: unknown;
|
|
149
|
+
if (reactLib) {
|
|
150
|
+
React = await this.loadScript(reactLib.cdnUrl, reactLib.globalVariable, debug, reactLib.fallbackCdnUrls);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Phase 2: ReactDOM and Babel can load in parallel (both only depend on React)
|
|
154
|
+
const phase2Promises: Promise<unknown>[] = [];
|
|
155
|
+
const phase2Labels: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (reactDOMLib) {
|
|
158
|
+
phase2Promises.push(this.loadScript(reactDOMLib.cdnUrl, reactDOMLib.globalVariable, debug, reactDOMLib.fallbackCdnUrls));
|
|
159
|
+
phase2Labels.push('ReactDOM');
|
|
160
|
+
}
|
|
161
|
+
if (babelLib) {
|
|
162
|
+
phase2Promises.push(this.loadScript(babelLib.cdnUrl, babelLib.globalVariable, debug, babelLib.fallbackCdnUrls));
|
|
163
|
+
phase2Labels.push('Babel');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const phase2Results = await Promise.all(phase2Promises);
|
|
167
|
+
const ReactDOM = phase2Labels.indexOf('ReactDOM') >= 0 ? phase2Results[phase2Labels.indexOf('ReactDOM')] : undefined;
|
|
168
|
+
const Babel = phase2Labels.indexOf('Babel') >= 0 ? phase2Results[phase2Labels.indexOf('Babel')] : undefined;
|
|
146
169
|
|
|
147
170
|
// Expose React and ReactDOM as globals for UMD libraries that expect them
|
|
148
171
|
// Many React component libraries (Recharts, Victory, etc.) expect these as globals
|