|
6 | 6 | * |
7 | 7 | */ |
8 | 8 |
|
9 | | -import { defer, fromEvent, Observable, map, shareReplay, take } from 'rxjs'; |
10 | | - |
11 | | -export interface IStateObj { |
12 | | - script$: Observable<void> | null; |
13 | | -} |
14 | | - |
15 | | -const createState = (): IStateObj => ({ |
16 | | - script$: null, |
17 | | -}); |
| 9 | +import { fromEvent, Observable, shareReplay, switchMap, BehaviorSubject, first, filter, map } from 'rxjs'; |
18 | 10 |
|
19 | 11 | interface ScriptLoader { |
20 | 12 | load: (doc: Document, url: string) => Observable<void>; |
| 13 | + /** Intended to only to be used by tests. */ |
21 | 14 | reinitialize: () => void; |
22 | 15 | } |
23 | 16 |
|
24 | | -const CreateScriptLoader = (): ScriptLoader => { |
25 | | - let state = createState(); |
| 17 | +const firstEmission = () => (source: Observable<unknown>): Observable<void> => source.pipe(first(), map(() => undefined)); |
26 | 18 |
|
27 | | - const load = (doc: Document, url: string) => ( |
28 | | - state.script$ || |
29 | | - // Caretaker note: the `script$` is a multicast observable since it's piped with `shareReplay`, |
30 | | - // so if there're multiple editor components simultaneously on the page, they'll subscribe to the internal |
31 | | - // `ReplaySubject`. The script will be loaded only once, and `ReplaySubject` will cache the result. |
32 | | - (state.script$ = defer(() => { |
| 19 | +const CreateScriptLoader = (): ScriptLoader => { |
| 20 | + const params$ = new BehaviorSubject<Parameters<ScriptLoader['load']> | null>(null); |
| 21 | + const loaded$: Observable<void> = params$.pipe( |
| 22 | + filter(Boolean), |
| 23 | + switchMap(([ doc, url ]) => { |
33 | 24 | const scriptTag = doc.createElement('script'); |
34 | 25 | scriptTag.referrerPolicy = 'origin'; |
35 | 26 | scriptTag.type = 'application/javascript'; |
36 | 27 | scriptTag.src = url; |
37 | 28 | doc.head.appendChild(scriptTag); |
38 | | - return fromEvent(scriptTag, 'load').pipe(take(1), map(() => undefined)); |
39 | | - }).pipe(shareReplay({ bufferSize: 1, refCount: true }))) |
| 29 | + return fromEvent(scriptTag, 'load').pipe(firstEmission()); |
| 30 | + }), |
| 31 | + // Caretaker note: `loaded$` is a multicast observable since it's piped with `shareReplay`, |
| 32 | + // so if there're multiple editor components simultaneously on the page, they'll subscribe to the internal |
| 33 | + // `ReplaySubject`. The script will be loaded only once, and `ReplaySubject` will cache the result. |
| 34 | + shareReplay({ bufferSize: 1, refCount: true }) |
40 | 35 | ); |
41 | 36 |
|
42 | | - // Only to be used by tests. |
43 | | - const reinitialize = () => { |
44 | | - state = createState(); |
45 | | - }; |
46 | | - |
47 | 37 | return { |
48 | | - load, |
49 | | - reinitialize, |
| 38 | + load: (...args) => { |
| 39 | + if (!params$.getValue()) { |
| 40 | + params$.next(args); |
| 41 | + } |
| 42 | + return loaded$; |
| 43 | + }, |
| 44 | + reinitialize: () => { |
| 45 | + params$.next(null); |
| 46 | + }, |
50 | 47 | }; |
51 | 48 | }; |
52 | 49 |
|
|
0 commit comments