@servicetitan/web-components 32.3.2 → 32.4.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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/use-prefetch.d.ts +10 -0
- package/dist/use-prefetch.d.ts.map +1 -0
- package/dist/use-prefetch.js +71 -0
- package/dist/use-prefetch.js.map +1 -0
- package/dist/utils/get-bundle-info.d.ts +1 -0
- package/dist/utils/get-bundle-info.d.ts.map +1 -1
- package/dist/utils/get-bundle-info.js +2 -1
- package/dist/utils/get-bundle-info.js.map +1 -1
- package/dist/utils/get-cached-metadata.d.ts +7 -3
- package/dist/utils/get-cached-metadata.d.ts.map +1 -1
- package/dist/utils/get-cached-metadata.js +14 -10
- package/dist/utils/get-cached-metadata.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/supports-prefetch.d.ts +2 -0
- package/dist/utils/supports-prefetch.d.ts.map +1 -0
- package/dist/utils/supports-prefetch.js +11 -0
- package/dist/utils/supports-prefetch.js.map +1 -0
- package/dist/utils/ttl-cache.d.ts +3 -3
- package/dist/utils/ttl-cache.d.ts.map +1 -1
- package/dist/utils/ttl-cache.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/use-prefetch.test.tsx +235 -0
- package/src/index.ts +1 -0
- package/src/use-prefetch.ts +74 -0
- package/src/utils/__tests__/supports-prefetch.test.ts +27 -0
- package/src/utils/get-bundle-info.ts +6 -1
- package/src/utils/get-cached-metadata.ts +23 -21
- package/src/utils/index.ts +1 -0
- package/src/utils/supports-prefetch.ts +9 -0
- package/src/utils/ttl-cache.ts +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export * from './history-manager';
|
|
|
7
7
|
export * from './loader';
|
|
8
8
|
export * from './register';
|
|
9
9
|
export * from './register-headless';
|
|
10
|
+
export * from './use-prefetch';
|
|
10
11
|
export type { JSON, JSONPrimitive, Serializable } from './types';
|
|
11
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAE/B,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './common';\nexport * from './contexts';\nexport * from './event-bus';\nexport * from './get-value-for-environment';\nexport * from './headless-loader';\nexport * from './history-manager';\nexport * from './loader';\nexport * from './register';\nexport * from './register-headless';\n\nexport type { JSON, JSONPrimitive, Serializable } from './types';\n"],"names":[],"mappings":"AAAA,cAAc,WAAW;AACzB,cAAc,aAAa;AAC3B,cAAc,cAAc;AAC5B,cAAc,8BAA8B;AAC5C,cAAc,oBAAoB;AAClC,cAAc,oBAAoB;AAClC,cAAc,WAAW;AACzB,cAAc,aAAa;AAC3B,cAAc,sBAAsB"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './common';\nexport * from './contexts';\nexport * from './event-bus';\nexport * from './get-value-for-environment';\nexport * from './headless-loader';\nexport * from './history-manager';\nexport * from './loader';\nexport * from './register';\nexport * from './register-headless';\nexport * from './use-prefetch';\n\nexport type { JSON, JSONPrimitive, Serializable } from './types';\n"],"names":[],"mappings":"AAAA,cAAc,WAAW;AACzB,cAAc,aAAa;AAC3B,cAAc,cAAc;AAC5B,cAAc,8BAA8B;AAC5C,cAAc,oBAAoB;AAClC,cAAc,oBAAoB;AAClC,cAAc,WAAW;AACzB,cAAc,aAAa;AAC3B,cAAc,sBAAsB;AACpC,cAAc,iBAAiB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LoaderProps } from './loader';
|
|
2
|
+
/**
|
|
3
|
+
* Prefetch and cache bundle info for a given MFE, so that Loader can load it faster later.
|
|
4
|
+
* @param src - The main package URL for the MFE
|
|
5
|
+
* @returns Promise resolving to the BundleInfo
|
|
6
|
+
*/
|
|
7
|
+
export declare function usePrefetch(): {
|
|
8
|
+
prefetch: (src: string, { cache }?: Pick<LoaderProps<any>, "cache">) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=use-prefetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-prefetch.d.ts","sourceRoot":"","sources":["../src/use-prefetch.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;GAIG;AACH,wBAAgB,WAAW;oBAQM,MAAM,cAAkB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;EAiCvF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Log } from '@servicetitan/log-service';
|
|
2
|
+
import { useOptionalDependencies } from '@servicetitan/react-ioc';
|
|
3
|
+
import { EXPOSED_DEPENDENCIES_TOKEN, EXPOSED_INSTANCE_DEPENDENCIES_TOKEN } from './common';
|
|
4
|
+
import { getBundleInfo, supportsPrefetch } from './utils';
|
|
5
|
+
/**
|
|
6
|
+
* Prefetch and cache bundle info for a given MFE, so that Loader can load it faster later.
|
|
7
|
+
* @param src - The main package URL for the MFE
|
|
8
|
+
* @returns Promise resolving to the BundleInfo
|
|
9
|
+
*/ export function usePrefetch() {
|
|
10
|
+
const [logService, exposedDependencies = EXPOSED_DEPENDENCIES] = useOptionalDependencies(Log, EXPOSED_DEPENDENCIES_TOKEN);
|
|
11
|
+
const [exposedInstanceDependencies = EXPOSED_INSTANCE_DEPENDENCIES] = useOptionalDependencies(EXPOSED_INSTANCE_DEPENDENCIES_TOKEN);
|
|
12
|
+
const prefetch = async (src, { cache = -1 } = {})=>{
|
|
13
|
+
try {
|
|
14
|
+
const bundleInfo = await getBundleInfo({
|
|
15
|
+
exposedDependencies,
|
|
16
|
+
exposedInstanceDependencies,
|
|
17
|
+
mainPackageUrl: src,
|
|
18
|
+
cache,
|
|
19
|
+
retries: 0
|
|
20
|
+
});
|
|
21
|
+
if (bundleInfo.cacheHit) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const { css, js } = bundleInfo.urls;
|
|
25
|
+
if (supportsPrefetch()) {
|
|
26
|
+
addLinks([
|
|
27
|
+
...css.map((href)=>({
|
|
28
|
+
href,
|
|
29
|
+
as: 'style'
|
|
30
|
+
})),
|
|
31
|
+
...js.map((href)=>({
|
|
32
|
+
href,
|
|
33
|
+
as: 'script'
|
|
34
|
+
}))
|
|
35
|
+
]);
|
|
36
|
+
} else {
|
|
37
|
+
await Promise.all([
|
|
38
|
+
...css,
|
|
39
|
+
...js
|
|
40
|
+
].map((url)=>fetch(url, {
|
|
41
|
+
priority: 'low'
|
|
42
|
+
})));
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
logService === null || logService === void 0 ? void 0 : logService.warning({
|
|
46
|
+
category: 'Microfrontends.Prefetch',
|
|
47
|
+
message: `Failed to prefetch ${src}`,
|
|
48
|
+
data: e
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
prefetch
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function addLinks(links) {
|
|
57
|
+
const existingLinks = document.head.getElementsByTagName('link');
|
|
58
|
+
const existingHrefs = new Set(Array.from(existingLinks).map(({ href })=>href));
|
|
59
|
+
links.forEach(({ as, href })=>{
|
|
60
|
+
if (!existingHrefs.has(href)) {
|
|
61
|
+
const link = document.createElement('link');
|
|
62
|
+
link.as = as;
|
|
63
|
+
link.crossOrigin = 'anonymous';
|
|
64
|
+
link.href = href;
|
|
65
|
+
link.rel = 'prefetch';
|
|
66
|
+
document.head.append(link);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=use-prefetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/use-prefetch.ts"],"sourcesContent":["import { Log } from '@servicetitan/log-service';\nimport { useOptionalDependencies } from '@servicetitan/react-ioc';\nimport { EXPOSED_DEPENDENCIES_TOKEN, EXPOSED_INSTANCE_DEPENDENCIES_TOKEN } from './common';\nimport { getBundleInfo, supportsPrefetch } from './utils';\nimport { LoaderProps } from './loader';\n\n/**\n * Prefetch and cache bundle info for a given MFE, so that Loader can load it faster later.\n * @param src - The main package URL for the MFE\n * @returns Promise resolving to the BundleInfo\n */\nexport function usePrefetch() {\n const [logService, exposedDependencies = EXPOSED_DEPENDENCIES] = useOptionalDependencies(\n Log,\n EXPOSED_DEPENDENCIES_TOKEN\n );\n const [exposedInstanceDependencies = EXPOSED_INSTANCE_DEPENDENCIES] = useOptionalDependencies(\n EXPOSED_INSTANCE_DEPENDENCIES_TOKEN\n );\n const prefetch = async (src: string, { cache = -1 }: Pick<LoaderProps<any>, 'cache'> = {}) => {\n try {\n const bundleInfo = await getBundleInfo({\n exposedDependencies,\n exposedInstanceDependencies,\n mainPackageUrl: src,\n cache,\n retries: 0,\n });\n\n if (bundleInfo.cacheHit) {\n return;\n }\n\n const { css, js } = bundleInfo.urls;\n if (supportsPrefetch()) {\n addLinks([\n ...css.map(href => ({ href, as: 'style' })),\n ...js.map(href => ({ href, as: 'script' })),\n ]);\n } else {\n await Promise.all([...css, ...js].map(url => fetch(url, { priority: 'low' })));\n }\n } catch (e: any) {\n logService?.warning({\n category: 'Microfrontends.Prefetch',\n message: `Failed to prefetch ${src}`,\n data: e,\n });\n }\n };\n\n return { prefetch };\n}\n\ninterface Link {\n as: string;\n href: string;\n}\n\nfunction addLinks(links: Link[]) {\n const existingLinks = document.head.getElementsByTagName('link');\n const existingHrefs = new Set(Array.from(existingLinks).map(({ href }) => href));\n\n links.forEach(({ as, href }) => {\n if (!existingHrefs.has(href)) {\n const link = document.createElement('link');\n link.as = as;\n link.crossOrigin = 'anonymous';\n link.href = href;\n link.rel = 'prefetch';\n document.head.append(link);\n }\n });\n}\n"],"names":["Log","useOptionalDependencies","EXPOSED_DEPENDENCIES_TOKEN","EXPOSED_INSTANCE_DEPENDENCIES_TOKEN","getBundleInfo","supportsPrefetch","usePrefetch","logService","exposedDependencies","EXPOSED_DEPENDENCIES","exposedInstanceDependencies","EXPOSED_INSTANCE_DEPENDENCIES","prefetch","src","cache","bundleInfo","mainPackageUrl","retries","cacheHit","css","js","urls","addLinks","map","href","as","Promise","all","url","fetch","priority","e","warning","category","message","data","links","existingLinks","document","head","getElementsByTagName","existingHrefs","Set","Array","from","forEach","has","link","createElement","crossOrigin","rel","append"],"mappings":"AAAA,SAASA,GAAG,QAAQ,4BAA4B;AAChD,SAASC,uBAAuB,QAAQ,0BAA0B;AAClE,SAASC,0BAA0B,EAAEC,mCAAmC,QAAQ,WAAW;AAC3F,SAASC,aAAa,EAAEC,gBAAgB,QAAQ,UAAU;AAG1D;;;;CAIC,GACD,OAAO,SAASC;IACZ,MAAM,CAACC,YAAYC,sBAAsBC,oBAAoB,CAAC,GAAGR,wBAC7DD,KACAE;IAEJ,MAAM,CAACQ,8BAA8BC,6BAA6B,CAAC,GAAGV,wBAClEE;IAEJ,MAAMS,WAAW,OAAOC,KAAa,EAAEC,QAAQ,CAAC,CAAC,EAAmC,GAAG,CAAC,CAAC;QACrF,IAAI;YACA,MAAMC,aAAa,MAAMX,cAAc;gBACnCI;gBACAE;gBACAM,gBAAgBH;gBAChBC;gBACAG,SAAS;YACb;YAEA,IAAIF,WAAWG,QAAQ,EAAE;gBACrB;YACJ;YAEA,MAAM,EAAEC,GAAG,EAAEC,EAAE,EAAE,GAAGL,WAAWM,IAAI;YACnC,IAAIhB,oBAAoB;gBACpBiB,SAAS;uBACFH,IAAII,GAAG,CAACC,CAAAA,OAAS,CAAA;4BAAEA;4BAAMC,IAAI;wBAAQ,CAAA;uBACrCL,GAAGG,GAAG,CAACC,CAAAA,OAAS,CAAA;4BAAEA;4BAAMC,IAAI;wBAAS,CAAA;iBAC3C;YACL,OAAO;gBACH,MAAMC,QAAQC,GAAG,CAAC;uBAAIR;uBAAQC;iBAAG,CAACG,GAAG,CAACK,CAAAA,MAAOC,MAAMD,KAAK;wBAAEE,UAAU;oBAAM;YAC9E;QACJ,EAAE,OAAOC,GAAQ;YACbxB,uBAAAA,iCAAAA,WAAYyB,OAAO,CAAC;gBAChBC,UAAU;gBACVC,SAAS,CAAC,mBAAmB,EAAErB,KAAK;gBACpCsB,MAAMJ;YACV;QACJ;IACJ;IAEA,OAAO;QAAEnB;IAAS;AACtB;AAOA,SAASU,SAASc,KAAa;IAC3B,MAAMC,gBAAgBC,SAASC,IAAI,CAACC,oBAAoB,CAAC;IACzD,MAAMC,gBAAgB,IAAIC,IAAIC,MAAMC,IAAI,CAACP,eAAed,GAAG,CAAC,CAAC,EAAEC,IAAI,EAAE,GAAKA;IAE1EY,MAAMS,OAAO,CAAC,CAAC,EAAEpB,EAAE,EAAED,IAAI,EAAE;QACvB,IAAI,CAACiB,cAAcK,GAAG,CAACtB,OAAO;YAC1B,MAAMuB,OAAOT,SAASU,aAAa,CAAC;YACpCD,KAAKtB,EAAE,GAAGA;YACVsB,KAAKE,WAAW,GAAG;YACnBF,KAAKvB,IAAI,GAAGA;YACZuB,KAAKG,GAAG,GAAG;YACXZ,SAASC,IAAI,CAACY,MAAM,CAACJ;QACzB;IACJ;AACJ"}
|
|
@@ -14,6 +14,7 @@ interface GetBundleInfoProps {
|
|
|
14
14
|
}
|
|
15
15
|
export declare function getBundleInfo({ cache, mainPackageUrl, fallbackPackageUrl, exposedDependencies, exposedInstanceDependencies, isHeadless, signal, retries, onRequestError, }: GetBundleInfoProps): Promise<{
|
|
16
16
|
bundledWith: Record<string, string | undefined> | undefined;
|
|
17
|
+
cacheHit: boolean | undefined;
|
|
17
18
|
disposer: () => void;
|
|
18
19
|
mfeSupportsRerendering: boolean;
|
|
19
20
|
mfeHasUniqueChunkNamespace: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-bundle-info.d.ts","sourceRoot":"","sources":["../../src/utils/get-bundle-info.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAW,YAAY,EAAE,MAAM,WAAW,CAAC;AAGpG,OAAO,EAAE,WAAW,EAAkB,MAAM,oBAAoB,CAAC;AAOjE,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC;AAEnE,UAAU,kBAAkB;IACxB,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,wBAAsB,aAAa,CAAC,EAChC,KAAK,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAwB,EACxB,2BAAgC,EAChC,UAAU,EACV,MAAM,EACN,OAAO,EACP,cAAc,GACjB,EAAE,kBAAkB
|
|
1
|
+
{"version":3,"file":"get-bundle-info.d.ts","sourceRoot":"","sources":["../../src/utils/get-bundle-info.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAW,YAAY,EAAE,MAAM,WAAW,CAAC;AAGpG,OAAO,EAAE,WAAW,EAAkB,MAAM,oBAAoB,CAAC;AAOjE,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC;AAEnE,UAAU,kBAAkB;IACxB,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED,wBAAsB,aAAa,CAAC,EAChC,KAAK,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAwB,EACxB,2BAAgC,EAChC,UAAU,EACV,MAAM,EACN,OAAO,EACP,cAAc,GACjB,EAAE,kBAAkB;;;;;;;;;;;;;;;GAsFpB"}
|
|
@@ -10,7 +10,7 @@ import { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namesp
|
|
|
10
10
|
import { getVersionMismatches } from './get-version-mismatches';
|
|
11
11
|
export async function getBundleInfo({ cache, mainPackageUrl, fallbackPackageUrl, exposedDependencies = {}, exposedInstanceDependencies = {}, isHeadless, signal, retries, onRequestError }) {
|
|
12
12
|
var _metadata_entrypoints;
|
|
13
|
-
const { data: metadata, url: packageUrl } = await getCachedMetadata({
|
|
13
|
+
const { cacheHit, data: metadata, url: packageUrl } = await getCachedMetadata({
|
|
14
14
|
cache,
|
|
15
15
|
fallbackPackageUrl,
|
|
16
16
|
mainPackageUrl,
|
|
@@ -65,6 +65,7 @@ export async function getBundleInfo({ cache, mainPackageUrl, fallbackPackageUrl,
|
|
|
65
65
|
const elementName = resolvedBundleType === 'headless' ? `headless-${name}` : name;
|
|
66
66
|
return {
|
|
67
67
|
bundledWith,
|
|
68
|
+
cacheHit,
|
|
68
69
|
disposer,
|
|
69
70
|
mfeSupportsRerendering: getMFESupportsRerendering(bundledWith, dependencies),
|
|
70
71
|
mfeHasUniqueChunkNamespace: getMFEHasUniqueChunkNamespace(bundledWith),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/get-bundle-info.ts"],"sourcesContent":["import semver from 'semver';\nimport get from 'lodash.get';\n\nimport { ExposedDependencies, ExposedInstanceDependencies, getJson, GetJsonError } from '../common';\nimport { getCachedMetadata } from './get-cached-metadata';\nimport { getCacheSetting } from './get-cache-setting';\nimport { EntryPoints, getEntrypoints } from './get-entry-points';\nimport { getMFEHasCompatibleLaunchDarklyVersion } from './get-mfe-has-compatible-launchdarkly-version';\nimport { getMFESupportsRerendering } from './get-mfe-supports-rerendering';\nimport { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';\nimport { getVersionMismatches } from './get-version-mismatches';\nimport type { Metadata } from '../types';\n\nexport type BundleInfo = Awaited<ReturnType<typeof getBundleInfo>>;\n\ninterface GetBundleInfoProps {\n cache: boolean | number;\n exposedDependencies?: ExposedDependencies;\n exposedInstanceDependencies?: ExposedInstanceDependencies;\n fallbackPackageUrl?: string;\n isHeadless?: boolean;\n mainPackageUrl: string;\n onRequestError?: (error: GetJsonError) => void;\n retries?: number;\n signal?: AbortSignal;\n}\n\nexport async function getBundleInfo({\n cache,\n mainPackageUrl,\n fallbackPackageUrl,\n exposedDependencies = {},\n exposedInstanceDependencies = {},\n isHeadless,\n signal,\n retries,\n onRequestError,\n}: GetBundleInfoProps) {\n const {
|
|
1
|
+
{"version":3,"sources":["../../src/utils/get-bundle-info.ts"],"sourcesContent":["import semver from 'semver';\nimport get from 'lodash.get';\n\nimport { ExposedDependencies, ExposedInstanceDependencies, getJson, GetJsonError } from '../common';\nimport { getCachedMetadata } from './get-cached-metadata';\nimport { getCacheSetting } from './get-cache-setting';\nimport { EntryPoints, getEntrypoints } from './get-entry-points';\nimport { getMFEHasCompatibleLaunchDarklyVersion } from './get-mfe-has-compatible-launchdarkly-version';\nimport { getMFESupportsRerendering } from './get-mfe-supports-rerendering';\nimport { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';\nimport { getVersionMismatches } from './get-version-mismatches';\nimport type { Metadata } from '../types';\n\nexport type BundleInfo = Awaited<ReturnType<typeof getBundleInfo>>;\n\ninterface GetBundleInfoProps {\n cache: boolean | number;\n exposedDependencies?: ExposedDependencies;\n exposedInstanceDependencies?: ExposedInstanceDependencies;\n fallbackPackageUrl?: string;\n isHeadless?: boolean;\n mainPackageUrl: string;\n onRequestError?: (error: GetJsonError) => void;\n retries?: number;\n signal?: AbortSignal;\n}\n\nexport async function getBundleInfo({\n cache,\n mainPackageUrl,\n fallbackPackageUrl,\n exposedDependencies = {},\n exposedInstanceDependencies = {},\n isHeadless,\n signal,\n retries,\n onRequestError,\n}: GetBundleInfoProps) {\n const {\n cacheHit,\n data: metadata,\n url: packageUrl,\n } = await getCachedMetadata({\n cache,\n fallbackPackageUrl,\n mainPackageUrl,\n onRequestError,\n retries,\n signal,\n });\n\n const { bundledWith, name, sharedDependencies } = metadata;\n const exactPackageUrl = packageUrl.replace('/dist/metadata.json', '');\n const requestCache = getCacheSetting(exactPackageUrl);\n\n let dependencies: NonNullable<Metadata['dependencies']>;\n if (metadata.dependencies) {\n dependencies = metadata.dependencies;\n } else {\n const { data: packageJson } = await getJson(`${exactPackageUrl}/package.json`, {\n cache: requestCache,\n retries,\n signal,\n });\n\n dependencies = packageJson.dependencies;\n }\n\n let mismatch: ReturnType<typeof getVersionMismatches>;\n let resolvedBundleType: 'headless' | 'light' | 'full';\n\n if (isHeadless) {\n resolvedBundleType = 'headless';\n } else {\n mismatch = getVersionMismatches(exposedDependencies, dependencies, sharedDependencies);\n const startupVersion = bundledWith?.['@servicetitan/startup'];\n const supportLightBundles = !!startupVersion && semver.gte(startupVersion, '17.0.0');\n const embed = !mismatch && supportLightBundles;\n resolvedBundleType = embed ? 'light' : 'full';\n }\n\n const withBaseUrl = (path: string) =>\n `${exactPackageUrl}/dist/bundle/${resolvedBundleType}/${path}`;\n\n let entrypoints: EntryPoints;\n if (metadata.entrypoints?.[resolvedBundleType]) {\n entrypoints = metadata.entrypoints[resolvedBundleType];\n } else {\n entrypoints = await getEntrypoints(withBaseUrl('entrypoints.json'), requestCache);\n }\n\n const disposer = () => {\n if (resolvedBundleType === 'light') {\n const path = Object.values<string>(sharedDependencies).shift();\n if (path) {\n get(window, path).getStorage().clear(name);\n }\n }\n };\n\n const urls = Object.entries(entrypoints).reduce(\n (result, [key, value]) => ({ ...result, [key]: value.map(withBaseUrl) }),\n {} as EntryPoints\n );\n\n const elementName = resolvedBundleType === 'headless' ? `headless-${name}` : name;\n\n return {\n bundledWith,\n cacheHit,\n disposer,\n mfeSupportsRerendering: getMFESupportsRerendering(bundledWith, dependencies),\n mfeHasUniqueChunkNamespace: getMFEHasUniqueChunkNamespace(bundledWith),\n mfeHasCompatibleLaunchDarklyVersion: getMFEHasCompatibleLaunchDarklyVersion(\n bundledWith,\n exposedInstanceDependencies\n ),\n mismatch,\n resolvedBundleType,\n urls,\n WebComponent: elementName,\n version: metadata.version,\n };\n}\n"],"names":["semver","get","getJson","getCachedMetadata","getCacheSetting","getEntrypoints","getMFEHasCompatibleLaunchDarklyVersion","getMFESupportsRerendering","getMFEHasUniqueChunkNamespace","getVersionMismatches","getBundleInfo","cache","mainPackageUrl","fallbackPackageUrl","exposedDependencies","exposedInstanceDependencies","isHeadless","signal","retries","onRequestError","metadata","cacheHit","data","url","packageUrl","bundledWith","name","sharedDependencies","exactPackageUrl","replace","requestCache","dependencies","packageJson","mismatch","resolvedBundleType","startupVersion","supportLightBundles","gte","embed","withBaseUrl","path","entrypoints","disposer","Object","values","shift","window","getStorage","clear","urls","entries","reduce","result","key","value","map","elementName","mfeSupportsRerendering","mfeHasUniqueChunkNamespace","mfeHasCompatibleLaunchDarklyVersion","WebComponent","version"],"mappings":"AAAA,OAAOA,YAAY,SAAS;AAC5B,OAAOC,SAAS,aAAa;AAE7B,SAA2DC,OAAO,QAAsB,YAAY;AACpG,SAASC,iBAAiB,QAAQ,wBAAwB;AAC1D,SAASC,eAAe,QAAQ,sBAAsB;AACtD,SAAsBC,cAAc,QAAQ,qBAAqB;AACjE,SAASC,sCAAsC,QAAQ,gDAAgD;AACvG,SAASC,yBAAyB,QAAQ,iCAAiC;AAC3E,SAASC,6BAA6B,QAAQ,uCAAuC;AACrF,SAASC,oBAAoB,QAAQ,2BAA2B;AAiBhE,OAAO,eAAeC,cAAc,EAChCC,KAAK,EACLC,cAAc,EACdC,kBAAkB,EAClBC,sBAAsB,CAAC,CAAC,EACxBC,8BAA8B,CAAC,CAAC,EAChCC,UAAU,EACVC,MAAM,EACNC,OAAO,EACPC,cAAc,EACG;QAgDbC;IA/CJ,MAAM,EACFC,QAAQ,EACRC,MAAMF,QAAQ,EACdG,KAAKC,UAAU,EAClB,GAAG,MAAMrB,kBAAkB;QACxBQ;QACAE;QACAD;QACAO;QACAD;QACAD;IACJ;IAEA,MAAM,EAAEQ,WAAW,EAAEC,IAAI,EAAEC,kBAAkB,EAAE,GAAGP;IAClD,MAAMQ,kBAAkBJ,WAAWK,OAAO,CAAC,uBAAuB;IAClE,MAAMC,eAAe1B,gBAAgBwB;IAErC,IAAIG;IACJ,IAAIX,SAASW,YAAY,EAAE;QACvBA,eAAeX,SAASW,YAAY;IACxC,OAAO;QACH,MAAM,EAAET,MAAMU,WAAW,EAAE,GAAG,MAAM9B,QAAQ,GAAG0B,gBAAgB,aAAa,CAAC,EAAE;YAC3EjB,OAAOmB;YACPZ;YACAD;QACJ;QAEAc,eAAeC,YAAYD,YAAY;IAC3C;IAEA,IAAIE;IACJ,IAAIC;IAEJ,IAAIlB,YAAY;QACZkB,qBAAqB;IACzB,OAAO;QACHD,WAAWxB,qBAAqBK,qBAAqBiB,cAAcJ;QACnE,MAAMQ,iBAAiBV,wBAAAA,kCAAAA,WAAa,CAAC,wBAAwB;QAC7D,MAAMW,sBAAsB,CAAC,CAACD,kBAAkBnC,OAAOqC,GAAG,CAACF,gBAAgB;QAC3E,MAAMG,QAAQ,CAACL,YAAYG;QAC3BF,qBAAqBI,QAAQ,UAAU;IAC3C;IAEA,MAAMC,cAAc,CAACC,OACjB,GAAGZ,gBAAgB,aAAa,EAAEM,mBAAmB,CAAC,EAAEM,MAAM;IAElE,IAAIC;IACJ,KAAIrB,wBAAAA,SAASqB,WAAW,cAApBrB,4CAAAA,qBAAsB,CAACc,mBAAmB,EAAE;QAC5CO,cAAcrB,SAASqB,WAAW,CAACP,mBAAmB;IAC1D,OAAO;QACHO,cAAc,MAAMpC,eAAekC,YAAY,qBAAqBT;IACxE;IAEA,MAAMY,WAAW;QACb,IAAIR,uBAAuB,SAAS;YAChC,MAAMM,OAAOG,OAAOC,MAAM,CAASjB,oBAAoBkB,KAAK;YAC5D,IAAIL,MAAM;gBACNvC,IAAI6C,QAAQN,MAAMO,UAAU,GAAGC,KAAK,CAACtB;YACzC;QACJ;IACJ;IAEA,MAAMuB,OAAON,OAAOO,OAAO,CAACT,aAAaU,MAAM,CAC3C,CAACC,QAAQ,CAACC,KAAKC,MAAM,GAAM,CAAA;YAAE,GAAGF,MAAM;YAAE,CAACC,IAAI,EAAEC,MAAMC,GAAG,CAAChB;QAAa,CAAA,GACtE,CAAC;IAGL,MAAMiB,cAActB,uBAAuB,aAAa,CAAC,SAAS,EAAER,MAAM,GAAGA;IAE7E,OAAO;QACHD;QACAJ;QACAqB;QACAe,wBAAwBlD,0BAA0BkB,aAAaM;QAC/D2B,4BAA4BlD,8BAA8BiB;QAC1DkC,qCAAqCrD,uCACjCmB,aACAV;QAEJkB;QACAC;QACAe;QACAW,cAAcJ;QACdK,SAASzC,SAASyC,OAAO;IAC7B;AACJ"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GetJsonError } from '../common';
|
|
2
2
|
import type { Metadata } from '../types';
|
|
3
|
+
type MetadataResponse = Awaited<ReturnType<typeof getMetadata>>;
|
|
3
4
|
interface GetMetadataProps {
|
|
4
5
|
fallbackPackageUrl?: string;
|
|
5
6
|
mainPackageUrl: string;
|
|
@@ -7,12 +8,15 @@ interface GetMetadataProps {
|
|
|
7
8
|
signal?: AbortSignal;
|
|
8
9
|
retries?: number;
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
-
cache: boolean | number;
|
|
12
|
-
}): Promise<{
|
|
11
|
+
declare function getMetadata(props: GetMetadataProps): Promise<{
|
|
13
12
|
data: Metadata;
|
|
14
13
|
headers: Headers;
|
|
15
14
|
url: string;
|
|
16
15
|
}>;
|
|
16
|
+
export declare function getCachedMetadata(props: GetMetadataProps & {
|
|
17
|
+
cache: boolean | number;
|
|
18
|
+
}): Promise<MetadataResponse & {
|
|
19
|
+
cacheHit?: boolean;
|
|
20
|
+
}>;
|
|
17
21
|
export {};
|
|
18
22
|
//# sourceMappingURL=get-cached-metadata.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-cached-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/get-cached-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"get-cached-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/get-cached-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,YAAY,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAOzC,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC;AAIhE,UAAU,gBAAgB;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC/C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,iBAAe,WAAW,CAAC,KAAK,EAAE,gBAAgB;;;;GAsBjD;AAED,wBAAsB,iBAAiB,CACnC,KAAK,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CA2BpD"}
|
|
@@ -30,16 +30,20 @@ async function getMetadata(props) {
|
|
|
30
30
|
export async function getCachedMetadata(props) {
|
|
31
31
|
let cache = props.cache;
|
|
32
32
|
const cacheKey = props.mainPackageUrl;
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
const cachedResponse = cache ? metadataResponseCache.get(cacheKey) : undefined;
|
|
34
|
+
if (cachedResponse) {
|
|
35
|
+
return {
|
|
36
|
+
...cachedResponse,
|
|
37
|
+
cacheHit: true
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const metadataResponse = await getMetadata(props);
|
|
41
|
+
const mfeHasUniqueChunkNamespace = getMFEHasUniqueChunkNamespace(metadataResponse.data.bundledWith);
|
|
42
|
+
if (cache === true && !mfeHasUniqueChunkNamespace) {
|
|
43
|
+
cache = -1;
|
|
44
|
+
}
|
|
45
|
+
if (cache) {
|
|
46
|
+
metadataResponseCache.set(cacheKey, metadataResponse, typeof cache === 'number' ? cache : undefined);
|
|
43
47
|
}
|
|
44
48
|
return metadataResponse;
|
|
45
49
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/get-cached-metadata.ts"],"sourcesContent":["import { getJson, GetJsonError } from '../common';\nimport type { Metadata } from '../types';\nimport { getCacheSetting } from './get-cache-setting';\nimport { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';\nimport { TtlCache } from './ttl-cache';\n\nconst MINUTE = 1000 * 60;\n\
|
|
1
|
+
{"version":3,"sources":["../../src/utils/get-cached-metadata.ts"],"sourcesContent":["import { getJson, GetJsonError } from '../common';\nimport type { Metadata } from '../types';\nimport { getCacheSetting } from './get-cache-setting';\nimport { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';\nimport { TtlCache } from './ttl-cache';\n\nconst MINUTE = 1000 * 60;\n\ntype MetadataResponse = Awaited<ReturnType<typeof getMetadata>>;\n\nconst metadataResponseCache = new TtlCache<MetadataResponse>({ ttlMs: 15 * MINUTE });\n\ninterface GetMetadataProps {\n fallbackPackageUrl?: string;\n mainPackageUrl: string;\n onRequestError?: (error: GetJsonError) => void;\n signal?: AbortSignal;\n retries?: number;\n}\n\nasync function getMetadata(props: GetMetadataProps) {\n const { fallbackPackageUrl, mainPackageUrl, onRequestError, retries, signal } = props;\n\n try {\n return await getJson<Metadata>(`${mainPackageUrl}/dist/metadata.json`, {\n cache: getCacheSetting(mainPackageUrl),\n retries: fallbackPackageUrl ? 0 : retries,\n onRequestError,\n signal,\n });\n } catch (e) {\n if (!fallbackPackageUrl) {\n throw e;\n }\n\n return getJson<Metadata>(`${fallbackPackageUrl}/dist/metadata.json`, {\n retries,\n onRequestError,\n cache: getCacheSetting(fallbackPackageUrl),\n signal,\n });\n }\n}\n\nexport async function getCachedMetadata(\n props: GetMetadataProps & { cache: boolean | number }\n): Promise<MetadataResponse & { cacheHit?: boolean }> {\n let cache = props.cache;\n\n const cacheKey = props.mainPackageUrl;\n const cachedResponse = cache ? metadataResponseCache.get(cacheKey) : undefined;\n if (cachedResponse) {\n return { ...cachedResponse, cacheHit: true };\n }\n\n const metadataResponse = await getMetadata(props);\n const mfeHasUniqueChunkNamespace = getMFEHasUniqueChunkNamespace(\n metadataResponse.data.bundledWith\n );\n\n if (cache === true && !mfeHasUniqueChunkNamespace) {\n cache = -1;\n }\n\n if (cache) {\n metadataResponseCache.set(\n cacheKey,\n metadataResponse,\n typeof cache === 'number' ? cache : undefined\n );\n }\n\n return metadataResponse;\n}\n"],"names":["getJson","getCacheSetting","getMFEHasUniqueChunkNamespace","TtlCache","MINUTE","metadataResponseCache","ttlMs","getMetadata","props","fallbackPackageUrl","mainPackageUrl","onRequestError","retries","signal","cache","e","getCachedMetadata","cacheKey","cachedResponse","get","undefined","cacheHit","metadataResponse","mfeHasUniqueChunkNamespace","data","bundledWith","set"],"mappings":"AAAA,SAASA,OAAO,QAAsB,YAAY;AAElD,SAASC,eAAe,QAAQ,sBAAsB;AACtD,SAASC,6BAA6B,QAAQ,uCAAuC;AACrF,SAASC,QAAQ,QAAQ,cAAc;AAEvC,MAAMC,SAAS,OAAO;AAItB,MAAMC,wBAAwB,IAAIF,SAA2B;IAAEG,OAAO,KAAKF;AAAO;AAUlF,eAAeG,YAAYC,KAAuB;IAC9C,MAAM,EAAEC,kBAAkB,EAAEC,cAAc,EAAEC,cAAc,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGL;IAEhF,IAAI;QACA,OAAO,MAAMR,QAAkB,GAAGU,eAAe,mBAAmB,CAAC,EAAE;YACnEI,OAAOb,gBAAgBS;YACvBE,SAASH,qBAAqB,IAAIG;YAClCD;YACAE;QACJ;IACJ,EAAE,OAAOE,GAAG;QACR,IAAI,CAACN,oBAAoB;YACrB,MAAMM;QACV;QAEA,OAAOf,QAAkB,GAAGS,mBAAmB,mBAAmB,CAAC,EAAE;YACjEG;YACAD;YACAG,OAAOb,gBAAgBQ;YACvBI;QACJ;IACJ;AACJ;AAEA,OAAO,eAAeG,kBAClBR,KAAqD;IAErD,IAAIM,QAAQN,MAAMM,KAAK;IAEvB,MAAMG,WAAWT,MAAME,cAAc;IACrC,MAAMQ,iBAAiBJ,QAAQT,sBAAsBc,GAAG,CAACF,YAAYG;IACrE,IAAIF,gBAAgB;QAChB,OAAO;YAAE,GAAGA,cAAc;YAAEG,UAAU;QAAK;IAC/C;IAEA,MAAMC,mBAAmB,MAAMf,YAAYC;IAC3C,MAAMe,6BAA6BrB,8BAC/BoB,iBAAiBE,IAAI,CAACC,WAAW;IAGrC,IAAIX,UAAU,QAAQ,CAACS,4BAA4B;QAC/CT,QAAQ,CAAC;IACb;IAEA,IAAIA,OAAO;QACPT,sBAAsBqB,GAAG,CACrBT,UACAK,kBACA,OAAOR,UAAU,WAAWA,QAAQM;IAE5C;IAEA,OAAOE;AACX"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './get-mfe-supports-rerendering';
|
|
|
6
6
|
export * from './get-version-mismatches';
|
|
7
7
|
export * from './is-versioned-url';
|
|
8
8
|
export * from './load-scripts';
|
|
9
|
+
export * from './supports-prefetch';
|
|
9
10
|
export * from './ttl-cache';
|
|
10
11
|
export * from './validate-bundle-info';
|
|
11
12
|
export * from './web-component';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from './get-mfe-supports-rerendering';
|
|
|
6
6
|
export * from './get-version-mismatches';
|
|
7
7
|
export * from './is-versioned-url';
|
|
8
8
|
export * from './load-scripts';
|
|
9
|
+
export * from './supports-prefetch';
|
|
9
10
|
export * from './ttl-cache';
|
|
10
11
|
export * from './validate-bundle-info';
|
|
11
12
|
export * from './web-component';
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from './get-bundle-info';\nexport * from './get-element-name';\nexport * from './get-entry-points';\nexport * from './get-ld-service-token';\nexport * from './get-mfe-supports-rerendering';\nexport * from './get-version-mismatches';\nexport * from './is-versioned-url';\nexport * from './load-scripts';\nexport * from './ttl-cache';\nexport * from './validate-bundle-info';\nexport * from './web-component';\n"],"names":[],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,qBAAqB;AACnC,cAAc,qBAAqB;AACnC,cAAc,yBAAyB;AACvC,cAAc,iCAAiC;AAC/C,cAAc,2BAA2B;AACzC,cAAc,qBAAqB;AACnC,cAAc,iBAAiB;AAC/B,cAAc,cAAc;AAC5B,cAAc,yBAAyB;AACvC,cAAc,kBAAkB"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from './get-bundle-info';\nexport * from './get-element-name';\nexport * from './get-entry-points';\nexport * from './get-ld-service-token';\nexport * from './get-mfe-supports-rerendering';\nexport * from './get-version-mismatches';\nexport * from './is-versioned-url';\nexport * from './load-scripts';\nexport * from './supports-prefetch';\nexport * from './ttl-cache';\nexport * from './validate-bundle-info';\nexport * from './web-component';\n"],"names":[],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,qBAAqB;AACnC,cAAc,qBAAqB;AACnC,cAAc,yBAAyB;AACvC,cAAc,iCAAiC;AAC/C,cAAc,2BAA2B;AACzC,cAAc,qBAAqB;AACnC,cAAc,iBAAiB;AAC/B,cAAc,sBAAsB;AACpC,cAAc,cAAc;AAC5B,cAAc,yBAAyB;AACvC,cAAc,kBAAkB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supports-prefetch.d.ts","sourceRoot":"","sources":["../../src/utils/supports-prefetch.ts"],"names":[],"mappings":"AAEA,wBAAgB,gBAAgB,YAM/B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
let result;
|
|
2
|
+
export function supportsPrefetch() {
|
|
3
|
+
if (result === undefined) {
|
|
4
|
+
var _link_relList_supports, _link_relList;
|
|
5
|
+
const link = document.createElement('link');
|
|
6
|
+
result = !!((_link_relList = link.relList) === null || _link_relList === void 0 ? void 0 : (_link_relList_supports = _link_relList.supports) === null || _link_relList_supports === void 0 ? void 0 : _link_relList_supports.call(_link_relList, 'prefetch'));
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=supports-prefetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/supports-prefetch.ts"],"sourcesContent":["let result: boolean | undefined;\n\nexport function supportsPrefetch() {\n if (result === undefined) {\n const link = document.createElement('link');\n result = !!link.relList?.supports?.('prefetch');\n }\n return result;\n}\n"],"names":["result","supportsPrefetch","undefined","link","document","createElement","relList","supports"],"mappings":"AAAA,IAAIA;AAEJ,OAAO,SAASC;IACZ,IAAID,WAAWE,WAAW;YAEXC,wBAAAA;QADX,MAAMA,OAAOC,SAASC,aAAa,CAAC;QACpCL,SAAS,CAAC,GAACG,gBAAAA,KAAKG,OAAO,cAAZH,qCAAAA,yBAAAA,cAAcI,QAAQ,cAAtBJ,6CAAAA,4BAAAA,eAAyB;IACxC;IACA,OAAOH;AACX"}
|
|
@@ -2,9 +2,9 @@ export interface TtlCacheOptions {
|
|
|
2
2
|
ttlMs?: number;
|
|
3
3
|
}
|
|
4
4
|
export declare class TtlCache<T = any> {
|
|
5
|
-
private data;
|
|
6
|
-
private timers;
|
|
7
|
-
private ttl;
|
|
5
|
+
private readonly data;
|
|
6
|
+
private readonly timers;
|
|
7
|
+
private readonly ttl;
|
|
8
8
|
constructor({ ttlMs }?: TtlCacheOptions);
|
|
9
9
|
set(key: string, value: T, ttl?: number): void;
|
|
10
10
|
get(key: string): T | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ttl-cache.d.ts","sourceRoot":"","sources":["../../src/utils/ttl-cache.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,QAAQ,CAAC,CAAC,GAAG,GAAG;IACzB,OAAO,CAAC,IAAI,CAAiB;
|
|
1
|
+
{"version":3,"file":"ttl-cache.d.ts","sourceRoot":"","sources":["../../src/utils/ttl-cache.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,QAAQ,CAAC,CAAC,GAAG,GAAG;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6C;IACpE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,EAAE,KAAmB,EAAE,GAAE,eAAoB;IAUzD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM;IAKvC,GAAG,CAAC,GAAG,EAAE,MAAM;IAIf,GAAG,CAAC,GAAG,EAAE,MAAM;IAIf,MAAM,CAAC,GAAG,EAAE,MAAM;IAKlB,KAAK;IAML,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,UAAU;CASrB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/ttl-cache.ts"],"sourcesContent":["const MINUTE = 1000 * 60;\nconst DEFAULT_TTL = 5 * MINUTE;\n\nexport interface TtlCacheOptions {\n ttlMs?: number;\n}\n\nexport class TtlCache<T = any> {\n private data: Map<string, T>;\n private timers: Map<string, ReturnType<typeof setTimeout>>;\n private ttl: number;\n\n constructor({ ttlMs = DEFAULT_TTL }: TtlCacheOptions = {}) {\n if (ttlMs < 0) {\n throw new Error('default TTL must be zero or greater');\n }\n\n this.data = new Map();\n this.timers = new Map();\n this.ttl = ttlMs;\n }\n\n set(key: string, value: T, ttl?: number) {\n this.setTimeout(key, ttl ?? this.ttl);\n this.data.set(key, value);\n }\n\n get(key: string) {\n return this.data.get(key);\n }\n\n has(key: string) {\n return this.data.has(key);\n }\n\n delete(key: string) {\n this.cancelTimeout(key);\n this.data.delete(key);\n }\n\n clear() {\n this.data.clear();\n this.timers.forEach(value => clearTimeout(value));\n this.timers.clear();\n }\n\n private cancelTimeout(key: string) {\n const id = this.timers.get(key);\n if (id) {\n clearTimeout(id);\n this.timers.delete(key);\n }\n }\n\n private setTimeout(key: string, ttl: number) {\n this.cancelTimeout(key);\n if (ttl < 0) {\n return;\n }\n\n const id = setTimeout(() => this.delete(key), ttl);\n this.timers.set(key, id);\n }\n}\n"],"names":["MINUTE","DEFAULT_TTL","TtlCache","set","key","value","ttl","setTimeout","data","get","has","delete","cancelTimeout","clear","timers","forEach","clearTimeout","id","ttlMs","Error","Map"],"mappings":";;;;;;;;;;;;;AAAA,MAAMA,SAAS,OAAO;AACtB,MAAMC,cAAc,IAAID;AAMxB,OAAO,MAAME;IAeTC,IAAIC,GAAW,EAAEC,KAAQ,EAAEC,GAAY,EAAE;QACrC,IAAI,CAACC,UAAU,CAACH,KAAKE,gBAAAA,iBAAAA,MAAO,IAAI,CAACA,GAAG;QACpC,IAAI,CAACE,IAAI,CAACL,GAAG,CAACC,KAAKC;IACvB;IAEAI,IAAIL,GAAW,EAAE;QACb,OAAO,IAAI,CAACI,IAAI,CAACC,GAAG,CAACL;IACzB;IAEAM,IAAIN,GAAW,EAAE;QACb,OAAO,IAAI,CAACI,IAAI,CAACE,GAAG,CAACN;IACzB;IAEAO,OAAOP,GAAW,EAAE;QAChB,IAAI,CAACQ,aAAa,CAACR;QACnB,IAAI,CAACI,IAAI,CAACG,MAAM,CAACP;IACrB;IAEAS,QAAQ;QACJ,IAAI,CAACL,IAAI,CAACK,KAAK;QACf,IAAI,CAACC,MAAM,CAACC,OAAO,CAACV,CAAAA,QAASW,aAAaX;QAC1C,IAAI,CAACS,MAAM,CAACD,KAAK;IACrB;IAEQD,cAAcR,GAAW,EAAE;QAC/B,MAAMa,KAAK,IAAI,CAACH,MAAM,CAACL,GAAG,CAACL;QAC3B,IAAIa,IAAI;YACJD,aAAaC;YACb,IAAI,CAACH,MAAM,CAACH,MAAM,CAACP;QACvB;IACJ;IAEQG,WAAWH,GAAW,EAAEE,GAAW,EAAE;QACzC,IAAI,CAACM,aAAa,CAACR;QACnB,IAAIE,MAAM,GAAG;YACT;QACJ;QAEA,MAAMW,KAAKV,WAAW,IAAM,IAAI,CAACI,MAAM,CAACP,MAAME;QAC9C,IAAI,CAACQ,MAAM,CAACX,GAAG,CAACC,KAAKa;IACzB;IAlDA,YAAY,EAAEC,QAAQjB,WAAW,EAAmB,GAAG,CAAC,CAAC,CAAE;QAJ3D,
|
|
1
|
+
{"version":3,"sources":["../../src/utils/ttl-cache.ts"],"sourcesContent":["const MINUTE = 1000 * 60;\nconst DEFAULT_TTL = 5 * MINUTE;\n\nexport interface TtlCacheOptions {\n ttlMs?: number;\n}\n\nexport class TtlCache<T = any> {\n private readonly data: Map<string, T>;\n private readonly timers: Map<string, ReturnType<typeof setTimeout>>;\n private readonly ttl: number;\n\n constructor({ ttlMs = DEFAULT_TTL }: TtlCacheOptions = {}) {\n if (ttlMs < 0) {\n throw new Error('default TTL must be zero or greater');\n }\n\n this.data = new Map();\n this.timers = new Map();\n this.ttl = ttlMs;\n }\n\n set(key: string, value: T, ttl?: number) {\n this.setTimeout(key, ttl ?? this.ttl);\n this.data.set(key, value);\n }\n\n get(key: string) {\n return this.data.get(key);\n }\n\n has(key: string) {\n return this.data.has(key);\n }\n\n delete(key: string) {\n this.cancelTimeout(key);\n this.data.delete(key);\n }\n\n clear() {\n this.data.clear();\n this.timers.forEach(value => clearTimeout(value));\n this.timers.clear();\n }\n\n private cancelTimeout(key: string) {\n const id = this.timers.get(key);\n if (id) {\n clearTimeout(id);\n this.timers.delete(key);\n }\n }\n\n private setTimeout(key: string, ttl: number) {\n this.cancelTimeout(key);\n if (ttl < 0) {\n return;\n }\n\n const id = setTimeout(() => this.delete(key), ttl);\n this.timers.set(key, id);\n }\n}\n"],"names":["MINUTE","DEFAULT_TTL","TtlCache","set","key","value","ttl","setTimeout","data","get","has","delete","cancelTimeout","clear","timers","forEach","clearTimeout","id","ttlMs","Error","Map"],"mappings":";;;;;;;;;;;;;AAAA,MAAMA,SAAS,OAAO;AACtB,MAAMC,cAAc,IAAID;AAMxB,OAAO,MAAME;IAeTC,IAAIC,GAAW,EAAEC,KAAQ,EAAEC,GAAY,EAAE;QACrC,IAAI,CAACC,UAAU,CAACH,KAAKE,gBAAAA,iBAAAA,MAAO,IAAI,CAACA,GAAG;QACpC,IAAI,CAACE,IAAI,CAACL,GAAG,CAACC,KAAKC;IACvB;IAEAI,IAAIL,GAAW,EAAE;QACb,OAAO,IAAI,CAACI,IAAI,CAACC,GAAG,CAACL;IACzB;IAEAM,IAAIN,GAAW,EAAE;QACb,OAAO,IAAI,CAACI,IAAI,CAACE,GAAG,CAACN;IACzB;IAEAO,OAAOP,GAAW,EAAE;QAChB,IAAI,CAACQ,aAAa,CAACR;QACnB,IAAI,CAACI,IAAI,CAACG,MAAM,CAACP;IACrB;IAEAS,QAAQ;QACJ,IAAI,CAACL,IAAI,CAACK,KAAK;QACf,IAAI,CAACC,MAAM,CAACC,OAAO,CAACV,CAAAA,QAASW,aAAaX;QAC1C,IAAI,CAACS,MAAM,CAACD,KAAK;IACrB;IAEQD,cAAcR,GAAW,EAAE;QAC/B,MAAMa,KAAK,IAAI,CAACH,MAAM,CAACL,GAAG,CAACL;QAC3B,IAAIa,IAAI;YACJD,aAAaC;YACb,IAAI,CAACH,MAAM,CAACH,MAAM,CAACP;QACvB;IACJ;IAEQG,WAAWH,GAAW,EAAEE,GAAW,EAAE;QACzC,IAAI,CAACM,aAAa,CAACR;QACnB,IAAIE,MAAM,GAAG;YACT;QACJ;QAEA,MAAMW,KAAKV,WAAW,IAAM,IAAI,CAACI,MAAM,CAACP,MAAME;QAC9C,IAAI,CAACQ,MAAM,CAACX,GAAG,CAACC,KAAKa;IACzB;IAlDA,YAAY,EAAEC,QAAQjB,WAAW,EAAmB,GAAG,CAAC,CAAC,CAAE;QAJ3D,uBAAiBO,QAAjB,KAAA;QACA,uBAAiBM,UAAjB,KAAA;QACA,uBAAiBR,OAAjB,KAAA;QAGI,IAAIY,QAAQ,GAAG;YACX,MAAM,IAAIC,MAAM;QACpB;QAEA,IAAI,CAACX,IAAI,GAAG,IAAIY;QAChB,IAAI,CAACN,MAAM,GAAG,IAAIM;QAClB,IAAI,CAACd,GAAG,GAAGY;IACf;AA2CJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/web-components",
|
|
3
|
-
"version": "32.
|
|
3
|
+
"version": "32.4.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"src"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@servicetitan/error-boundary": "32.
|
|
18
|
+
"@servicetitan/error-boundary": "32.4.0",
|
|
19
19
|
"lodash.get": "~4.4.2",
|
|
20
20
|
"mitt": "~3.0.1",
|
|
21
21
|
"semver": "~7.7.3"
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@servicetitan/anvil2": "^1.47.1",
|
|
25
25
|
"@servicetitan/design-system": "~14.5.3",
|
|
26
|
-
"@servicetitan/launchdarkly-service": "32.
|
|
27
|
-
"@servicetitan/log-service": "32.
|
|
28
|
-
"@servicetitan/react-ioc": "32.
|
|
26
|
+
"@servicetitan/launchdarkly-service": "32.4.0",
|
|
27
|
+
"@servicetitan/log-service": "32.4.0",
|
|
28
|
+
"@servicetitan/react-ioc": "32.4.0",
|
|
29
29
|
"@testing-library/dom": "^10.4.1",
|
|
30
30
|
"@testing-library/jest-dom": "^6.9.1",
|
|
31
31
|
"@testing-library/react": "^16.3.0",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@servicetitan/anvil2": ">=1.47.1",
|
|
45
45
|
"@servicetitan/design-system": ">=14.5.3",
|
|
46
|
-
"@servicetitan/launchdarkly-service": "32.
|
|
47
|
-
"@servicetitan/log-service": "32.
|
|
48
|
-
"@servicetitan/react-ioc": "32.
|
|
46
|
+
"@servicetitan/launchdarkly-service": "32.4.0",
|
|
47
|
+
"@servicetitan/log-service": "32.4.0",
|
|
48
|
+
"@servicetitan/react-ioc": "32.4.0",
|
|
49
49
|
"history": "~4.10.1",
|
|
50
50
|
"react": ">=17.0.2",
|
|
51
51
|
"react-dom": ">=17.0.2"
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"cli": {
|
|
68
68
|
"webpack": false
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "836bcc0dc0859bf1ac0d3c679996e7b753893674"
|
|
71
71
|
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { FC, PropsWithChildren } from 'react';
|
|
2
|
+
import { Log } from '@servicetitan/log-service';
|
|
3
|
+
import { Provider } from '@servicetitan/react-ioc';
|
|
4
|
+
import { renderHook } from '@testing-library/react';
|
|
5
|
+
import { getBundleInfo, supportsPrefetch } from '../utils';
|
|
6
|
+
import {
|
|
7
|
+
EXPOSED_DEPENDENCIES_TOKEN,
|
|
8
|
+
EXPOSED_INSTANCE_DEPENDENCIES_TOKEN,
|
|
9
|
+
ExposedDependencies,
|
|
10
|
+
ExposedInstanceDependencies,
|
|
11
|
+
} from '../common';
|
|
12
|
+
import { usePrefetch } from '../use-prefetch';
|
|
13
|
+
|
|
14
|
+
jest.mock('../utils', () => ({
|
|
15
|
+
...jest.requireActual('../utils'),
|
|
16
|
+
getBundleInfo: jest.fn(),
|
|
17
|
+
supportsPrefetch: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe(`[web-components] ${usePrefetch.name}`, () => {
|
|
21
|
+
const src = 'https://example.com';
|
|
22
|
+
let singletons: Parameters<typeof Provider>[0]['singletons'];
|
|
23
|
+
let urls: Awaited<ReturnType<typeof getBundleInfo>>['urls'];
|
|
24
|
+
let options: Parameters<ReturnType<typeof usePrefetch>['prefetch']>[1];
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
Object.assign(globalThis, {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
29
|
+
EXPOSED_DEPENDENCIES: { foo: { version: '1.0.0', variable: 'bar' } },
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
31
|
+
EXPOSED_INSTANCE_DEPENDENCIES: { launchDarkly: { version: '1.0.0' } },
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
singletons = undefined;
|
|
38
|
+
urls = { css: [], js: [] };
|
|
39
|
+
options = undefined;
|
|
40
|
+
jest.mocked(getBundleInfo).mockResolvedValue({ urls } as any);
|
|
41
|
+
jest.mocked(supportsPrefetch).mockReturnValue(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const subject = async () => {
|
|
45
|
+
const Wrapper: FC<PropsWithChildren> = ({ children }) => (
|
|
46
|
+
<Provider singletons={singletons}>{children}</Provider>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const { prefetch } = renderHook(() => usePrefetch(), { wrapper: Wrapper }).result.current;
|
|
50
|
+
|
|
51
|
+
return prefetch(src, options);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
test('retrieves bundle info', async () => {
|
|
55
|
+
await subject();
|
|
56
|
+
|
|
57
|
+
expect(getBundleInfo).toHaveBeenCalledWith({
|
|
58
|
+
exposedDependencies: EXPOSED_DEPENDENCIES,
|
|
59
|
+
exposedInstanceDependencies: EXPOSED_INSTANCE_DEPENDENCIES,
|
|
60
|
+
mainPackageUrl: src,
|
|
61
|
+
cache: -1,
|
|
62
|
+
retries: 0,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('with EXPOSED_DEPENDENCIES_TOKEN', () => {
|
|
67
|
+
const exposedDependencies: ExposedDependencies = {
|
|
68
|
+
baz: { version: '2.0.0', variable: 'qux' },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
singletons = [
|
|
73
|
+
{
|
|
74
|
+
provide: EXPOSED_DEPENDENCIES_TOKEN,
|
|
75
|
+
useValue: exposedDependencies,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('passes exposed dependencies to getBundleInfo', async () => {
|
|
81
|
+
await subject();
|
|
82
|
+
|
|
83
|
+
expect(getBundleInfo).toHaveBeenCalledWith(
|
|
84
|
+
expect.objectContaining({ exposedDependencies })
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('with EXPOSED_INSTANCE_DEPENDENCIES_TOKEN', () => {
|
|
90
|
+
const exposedInstanceDependencies: ExposedInstanceDependencies = {
|
|
91
|
+
launchDarkly: { version: '2.0.0', ldService: {} as any },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
singletons = [
|
|
96
|
+
{
|
|
97
|
+
provide: EXPOSED_INSTANCE_DEPENDENCIES_TOKEN,
|
|
98
|
+
useValue: exposedInstanceDependencies,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('passes exposed instance dependencies to getBundleInfo', async () => {
|
|
104
|
+
await subject();
|
|
105
|
+
|
|
106
|
+
expect(getBundleInfo).toHaveBeenCalledWith(
|
|
107
|
+
expect.objectContaining({
|
|
108
|
+
exposedInstanceDependencies,
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('with cache option', () => {
|
|
115
|
+
beforeEach(() => (options = { cache: 1000 }));
|
|
116
|
+
|
|
117
|
+
test('passes cache option to getBundleInto', async () => {
|
|
118
|
+
await subject();
|
|
119
|
+
|
|
120
|
+
expect(getBundleInfo).toHaveBeenCalledWith(
|
|
121
|
+
expect.objectContaining({ cache: options!.cache })
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('when bundle contains urls', () => {
|
|
127
|
+
const css = ['foo', 'bar'].map(name => `${src}/${name}.bundle.css`);
|
|
128
|
+
const js = ['foo', 'bar'].map(name => `${src}/${name}.bundle.js`);
|
|
129
|
+
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
Object.assign(urls, { css, js });
|
|
132
|
+
Array.from(document.head.getElementsByTagName('link')).forEach(link => link.remove());
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
function getLinks() {
|
|
136
|
+
return Array.from(document.head.getElementsByTagName('link')).map(
|
|
137
|
+
({ as, crossOrigin, href, rel }) => ({ as, crossOrigin, href, rel })
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function itDoesNotAddLinks() {
|
|
142
|
+
test('does not add links', async () => {
|
|
143
|
+
const originalLinks = getLinks();
|
|
144
|
+
|
|
145
|
+
await subject();
|
|
146
|
+
|
|
147
|
+
expect(getLinks()).toEqual(originalLinks);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
test('adds prefetch links to document head', async () => {
|
|
152
|
+
await subject();
|
|
153
|
+
|
|
154
|
+
expect(getLinks()).toEqual([
|
|
155
|
+
...css.map(href => ({
|
|
156
|
+
as: 'style',
|
|
157
|
+
crossOrigin: 'anonymous',
|
|
158
|
+
href,
|
|
159
|
+
rel: 'prefetch',
|
|
160
|
+
})),
|
|
161
|
+
...js.map(href => ({
|
|
162
|
+
as: 'script',
|
|
163
|
+
crossOrigin: 'anonymous',
|
|
164
|
+
href,
|
|
165
|
+
rel: 'prefetch',
|
|
166
|
+
})),
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('when bundle is already cached', () => {
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
jest.mocked(getBundleInfo).mockResolvedValue({ urls, cacheHit: true } as any);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
itDoesNotAddLinks();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('when links already exist', () => {
|
|
179
|
+
function addLink(props: Record<string, any>) {
|
|
180
|
+
const link = document.createElement('link');
|
|
181
|
+
Object.assign(link, props);
|
|
182
|
+
document.head.append(link);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
css.forEach(href => addLink({ href }));
|
|
187
|
+
js.forEach(href => addLink({ href }));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
itDoesNotAddLinks();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('when rel=prefetch is not supported', () => {
|
|
194
|
+
beforeEach(() => jest.mocked(supportsPrefetch).mockReturnValue(false));
|
|
195
|
+
|
|
196
|
+
test('manually fetches urls with low priority', async () => {
|
|
197
|
+
const fetchSpy = jest.spyOn(globalThis, 'fetch').mockResolvedValue({} as any);
|
|
198
|
+
|
|
199
|
+
await subject();
|
|
200
|
+
|
|
201
|
+
[...css, ...js].forEach(url =>
|
|
202
|
+
expect(fetchSpy).toHaveBeenCalledWith(url, { priority: 'low' })
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('when getBundleInfo throws an error', () => {
|
|
209
|
+
const error = 'Oops!';
|
|
210
|
+
|
|
211
|
+
beforeEach(() => jest.mocked(getBundleInfo).mockRejectedValue(error));
|
|
212
|
+
|
|
213
|
+
test('suppresses the error', async () => {
|
|
214
|
+
await expect(subject()).resolves.toBe(undefined);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('when Log is provided', () => {
|
|
218
|
+
const log = { warning: jest.fn() };
|
|
219
|
+
|
|
220
|
+
beforeEach(() => {
|
|
221
|
+
singletons = [{ provide: Log, useValue: log }];
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('logs warning', async () => {
|
|
225
|
+
await subject();
|
|
226
|
+
|
|
227
|
+
expect(log.warning).toHaveBeenCalledWith({
|
|
228
|
+
category: 'Microfrontends.Prefetch',
|
|
229
|
+
message: expect.stringMatching(/failed to prefetch/i),
|
|
230
|
+
data: error,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Log } from '@servicetitan/log-service';
|
|
2
|
+
import { useOptionalDependencies } from '@servicetitan/react-ioc';
|
|
3
|
+
import { EXPOSED_DEPENDENCIES_TOKEN, EXPOSED_INSTANCE_DEPENDENCIES_TOKEN } from './common';
|
|
4
|
+
import { getBundleInfo, supportsPrefetch } from './utils';
|
|
5
|
+
import { LoaderProps } from './loader';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prefetch and cache bundle info for a given MFE, so that Loader can load it faster later.
|
|
9
|
+
* @param src - The main package URL for the MFE
|
|
10
|
+
* @returns Promise resolving to the BundleInfo
|
|
11
|
+
*/
|
|
12
|
+
export function usePrefetch() {
|
|
13
|
+
const [logService, exposedDependencies = EXPOSED_DEPENDENCIES] = useOptionalDependencies(
|
|
14
|
+
Log,
|
|
15
|
+
EXPOSED_DEPENDENCIES_TOKEN
|
|
16
|
+
);
|
|
17
|
+
const [exposedInstanceDependencies = EXPOSED_INSTANCE_DEPENDENCIES] = useOptionalDependencies(
|
|
18
|
+
EXPOSED_INSTANCE_DEPENDENCIES_TOKEN
|
|
19
|
+
);
|
|
20
|
+
const prefetch = async (src: string, { cache = -1 }: Pick<LoaderProps<any>, 'cache'> = {}) => {
|
|
21
|
+
try {
|
|
22
|
+
const bundleInfo = await getBundleInfo({
|
|
23
|
+
exposedDependencies,
|
|
24
|
+
exposedInstanceDependencies,
|
|
25
|
+
mainPackageUrl: src,
|
|
26
|
+
cache,
|
|
27
|
+
retries: 0,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (bundleInfo.cacheHit) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { css, js } = bundleInfo.urls;
|
|
35
|
+
if (supportsPrefetch()) {
|
|
36
|
+
addLinks([
|
|
37
|
+
...css.map(href => ({ href, as: 'style' })),
|
|
38
|
+
...js.map(href => ({ href, as: 'script' })),
|
|
39
|
+
]);
|
|
40
|
+
} else {
|
|
41
|
+
await Promise.all([...css, ...js].map(url => fetch(url, { priority: 'low' })));
|
|
42
|
+
}
|
|
43
|
+
} catch (e: any) {
|
|
44
|
+
logService?.warning({
|
|
45
|
+
category: 'Microfrontends.Prefetch',
|
|
46
|
+
message: `Failed to prefetch ${src}`,
|
|
47
|
+
data: e,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return { prefetch };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Link {
|
|
56
|
+
as: string;
|
|
57
|
+
href: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function addLinks(links: Link[]) {
|
|
61
|
+
const existingLinks = document.head.getElementsByTagName('link');
|
|
62
|
+
const existingHrefs = new Set(Array.from(existingLinks).map(({ href }) => href));
|
|
63
|
+
|
|
64
|
+
links.forEach(({ as, href }) => {
|
|
65
|
+
if (!existingHrefs.has(href)) {
|
|
66
|
+
const link = document.createElement('link');
|
|
67
|
+
link.as = as;
|
|
68
|
+
link.crossOrigin = 'anonymous';
|
|
69
|
+
link.href = href;
|
|
70
|
+
link.rel = 'prefetch';
|
|
71
|
+
document.head.append(link);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
describe(`[web-components] supportsPrefetch`, () => {
|
|
2
|
+
beforeEach(() => jest.resetModules());
|
|
3
|
+
|
|
4
|
+
const subject = () => {
|
|
5
|
+
const supportsPrefetch = require('../supports-prefetch').supportsPrefetch;
|
|
6
|
+
return supportsPrefetch();
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
test('returns false', () => {
|
|
10
|
+
expect(subject()).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('when prefetch is supported', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
const mockLink = {
|
|
16
|
+
relList: { supports: (prop: string) => prop === 'prefetch' },
|
|
17
|
+
} as any;
|
|
18
|
+
jest.spyOn(document, 'createElement').mockImplementation(tag => {
|
|
19
|
+
return tag === 'link' ? mockLink : (undefined as any);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('returns true', () => {
|
|
24
|
+
expect(subject()).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -36,7 +36,11 @@ export async function getBundleInfo({
|
|
|
36
36
|
retries,
|
|
37
37
|
onRequestError,
|
|
38
38
|
}: GetBundleInfoProps) {
|
|
39
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
cacheHit,
|
|
41
|
+
data: metadata,
|
|
42
|
+
url: packageUrl,
|
|
43
|
+
} = await getCachedMetadata({
|
|
40
44
|
cache,
|
|
41
45
|
fallbackPackageUrl,
|
|
42
46
|
mainPackageUrl,
|
|
@@ -103,6 +107,7 @@ export async function getBundleInfo({
|
|
|
103
107
|
|
|
104
108
|
return {
|
|
105
109
|
bundledWith,
|
|
110
|
+
cacheHit,
|
|
106
111
|
disposer,
|
|
107
112
|
mfeSupportsRerendering: getMFESupportsRerendering(bundledWith, dependencies),
|
|
108
113
|
mfeHasUniqueChunkNamespace: getMFEHasUniqueChunkNamespace(bundledWith),
|
|
@@ -6,9 +6,9 @@ import { TtlCache } from './ttl-cache';
|
|
|
6
6
|
|
|
7
7
|
const MINUTE = 1000 * 60;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
});
|
|
9
|
+
type MetadataResponse = Awaited<ReturnType<typeof getMetadata>>;
|
|
10
|
+
|
|
11
|
+
const metadataResponseCache = new TtlCache<MetadataResponse>({ ttlMs: 15 * MINUTE });
|
|
12
12
|
|
|
13
13
|
interface GetMetadataProps {
|
|
14
14
|
fallbackPackageUrl?: string;
|
|
@@ -42,30 +42,32 @@ async function getMetadata(props: GetMetadataProps) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export async function getCachedMetadata(
|
|
45
|
+
export async function getCachedMetadata(
|
|
46
|
+
props: GetMetadataProps & { cache: boolean | number }
|
|
47
|
+
): Promise<MetadataResponse & { cacheHit?: boolean }> {
|
|
46
48
|
let cache = props.cache;
|
|
49
|
+
|
|
47
50
|
const cacheKey = props.mainPackageUrl;
|
|
51
|
+
const cachedResponse = cache ? metadataResponseCache.get(cacheKey) : undefined;
|
|
52
|
+
if (cachedResponse) {
|
|
53
|
+
return { ...cachedResponse, cacheHit: true };
|
|
54
|
+
}
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
const metadataResponse = await getMetadata(props);
|
|
57
|
+
const mfeHasUniqueChunkNamespace = getMFEHasUniqueChunkNamespace(
|
|
58
|
+
metadataResponse.data.bundledWith
|
|
59
|
+
);
|
|
50
60
|
|
|
51
|
-
if (!
|
|
52
|
-
|
|
61
|
+
if (cache === true && !mfeHasUniqueChunkNamespace) {
|
|
62
|
+
cache = -1;
|
|
63
|
+
}
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
if (cache) {
|
|
66
|
+
metadataResponseCache.set(
|
|
67
|
+
cacheKey,
|
|
68
|
+
metadataResponse,
|
|
69
|
+
typeof cache === 'number' ? cache : undefined
|
|
56
70
|
);
|
|
57
|
-
|
|
58
|
-
if (cache === true && !mfeHasUniqueChunkNamespace) {
|
|
59
|
-
cache = -1;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (cache) {
|
|
63
|
-
metadataResponseCache.set(
|
|
64
|
-
cacheKey,
|
|
65
|
-
metadataResponse,
|
|
66
|
-
typeof cache === 'number' ? cache : undefined
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
return metadataResponse;
|
package/src/utils/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './get-mfe-supports-rerendering';
|
|
|
6
6
|
export * from './get-version-mismatches';
|
|
7
7
|
export * from './is-versioned-url';
|
|
8
8
|
export * from './load-scripts';
|
|
9
|
+
export * from './supports-prefetch';
|
|
9
10
|
export * from './ttl-cache';
|
|
10
11
|
export * from './validate-bundle-info';
|
|
11
12
|
export * from './web-component';
|
package/src/utils/ttl-cache.ts
CHANGED
|
@@ -6,9 +6,9 @@ export interface TtlCacheOptions {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export class TtlCache<T = any> {
|
|
9
|
-
private data: Map<string, T>;
|
|
10
|
-
private timers: Map<string, ReturnType<typeof setTimeout>>;
|
|
11
|
-
private ttl: number;
|
|
9
|
+
private readonly data: Map<string, T>;
|
|
10
|
+
private readonly timers: Map<string, ReturnType<typeof setTimeout>>;
|
|
11
|
+
private readonly ttl: number;
|
|
12
12
|
|
|
13
13
|
constructor({ ttlMs = DEFAULT_TTL }: TtlCacheOptions = {}) {
|
|
14
14
|
if (ttlMs < 0) {
|