@servicetitan/web-components 23.0.0 → 23.2.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/dist/__mocks__/index.d.ts +3 -0
- package/dist/__mocks__/index.d.ts.map +1 -0
- package/dist/__mocks__/index.js +3 -0
- package/dist/__mocks__/index.js.map +1 -0
- package/dist/__mocks__/register-component.d.ts +4 -0
- package/dist/__mocks__/register-component.d.ts.map +1 -0
- package/dist/__mocks__/register-component.js +14 -0
- package/dist/__mocks__/register-component.js.map +1 -0
- package/dist/__mocks__/test-component.d.ts +3 -0
- package/dist/__mocks__/test-component.d.ts.map +1 -0
- package/dist/__mocks__/test-component.js +14 -0
- package/dist/__mocks__/test-component.js.map +1 -0
- package/dist/contexts/tests/mfe-data-context.test.js +1 -1
- package/dist/contexts/tests/mfe-data-context.test.js.map +1 -1
- package/dist/contexts/tests/mfe-metadata-context.test.js +1 -1
- package/dist/contexts/tests/mfe-metadata-context.test.js.map +1 -1
- package/dist/loader.d.ts +7 -17
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +74 -224
- package/dist/loader.js.map +1 -1
- package/dist/register.d.ts +4 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +26 -13
- package/dist/register.js.map +1 -1
- package/dist/render.d.ts +11 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +39 -0
- package/dist/render.js.map +1 -0
- package/dist/utils/get-bundle-info.d.ts +24 -0
- package/dist/utils/get-bundle-info.d.ts.map +1 -0
- package/dist/utils/get-bundle-info.js +98 -0
- package/dist/utils/get-bundle-info.js.map +1 -0
- package/dist/utils/get-entry-points.d.ts +6 -0
- package/dist/utils/get-entry-points.d.ts.map +1 -0
- package/dist/utils/get-entry-points.js +24 -0
- package/dist/utils/get-entry-points.js.map +1 -0
- package/dist/{get-mfe-supports-rerendering.d.ts → utils/get-mfe-supports-rerendering.d.ts} +1 -1
- package/dist/utils/get-mfe-supports-rerendering.d.ts.map +1 -0
- package/dist/utils/get-mfe-supports-rerendering.js.map +1 -0
- package/dist/{utils.d.ts → utils/get-version-mismatches.d.ts} +2 -3
- package/dist/utils/get-version-mismatches.d.ts.map +1 -0
- package/dist/{utils.js → utils/get-version-mismatches.js} +2 -10
- package/dist/utils/get-version-mismatches.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/is-versioned-url.d.ts +2 -0
- package/dist/utils/is-versioned-url.d.ts.map +1 -0
- package/dist/utils/is-versioned-url.js +9 -0
- package/dist/utils/is-versioned-url.js.map +1 -0
- package/dist/utils/load-scripts.d.ts +3 -0
- package/dist/utils/load-scripts.d.ts.map +1 -0
- package/dist/utils/load-scripts.js +31 -0
- package/dist/utils/load-scripts.js.map +1 -0
- package/dist/utils/validate-bundle-info.d.ts +7 -0
- package/dist/utils/validate-bundle-info.d.ts.map +1 -0
- package/dist/utils/validate-bundle-info.js +10 -0
- package/dist/utils/validate-bundle-info.js.map +1 -0
- package/dist/utils/web-component.d.ts +17 -0
- package/dist/utils/web-component.d.ts.map +1 -0
- package/dist/utils/web-component.js +54 -0
- package/dist/utils/web-component.js.map +1 -0
- package/package.json +17 -18
- package/src/__mocks__/index.ts +2 -0
- package/src/__mocks__/register-component.tsx +20 -0
- package/src/__mocks__/test-component.tsx +30 -0
- package/src/__tests__/event-bus.test.ts +3 -2
- package/src/__tests__/loader.test.tsx +17 -11
- package/src/__tests__/register-legacy-root.test.tsx +36 -0
- package/src/__tests__/register.test.tsx +16 -81
- package/src/contexts/tests/mfe-data-context.test.tsx +4 -4
- package/src/contexts/tests/mfe-metadata-context.test.tsx +4 -4
- package/src/loader.tsx +129 -374
- package/src/register.tsx +25 -14
- package/src/render.ts +62 -0
- package/src/{__tests__ → utils/__tests__}/get-mfe-supports-rerendering.test.ts +1 -1
- package/src/utils/__tests__/get-version-mismatches.test.ts +76 -0
- package/src/utils/__tests__/is-versioned-url.test.ts +18 -0
- package/src/utils/get-bundle-info.ts +121 -0
- package/src/utils/get-entry-points.ts +23 -0
- package/src/{get-mfe-supports-rerendering.ts → utils/get-mfe-supports-rerendering.ts} +1 -1
- package/src/{utils.ts → utils/get-version-mismatches.ts} +1 -12
- package/src/utils/index.ts +8 -0
- package/src/utils/is-versioned-url.ts +11 -0
- package/src/utils/load-scripts.ts +38 -0
- package/src/utils/validate-bundle-info.ts +15 -0
- package/src/utils/web-component.tsx +96 -0
- package/dist/get-mfe-supports-rerendering.d.ts.map +0 -1
- package/dist/get-mfe-supports-rerendering.js.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/__tests__/utils.test.ts +0 -95
- /package/dist/{get-mfe-supports-rerendering.js → utils/get-mfe-supports-rerendering.js} +0 -0
package/src/loader.tsx
CHANGED
|
@@ -1,425 +1,180 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ReactElement,
|
|
3
|
+
ReactNode,
|
|
3
4
|
Suspense,
|
|
4
|
-
useEffect,
|
|
5
|
-
useMemo,
|
|
6
|
-
useState,
|
|
7
5
|
forwardRef,
|
|
6
|
+
lazy,
|
|
7
|
+
useEffect,
|
|
8
8
|
useImperativeHandle,
|
|
9
|
-
|
|
10
|
-
ReactNode,
|
|
11
|
-
Ref,
|
|
9
|
+
useMemo,
|
|
12
10
|
} from 'react';
|
|
13
|
-
|
|
14
|
-
import semver from 'semver';
|
|
15
|
-
|
|
16
|
-
import get from 'lodash.get';
|
|
17
|
-
|
|
18
11
|
import { ErrorBoundary } from '@servicetitan/error-boundary';
|
|
19
12
|
import { Log } from '@servicetitan/log-service';
|
|
20
13
|
import { useOptionalDependencies } from '@servicetitan/react-ioc';
|
|
21
14
|
|
|
22
15
|
import {
|
|
23
|
-
ExposedDependencies,
|
|
24
16
|
EXPOSED_DEPENDENCIES_TOKEN,
|
|
25
|
-
|
|
26
|
-
getJson,
|
|
17
|
+
ExposedDependencies,
|
|
27
18
|
GetJsonError,
|
|
28
|
-
|
|
19
|
+
useCancelation,
|
|
29
20
|
} from './common';
|
|
21
|
+
import { MFEContextData } from './contexts/mfe-data-context';
|
|
30
22
|
import { EventBus, EVENT_BUS_TOKEN, useEventBus } from './event-bus';
|
|
31
|
-
import { HistoryManager } from './history-manager';
|
|
32
23
|
import { EXPOSED_DEPENDENCIES } from './globals';
|
|
33
|
-
import {
|
|
34
|
-
import { MFEContextData } from './contexts/mfe-data-context';
|
|
35
|
-
import { getMFESupportsRerendering } from './get-mfe-supports-rerendering';
|
|
36
|
-
|
|
37
|
-
export interface Metadata {
|
|
38
|
-
name: string;
|
|
39
|
-
bundledWith?: Record<string, string | undefined>;
|
|
40
|
-
sharedDependencies: Record<string, string>;
|
|
41
|
-
dependencies?: Record<string, string>;
|
|
42
|
-
entrypoints?: Record<string, { css: string[]; js: string[] }>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface EntryPoints {
|
|
46
|
-
css: string[];
|
|
47
|
-
js: string[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function getEntrypoints(
|
|
51
|
-
url: string,
|
|
52
|
-
cache: 'no-store' | undefined,
|
|
53
|
-
signal?: AbortSignal
|
|
54
|
-
): Promise<EntryPoints> {
|
|
55
|
-
try {
|
|
56
|
-
return (
|
|
57
|
-
await getJson(url, {
|
|
58
|
-
cache,
|
|
59
|
-
signal,
|
|
60
|
-
})
|
|
61
|
-
).data;
|
|
62
|
-
} catch {
|
|
63
|
-
return { css: ['index.css'], js: ['index.js'] };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function getBundleInfo(
|
|
68
|
-
mainPackageUrl: string,
|
|
69
|
-
fallbackPackageUrl: string | undefined,
|
|
70
|
-
exposedDependencies: ExposedDependencies = {},
|
|
71
|
-
signal?: AbortSignal,
|
|
72
|
-
retries?: number,
|
|
73
|
-
onRequestError?: (error: GetJsonError) => void
|
|
74
|
-
) {
|
|
75
|
-
const { data: metadata, url: packageUrl } = await getJson<Metadata>(
|
|
76
|
-
`${mainPackageUrl}/dist/metadata.json`,
|
|
77
|
-
{
|
|
78
|
-
cache: isVersionedUrl(mainPackageUrl) ? undefined : 'no-store',
|
|
79
|
-
retries: fallbackPackageUrl ? 0 : retries,
|
|
80
|
-
onRequestError,
|
|
81
|
-
signal,
|
|
82
|
-
}
|
|
83
|
-
).catch(e => {
|
|
84
|
-
if (!fallbackPackageUrl) {
|
|
85
|
-
throw e;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return getJson(`${fallbackPackageUrl}/dist/metadata.json`, {
|
|
89
|
-
retries,
|
|
90
|
-
onRequestError,
|
|
91
|
-
cache: isVersionedUrl(fallbackPackageUrl) ? undefined : 'no-store',
|
|
92
|
-
signal,
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const exactPackageUrl = packageUrl.replace('/dist/metadata.json', '');
|
|
97
|
-
const cache = isVersionedUrl(exactPackageUrl) ? undefined : 'no-store';
|
|
98
|
-
|
|
99
|
-
let dependencies: NonNullable<Metadata['dependencies']>;
|
|
100
|
-
|
|
101
|
-
if (metadata.dependencies) {
|
|
102
|
-
dependencies = metadata.dependencies;
|
|
103
|
-
} else {
|
|
104
|
-
const { data: packageJson } = await getJson(`${exactPackageUrl}/package.json`, {
|
|
105
|
-
retries,
|
|
106
|
-
cache,
|
|
107
|
-
signal,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
dependencies = packageJson.dependencies;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const mismatch = getVersionMismatches(
|
|
114
|
-
exposedDependencies,
|
|
115
|
-
dependencies,
|
|
116
|
-
metadata.sharedDependencies
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const hasMismatch = !!Object.keys(mismatch).length;
|
|
120
|
-
const startupVersion = metadata.bundledWith?.['@servicetitan/startup'];
|
|
121
|
-
const supportLightBundles = startupVersion && semver.gte(startupVersion, '17.0.0');
|
|
122
|
-
const embed = !hasMismatch && supportLightBundles;
|
|
123
|
-
const bundleType = embed ? 'light' : 'full';
|
|
124
|
-
const withBaseUrl = (path: string) => {
|
|
125
|
-
return `${exactPackageUrl}/dist/bundle/${bundleType}/${path}`;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
let entrypoints: EntryPoints;
|
|
129
|
-
|
|
130
|
-
if (metadata.entrypoints?.[bundleType]) {
|
|
131
|
-
entrypoints = metadata.entrypoints[bundleType];
|
|
132
|
-
} else {
|
|
133
|
-
entrypoints = await getEntrypoints(withBaseUrl('entrypoints.json'), cache);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
urls: {
|
|
138
|
-
css: entrypoints.css.map(withBaseUrl),
|
|
139
|
-
js: entrypoints.js.map(withBaseUrl),
|
|
140
|
-
},
|
|
141
|
-
WebComponent: metadata.name,
|
|
142
|
-
disposer: () => {
|
|
143
|
-
if (embed) {
|
|
144
|
-
const path = Object.values<string>(metadata.sharedDependencies).shift();
|
|
145
|
-
if (path) {
|
|
146
|
-
get(window, path).getStorage().clear(metadata.name);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
mismatch: hasMismatch ? mismatch : undefined,
|
|
151
|
-
mfeSupportsRerendering: getMFESupportsRerendering(metadata.bundledWith, dependencies),
|
|
152
|
-
};
|
|
153
|
-
}
|
|
24
|
+
import { BundleInfo, getBundleInfo, validateBundleInfo, loadScripts, webComponent } from './utils';
|
|
154
25
|
|
|
155
26
|
export type LoaderRef = EventBus;
|
|
156
27
|
|
|
28
|
+
export type FallbackNode = NonNullable<ReactNode> | null;
|
|
29
|
+
|
|
157
30
|
export interface LoaderProps<T> {
|
|
158
31
|
src: string;
|
|
159
32
|
fallbackSrc?: string;
|
|
160
33
|
/** Values in the object will be stringified and then parsed before being delivered to the MFE via props or context. */
|
|
161
34
|
data?: MFEContextData<T>;
|
|
162
35
|
basename?: string;
|
|
163
|
-
loadingFallback?:
|
|
164
|
-
errorFallback?:
|
|
36
|
+
loadingFallback?: FallbackNode;
|
|
37
|
+
errorFallback?: FallbackNode;
|
|
165
38
|
className?: string;
|
|
166
39
|
retries?: number;
|
|
167
40
|
onRequestError?(error: GetJsonError): boolean;
|
|
168
41
|
}
|
|
169
42
|
|
|
170
|
-
type
|
|
43
|
+
export type LoaderDataProps = Pick<LoaderProps<{}>, 'data' | 'className'>;
|
|
171
44
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
forwardRef<LoaderRef, LoaderProps<{}>>(
|
|
175
|
-
(
|
|
176
|
-
{
|
|
177
|
-
src,
|
|
178
|
-
fallbackSrc,
|
|
179
|
-
data = {},
|
|
180
|
-
basename,
|
|
181
|
-
loadingFallback,
|
|
182
|
-
errorFallback,
|
|
183
|
-
className,
|
|
184
|
-
retries,
|
|
185
|
-
onRequestError,
|
|
186
|
-
},
|
|
187
|
-
ref
|
|
188
|
-
) => {
|
|
189
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
190
|
-
|
|
191
|
-
const bus = useEventBus();
|
|
192
|
-
const [
|
|
193
|
-
logService,
|
|
194
|
-
historyManager,
|
|
195
|
-
exposedDependencies = EXPOSED_DEPENDENCIES,
|
|
196
|
-
eventBus = bus,
|
|
197
|
-
] = useOptionalDependencies(
|
|
198
|
-
Log,
|
|
199
|
-
HistoryManager,
|
|
200
|
-
EXPOSED_DEPENDENCIES_TOKEN,
|
|
201
|
-
EVENT_BUS_TOKEN
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const Loader = useMemo(() => {
|
|
205
|
-
const Component = forwardRef<LoaderRef, DataProps>((props: DataProps, ref) => {
|
|
206
|
-
const cancelation = useCancelation();
|
|
207
|
-
|
|
208
|
-
useImperativeHandle(ref, () => eventBus, []);
|
|
209
|
-
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
setIsLoading(true);
|
|
212
|
-
|
|
213
|
-
return () => {
|
|
214
|
-
cancelation.abort();
|
|
215
|
-
};
|
|
216
|
-
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
217
|
-
|
|
218
|
-
const Lazy = useMemo(
|
|
219
|
-
() =>
|
|
220
|
-
lazy<(props: DataProps) => JSX.Element | null>(async () => {
|
|
221
|
-
let bundleInfo: Awaited<ReturnType<typeof getBundleInfo>>;
|
|
222
|
-
try {
|
|
223
|
-
bundleInfo = await getBundleInfo(
|
|
224
|
-
src,
|
|
225
|
-
fallbackSrc,
|
|
226
|
-
exposedDependencies,
|
|
227
|
-
cancelation.signal,
|
|
228
|
-
retries,
|
|
229
|
-
onRequestError
|
|
230
|
-
);
|
|
231
|
-
} catch (e) {
|
|
232
|
-
if (cancelation.signal.aborted) {
|
|
233
|
-
return {
|
|
234
|
-
default: () => null,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
throw e;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const {
|
|
242
|
-
urls,
|
|
243
|
-
WebComponent,
|
|
244
|
-
disposer,
|
|
245
|
-
mismatch,
|
|
246
|
-
mfeSupportsRerendering,
|
|
247
|
-
} = bundleInfo;
|
|
45
|
+
export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
|
|
46
|
+
const { basename, fallbackSrc, onRequestError, retries, src } = props;
|
|
248
47
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
'Some of the package dependencies have incompatible versions.',
|
|
254
|
-
data: {
|
|
255
|
-
package: src,
|
|
256
|
-
dependencies: JSON.stringify(mismatch),
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
}
|
|
48
|
+
const bus = useEventBus();
|
|
49
|
+
const [logService, exposedDependencies = EXPOSED_DEPENDENCIES, eventBus = bus] =
|
|
50
|
+
useOptionalDependencies(Log, EXPOSED_DEPENDENCIES_TOKEN, EVENT_BUS_TOKEN);
|
|
51
|
+
const cancelation = useCancelation();
|
|
260
52
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
266
|
-
if (!script) {
|
|
267
|
-
return new Promise<void>(resolve => {
|
|
268
|
-
const script = document.createElement('script');
|
|
53
|
+
const LoadingFallback = useMemo(
|
|
54
|
+
() => getFallback(props.loadingFallback, 'Loading...'),
|
|
55
|
+
[props.loadingFallback]
|
|
56
|
+
);
|
|
269
57
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
resolve();
|
|
275
|
-
};
|
|
276
|
-
script.async = true;
|
|
277
|
-
script.src = url;
|
|
58
|
+
const ErrorFallback = useMemo(
|
|
59
|
+
() => getFallback(props.errorFallback, 'Something went wrong'),
|
|
60
|
+
[props.errorFallback]
|
|
61
|
+
);
|
|
278
62
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
63
|
+
const Loader = useMemo(
|
|
64
|
+
() =>
|
|
65
|
+
lazyLoader({
|
|
66
|
+
basename,
|
|
67
|
+
cancelation,
|
|
68
|
+
exposedDependencies,
|
|
69
|
+
eventBus,
|
|
70
|
+
fallbackSrc,
|
|
71
|
+
logService,
|
|
72
|
+
onRequestError,
|
|
73
|
+
retries,
|
|
74
|
+
src,
|
|
75
|
+
LoadingFallback,
|
|
76
|
+
}),
|
|
77
|
+
[
|
|
78
|
+
basename,
|
|
79
|
+
cancelation,
|
|
80
|
+
exposedDependencies,
|
|
81
|
+
eventBus,
|
|
82
|
+
logService,
|
|
83
|
+
onRequestError,
|
|
84
|
+
fallbackSrc,
|
|
85
|
+
retries,
|
|
86
|
+
src,
|
|
87
|
+
LoadingFallback,
|
|
88
|
+
]
|
|
89
|
+
);
|
|
291
90
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
91
|
+
return (
|
|
92
|
+
<ErrorBoundary moduleName={src} fallback={ErrorFallback}>
|
|
93
|
+
<Suspense fallback={<LoadingFallback />}>
|
|
94
|
+
<Loader ref={ref} data={props.data ?? {}} className={props.className} />
|
|
95
|
+
</Suspense>
|
|
96
|
+
</ErrorBoundary>
|
|
97
|
+
);
|
|
98
|
+
});
|
|
295
99
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
onReady: () => {
|
|
304
|
-
setIsLoading(false);
|
|
305
|
-
},
|
|
306
|
-
onDispose: () => {
|
|
307
|
-
disposer();
|
|
308
|
-
},
|
|
309
|
-
});
|
|
310
|
-
};
|
|
100
|
+
type LazyLoaderProps = Omit<LoaderProps<any>, 'loadingFallback'> & {
|
|
101
|
+
cancelation: AbortController;
|
|
102
|
+
exposedDependencies?: ExposedDependencies;
|
|
103
|
+
eventBus: EventBus;
|
|
104
|
+
logService?: Log;
|
|
105
|
+
LoadingFallback: () => JSX.Element;
|
|
106
|
+
};
|
|
311
107
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const stringifiedData = useMemo(
|
|
315
|
-
() => JSON.stringify(props.data),
|
|
316
|
-
[props.data]
|
|
317
|
-
);
|
|
108
|
+
function lazyLoader(props: LazyLoaderProps) {
|
|
109
|
+
const { cancelation, eventBus } = props;
|
|
318
110
|
|
|
319
|
-
|
|
320
|
-
* Set key for rendering the web component so that if the data from the Host change,
|
|
321
|
-
* and the MFE's version of startup doesn't support watching for data changes, then
|
|
322
|
-
* the web component will be re-mounted whenever the data changes.
|
|
323
|
-
* This is only used as a fallback when the MFE's version is older.
|
|
324
|
-
*/
|
|
325
|
-
const key = mfeSupportsRerendering
|
|
326
|
-
? undefined
|
|
327
|
-
: stringifiedData;
|
|
111
|
+
const LazyWebComponent = lazyWebComponent(props);
|
|
328
112
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
* Ex: fooBar -> data-foo-bar
|
|
332
|
-
*/
|
|
333
|
-
const dataAttrs = useMemo(
|
|
334
|
-
() =>
|
|
335
|
-
Object.fromEntries(
|
|
336
|
-
Object.entries(props.data ?? {}).map(
|
|
337
|
-
([key, value]) => [
|
|
338
|
-
'data-' +
|
|
339
|
-
key.replace(
|
|
340
|
-
/[A-Z]/g,
|
|
341
|
-
letter =>
|
|
342
|
-
'-' + letter.toLowerCase()
|
|
343
|
-
),
|
|
344
|
-
value,
|
|
345
|
-
]
|
|
346
|
-
)
|
|
347
|
-
),
|
|
348
|
-
[props.data]
|
|
349
|
-
);
|
|
113
|
+
return forwardRef<LoaderRef, LoaderDataProps>((props: LoaderDataProps, ref) => {
|
|
114
|
+
useImperativeHandle(ref, () => eventBus, []);
|
|
350
115
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
{...(urls.css.length
|
|
357
|
-
? {
|
|
358
|
-
'style-urls': encodeURIComponent(
|
|
359
|
-
JSON.stringify(urls.css)
|
|
360
|
-
),
|
|
361
|
-
}
|
|
362
|
-
: {})}
|
|
363
|
-
/*
|
|
364
|
-
* Continue to spread data as separate data attributes for backwards compatibility,
|
|
365
|
-
* in case the Host has updated web-components, but the MFE has not
|
|
366
|
-
*/
|
|
367
|
-
{...dataAttrs}
|
|
368
|
-
// Stringify data object so that it can be passed as a data-attribute
|
|
369
|
-
data-mfe-data={stringifiedData}
|
|
370
|
-
/>
|
|
371
|
-
);
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
}),
|
|
375
|
-
[] // eslint-disable-line react-hooks/exhaustive-deps
|
|
376
|
-
);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
return () => {
|
|
118
|
+
cancelation.abort();
|
|
119
|
+
};
|
|
120
|
+
}, []);
|
|
377
121
|
|
|
378
|
-
|
|
379
|
-
|
|
122
|
+
return <LazyWebComponent data={props.data} className={props.className} />;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
380
125
|
|
|
381
|
-
|
|
382
|
-
|
|
126
|
+
function lazyWebComponent(props: LazyLoaderProps) {
|
|
127
|
+
const {
|
|
128
|
+
basename,
|
|
129
|
+
cancelation,
|
|
130
|
+
exposedDependencies,
|
|
131
|
+
eventBus,
|
|
132
|
+
fallbackSrc,
|
|
133
|
+
logService,
|
|
134
|
+
onRequestError,
|
|
135
|
+
retries,
|
|
136
|
+
src,
|
|
137
|
+
LoadingFallback,
|
|
138
|
+
} = props;
|
|
139
|
+
|
|
140
|
+
return lazy<(props: LoaderDataProps) => JSX.Element | null>(async () => {
|
|
141
|
+
let bundleInfo: BundleInfo;
|
|
142
|
+
try {
|
|
143
|
+
bundleInfo = await getBundleInfo(
|
|
383
144
|
src,
|
|
384
145
|
fallbackSrc,
|
|
385
146
|
exposedDependencies,
|
|
147
|
+
cancelation.signal,
|
|
386
148
|
retries,
|
|
387
|
-
onRequestError
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
149
|
+
onRequestError
|
|
150
|
+
);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
if (cancelation.signal.aborted) {
|
|
153
|
+
return {
|
|
154
|
+
default: () => null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
393
157
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (loadingFallback !== undefined) {
|
|
397
|
-
return loadingFallback as ReactElement;
|
|
398
|
-
}
|
|
158
|
+
throw e;
|
|
159
|
+
}
|
|
399
160
|
|
|
400
|
-
|
|
401
|
-
},
|
|
402
|
-
[loadingFallback]
|
|
403
|
-
);
|
|
161
|
+
validateBundleInfo(bundleInfo, { logService, packageName: src });
|
|
404
162
|
|
|
405
|
-
|
|
406
|
-
() => () => {
|
|
407
|
-
if (errorFallback !== undefined) {
|
|
408
|
-
return errorFallback as ReactElement;
|
|
409
|
-
}
|
|
163
|
+
await loadScripts(bundleInfo);
|
|
410
164
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
165
|
+
return {
|
|
166
|
+
default: webComponent({
|
|
167
|
+
basename,
|
|
168
|
+
bundleInfo,
|
|
169
|
+
eventBus,
|
|
170
|
+
exposedDependencies,
|
|
171
|
+
logService,
|
|
172
|
+
LoadingFallback,
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
415
177
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
<Suspense fallback={null}>
|
|
420
|
-
<Loader ref={ref} data={data} className={className} />
|
|
421
|
-
</Suspense>
|
|
422
|
-
</ErrorBoundary>
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
);
|
|
178
|
+
function getFallback(node: FallbackNode | undefined, message: string): () => JSX.Element {
|
|
179
|
+
return () => (node !== undefined ? (node as ReactElement) : <h2>{message}</h2>);
|
|
180
|
+
}
|
package/src/register.tsx
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { FC, ComponentType } from 'react';
|
|
2
|
-
import { render, unmountComponentAtNode } from 'react-dom';
|
|
3
|
-
|
|
4
|
-
import { Log } from '@servicetitan/log-service';
|
|
1
|
+
import { FC, ComponentType, useEffect } from 'react';
|
|
5
2
|
import { DefaultPortalContext, TablePopupPropsContext } from '@servicetitan/design-system';
|
|
3
|
+
import { Log } from '@servicetitan/log-service';
|
|
6
4
|
import { Provider } from '@servicetitan/react-ioc';
|
|
7
5
|
|
|
8
6
|
import {
|
|
@@ -14,11 +12,12 @@ import {
|
|
|
14
12
|
IS_WEB_COMPONENT_TOKEN,
|
|
15
13
|
RENDER_ROOT_TOKEN,
|
|
16
14
|
} from './common';
|
|
17
|
-
import { EventBus, EVENT_BUS_TOKEN } from './event-bus';
|
|
18
|
-
import { HistoryManager } from './history-manager';
|
|
19
|
-
import { WEB_COMPONENT_NAME } from './globals';
|
|
20
15
|
import { MFEDataContext } from './contexts/mfe-data-context';
|
|
21
16
|
import { MFEMetadataContext } from './contexts/mfe-metadata-context';
|
|
17
|
+
import { EventBus, EVENT_BUS_TOKEN } from './event-bus';
|
|
18
|
+
import { WEB_COMPONENT_NAME } from './globals';
|
|
19
|
+
import { HistoryManager } from './history-manager';
|
|
20
|
+
import { render } from './render';
|
|
22
21
|
|
|
23
22
|
function createLink(url: string, onLoad: () => void) {
|
|
24
23
|
const element = document.createElement('link');
|
|
@@ -34,7 +33,11 @@ function createLink(url: string, onLoad: () => void) {
|
|
|
34
33
|
|
|
35
34
|
const Placeholder: FC = () => <div style={{ display: 'none' }} />;
|
|
36
35
|
|
|
37
|
-
export
|
|
36
|
+
export interface RegisterOptions {
|
|
37
|
+
legacyRoot?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function register(Component: ComponentType, light: boolean, options?: RegisterOptions) {
|
|
38
41
|
class WebComponent extends HTMLElement implements IWebComponent {
|
|
39
42
|
private root?: ShadowRoot;
|
|
40
43
|
private portal?: ShadowRoot;
|
|
@@ -52,6 +55,7 @@ export function register(Component: ComponentType, light: boolean) {
|
|
|
52
55
|
private basename?: string;
|
|
53
56
|
private onReady?: () => void;
|
|
54
57
|
private onDispose?: () => void;
|
|
58
|
+
private view?: ReturnType<typeof render>;
|
|
55
59
|
|
|
56
60
|
connectedCallback() {
|
|
57
61
|
/*
|
|
@@ -92,7 +96,7 @@ export function register(Component: ComponentType, light: boolean) {
|
|
|
92
96
|
.appendChild(document.createElement('div'))
|
|
93
97
|
.attachShadow({ mode: 'open' });
|
|
94
98
|
|
|
95
|
-
render(<Placeholder />, this.root);
|
|
99
|
+
this.view = render(<Placeholder />, this.root, options);
|
|
96
100
|
|
|
97
101
|
this.styles.elements.push(...this.attachStyles(this.root, styleUrls));
|
|
98
102
|
this.styles.elements.push(...this.attachStyles(this.portal, styleUrls));
|
|
@@ -106,7 +110,7 @@ export function register(Component: ComponentType, light: boolean) {
|
|
|
106
110
|
element.remove();
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
|
|
113
|
+
this.view?.unmount();
|
|
110
114
|
document.body.removeChild(this.portal!.host);
|
|
111
115
|
this.root = undefined;
|
|
112
116
|
this.portal = undefined;
|
|
@@ -179,7 +183,7 @@ export function register(Component: ComponentType, light: boolean) {
|
|
|
179
183
|
portalShadowRoot: this.portal!,
|
|
180
184
|
};
|
|
181
185
|
|
|
182
|
-
|
|
186
|
+
this.view?.rerender(
|
|
183
187
|
<Provider
|
|
184
188
|
singletons={[
|
|
185
189
|
{ provide: IS_WEB_COMPONENT_TOKEN, useValue: true },
|
|
@@ -210,14 +214,15 @@ export function register(Component: ComponentType, light: boolean) {
|
|
|
210
214
|
<MFEDataContext.Provider value={{ ...mfeData }}>
|
|
211
215
|
<MFEMetadataContext.Provider value={metadata}>
|
|
212
216
|
<Component {...mfeData} />
|
|
217
|
+
{this.onReady && (
|
|
218
|
+
<RenderCallback callback={this.onReady} />
|
|
219
|
+
)}
|
|
213
220
|
</MFEMetadataContext.Provider>
|
|
214
221
|
</MFEDataContext.Provider>
|
|
215
222
|
</Provider>
|
|
216
223
|
</TablePopupPropsContext.Provider>
|
|
217
224
|
</DefaultPortalContext.Provider>
|
|
218
|
-
</Provider
|
|
219
|
-
this.root!,
|
|
220
|
-
this.onReady
|
|
225
|
+
</Provider>
|
|
221
226
|
);
|
|
222
227
|
}
|
|
223
228
|
};
|
|
@@ -231,3 +236,9 @@ export function register(Component: ComponentType, light: boolean) {
|
|
|
231
236
|
|
|
232
237
|
window.customElements.define(WEB_COMPONENT_NAME, WebComponent);
|
|
233
238
|
}
|
|
239
|
+
|
|
240
|
+
function RenderCallback({ callback }: { callback: () => void }) {
|
|
241
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
242
|
+
useEffect(() => callback(), []);
|
|
243
|
+
return null;
|
|
244
|
+
}
|