Skip to content

Commit 6b74463

Browse files
committed
Fix libraries loading in callbacks.
1 parent 008cb60 commit 6b74463

File tree

9 files changed

+213
-157
lines changed

9 files changed

+213
-157
lines changed

dash/dash-renderer/src/actions/callbacks.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
CallbackResponseData
3535
} from '../types/callbacks';
3636
import {isMultiValued, stringifyId, isMultiOutputProp} from './dependencies';
37-
import {urlBase} from './utils';
37+
import {crawlLayout, urlBase} from './utils';
3838
import {getCSRFHeader} from '.';
3939
import {createAction, Action} from 'redux-actions';
4040
import {addHttpHeaders} from '../actions';
@@ -44,6 +44,9 @@ import {handlePatch, isPatch} from './patch';
4444
import {getPath} from './paths';
4545

4646
import {requestDependencies} from './requestDependencies';
47+
import loadLibrary from '../libraries/loadLibrary';
48+
import fetchDist from '../libraries/fetchDist';
49+
import {setLibraryLoaded} from './libraries';
4750

4851
export const addBlockedCallbacks = createAction<IBlockedCallback[]>(
4952
CallbackActionType.AddBlocked
@@ -362,6 +365,7 @@ function handleServerside(
362365
let runningOff: any;
363366
let progressDefault: any;
364367
let moreArgs = additionalArgs;
368+
const libraries = Object.keys(getState().libraries);
365369

366370
const fetchCallback = () => {
367371
const headers = getCSRFHeader() as any;
@@ -508,8 +512,37 @@ function handleServerside(
508512
}
509513

510514
if (!long || data.response !== undefined) {
511-
completeJob();
512-
finishLine(data);
515+
const newLibs: string[] = [];
516+
Object.values(data.response as any).forEach(
517+
(newData: any) => {
518+
Object.values(newData).forEach(newProp => {
519+
crawlLayout(newProp, (c: any) => {
520+
if (!libraries.includes(c.namespace)) {
521+
newLibs.push(c.namespace);
522+
}
523+
});
524+
});
525+
}
526+
);
527+
if (newLibs.length) {
528+
fetchDist(
529+
getState().config.requests_pathname_prefix,
530+
newLibs
531+
)
532+
.then(data => {
533+
return Promise.all(data.map(loadLibrary));
534+
})
535+
.then(() => {
536+
completeJob();
537+
finishLine(data);
538+
dispatch(
539+
setLibraryLoaded({libraries: newLibs})
540+
);
541+
});
542+
} else {
543+
completeJob();
544+
finishLine(data);
545+
}
513546
} else {
514547
// Poll chain.
515548
setTimeout(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {LibrariesActions} from '../libraries/libraryTypes';
2+
3+
const createAction = (type: LibrariesActions) => (payload: any) => ({
4+
type,
5+
payload
6+
});
7+
8+
export const setLibraryLoading = createAction(LibrariesActions.LOAD);
9+
export const setLibraryLoaded = createAction(LibrariesActions.LOADED);
10+
export const setLibraryToLoad = createAction(LibrariesActions.TO_LOAD);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {LibraryResource} from './libraryTypes';
2+
3+
export default function fetchDist(
4+
pathnamePrefix: string,
5+
libraries: string[]
6+
): Promise<LibraryResource[]> {
7+
return fetch(`${pathnamePrefix}_dash-dist`, {
8+
body: JSON.stringify(libraries),
9+
headers: {'Content-Type': 'application/json'},
10+
method: 'POST'
11+
}).then(response => response.json());
12+
}

dash/dash-renderer/src/libraries/librariesContext.ts

Lines changed: 42 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,26 @@
1+
import {createContext, useContext, useEffect, useState} from 'react';
2+
import {pathOr, toPairs} from 'ramda';
3+
import loadLibrary from './loadLibrary';
4+
import {batch, useDispatch, useSelector} from 'react-redux';
5+
import {LibrariesState} from './libraryTypes';
16
import {
2-
createContext,
3-
useContext,
4-
useReducer,
5-
useEffect,
6-
useState
7-
} from 'react';
8-
import {assocPath, pathOr, pipe, toPairs} from 'ramda';
9-
10-
type LibraryResource = {
11-
type: '_js_dist' | '_css_dist';
12-
url: string;
13-
async?: string;
14-
namespace: string;
15-
relative_package_path?: string;
16-
};
17-
18-
type LibrariesState = {
19-
[libname: string]: {
20-
toLoad: boolean;
21-
loading: boolean;
22-
loaded: boolean;
23-
dist?: LibraryResource[];
24-
};
25-
};
26-
27-
enum LibrariesActions {
28-
LOAD,
29-
LOADED,
30-
TO_LOAD
31-
}
32-
33-
type LoadingPayload = {
34-
libraries: string[];
35-
};
36-
37-
type LoadedPayload = {
38-
libraries: string[];
39-
};
40-
41-
type ToLoadPayload = {
42-
library: string;
43-
};
44-
45-
type LibrariesAction = {
46-
type: LibrariesActions;
47-
payload: LoadingPayload | LoadedPayload | ToLoadPayload;
48-
};
7+
setLibraryLoaded,
8+
setLibraryLoading,
9+
setLibraryToLoad
10+
} from '../actions/libraries';
11+
import fetchDist from './fetchDist';
4912

5013
export type LibrariesContextType = {
5114
state: LibrariesState;
52-
setLoading: (payload: LoadingPayload) => void;
53-
setLoaded: (payload: LoadedPayload) => void;
54-
setToLoad: (payload: ToLoadPayload) => void;
5515
isLoading: (libraryName: string) => boolean;
5616
isLoaded: (libraryName: string) => boolean;
5717
fetchLibraries: () => void;
5818
getLibrariesToLoad: () => string[];
5919
addToLoad: (libName: string) => void;
6020
};
6121

62-
function handleLoad(library: string, state: LibrariesState) {
63-
return pipe(
64-
assocPath([library, 'loading'], true),
65-
assocPath([library, 'toLoad'], false)
66-
)(state) as LibrariesState;
67-
}
68-
69-
function handleLoaded(library: string, state: LibrariesState) {
70-
return pipe(
71-
assocPath([library, 'loaded'], true),
72-
assocPath([library, 'loading'], false)
73-
)(state) as LibrariesState;
74-
}
75-
76-
export function librariesReducer(
77-
state: LibrariesState,
78-
action: LibrariesAction
79-
): LibrariesState {
80-
switch (action.type) {
81-
case LibrariesActions.LOAD:
82-
return (action.payload as LoadingPayload).libraries.reduce(
83-
(acc, lib) => handleLoad(lib, acc),
84-
state
85-
);
86-
case LibrariesActions.LOADED:
87-
return (action.payload as LoadedPayload).libraries.reduce(
88-
(acc, lib) => handleLoaded(lib, acc),
89-
state
90-
);
91-
case LibrariesActions.TO_LOAD:
92-
return pipe(
93-
assocPath(
94-
[(action.payload as ToLoadPayload).library, 'toLoad'],
95-
true
96-
)
97-
)(state) as LibrariesState;
98-
default:
99-
return state;
100-
}
22+
function librarySelector(s: any) {
23+
return s.libraries as LibrariesState;
10124
}
10225

10326
export function createLibrariesContext(
@@ -106,26 +29,9 @@ export function createLibrariesContext(
10629
onReady: () => void,
10730
ready: boolean
10831
): LibrariesContextType {
109-
const [state, dispatch] = useReducer(librariesReducer, {}, () => {
110-
const libState: LibrariesState = {};
111-
initialLibraries.forEach(lib => {
112-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
113-
// @ts-ignore
114-
if (window[lib]) {
115-
libState[lib] = {toLoad: false, loaded: true, loading: false};
116-
} else {
117-
libState[lib] = {toLoad: true, loaded: false, loading: false};
118-
}
119-
});
120-
return libState;
121-
});
32+
const dispatch = useDispatch();
33+
const state = useSelector(librarySelector);
12234
const [callback, setCallback] = useState<number>(-1);
123-
const createAction = (type: LibrariesActions) => (payload: any) =>
124-
dispatch({type, payload});
125-
126-
const setLoading = createAction(LibrariesActions.LOAD);
127-
const setLoaded = createAction(LibrariesActions.LOADED);
128-
const setToLoad = createAction(LibrariesActions.TO_LOAD);
12935

13036
const isLoaded = (libraryName: string) =>
13137
pathOr(false, [libraryName, 'loaded'], state);
@@ -139,9 +45,9 @@ export function createLibrariesContext(
13945
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
14046
// @ts-ignore
14147
if (window[libraryName]) {
142-
setLoaded({libraries: [libraryName]});
48+
dispatch(setLibraryLoaded({libraries: [libraryName]}));
14349
} else {
144-
setToLoad({library: libraryName});
50+
dispatch(setLibraryToLoad({library: libraryName}));
14551
}
14652
}
14753
// if lib is already in don't do anything.
@@ -161,57 +67,42 @@ export function createLibrariesContext(
16167
return;
16268
}
16369

164-
setLoading({libraries});
70+
dispatch(setLibraryLoading({libraries}));
16571

166-
fetch(`${pathnamePrefix}_dash-dist`, {
167-
body: JSON.stringify(libraries),
168-
headers: {'Content-Type': 'application/json'},
169-
method: 'POST'
170-
})
171-
.then(response => response.json())
72+
fetchDist(pathnamePrefix, libraries)
17273
.then(data => {
173-
const head = document.querySelector('head');
174-
const loadPromises: Promise<void>[] = [];
175-
data.forEach((resource: LibraryResource) => {
176-
if (resource.type === '_js_dist') {
177-
const element = document.createElement('script');
178-
element.src = resource.url;
179-
element.async = true;
180-
loadPromises.push(
181-
new Promise((resolve, reject) => {
182-
element.onload = () => {
183-
resolve();
184-
};
185-
element.onerror = error => reject(error);
186-
})
187-
);
188-
head?.appendChild(element);
189-
} else if (resource.type === '_css_dist') {
190-
const element = document.createElement('link');
191-
element.href = resource.url;
192-
element.rel = 'stylesheet';
193-
loadPromises.push(
194-
new Promise((resolve, reject) => {
195-
element.onload = () => {
196-
resolve();
197-
};
198-
element.onerror = error => reject(error);
199-
})
200-
);
201-
head?.appendChild(element);
202-
}
203-
});
204-
return Promise.all(loadPromises);
74+
return Promise.all(data.map(loadLibrary));
20575
})
20676
.then(() => {
207-
setLoaded({libraries});
77+
dispatch(setLibraryLoaded({libraries}));
20878
setCallback(-1);
20979
onReady();
21080
});
21181
};
21282

83+
useEffect(() => {
84+
batch(() => {
85+
const loaded: string[] = [];
86+
initialLibraries.forEach(lib => {
87+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
88+
// @ts-ignore
89+
if (window[lib]) {
90+
loaded.push(lib);
91+
} else {
92+
dispatch(setLibraryToLoad({library: lib}));
93+
}
94+
});
95+
if (loaded.length) {
96+
dispatch(setLibraryLoaded({libraries: loaded}));
97+
}
98+
});
99+
}, [initialLibraries]);
100+
213101
// Load libraries on a throttle to have time to gather all the components in one go.
214102
useEffect(() => {
103+
if (ready) {
104+
return;
105+
}
215106
const libraries = getLibrariesToLoad();
216107
if (!libraries.length) {
217108
if (!ready && initialLibraries.length === 0) {
@@ -224,13 +115,10 @@ export function createLibrariesContext(
224115
}
225116
const timeout = window.setTimeout(fetchLibraries, 0);
226117
setCallback(timeout);
227-
}, [state]);
118+
}, [state, ready]);
228119

229120
return {
230121
state,
231-
setLoading,
232-
setLoaded,
233-
setToLoad,
234122
isLoaded,
235123
isLoading,
236124
fetchLibraries,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export enum LibrariesActions {
2+
LOAD = 'LOAD_LIBRARY',
3+
LOADED = 'LOADED_LIBRARY',
4+
TO_LOAD = 'TO_LOAD'
5+
}
6+
7+
export type LibrariesState = {
8+
[libname: string]: {
9+
toLoad: boolean;
10+
loading: boolean;
11+
loaded: boolean;
12+
};
13+
};
14+
export type LibraryResource = {
15+
type: '_js_dist' | '_css_dist';
16+
url: string;
17+
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {LibraryResource} from './libraryTypes';
2+
3+
export default function (resource: LibraryResource) {
4+
let prom;
5+
const head = document.querySelector('head');
6+
if (resource.type === '_js_dist') {
7+
const element = document.createElement('script');
8+
element.src = resource.url;
9+
element.async = true;
10+
prom = new Promise<void>((resolve, reject) => {
11+
element.onload = () => {
12+
resolve();
13+
};
14+
element.onerror = error => reject(error);
15+
});
16+
17+
head?.appendChild(element);
18+
} else if (resource.type === '_css_dist') {
19+
const element = document.createElement('link');
20+
element.href = resource.url;
21+
element.rel = 'stylesheet';
22+
prom = new Promise<void>((resolve, reject) => {
23+
element.onload = () => {
24+
resolve();
25+
};
26+
element.onerror = error => reject(error);
27+
});
28+
head?.appendChild(element);
29+
}
30+
return prom;
31+
}

0 commit comments

Comments
 (0)