@servicetitan/web-components 32.3.1 → 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.
Files changed (37) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/use-prefetch.d.ts +10 -0
  6. package/dist/use-prefetch.d.ts.map +1 -0
  7. package/dist/use-prefetch.js +71 -0
  8. package/dist/use-prefetch.js.map +1 -0
  9. package/dist/utils/get-bundle-info.d.ts +1 -0
  10. package/dist/utils/get-bundle-info.d.ts.map +1 -1
  11. package/dist/utils/get-bundle-info.js +2 -1
  12. package/dist/utils/get-bundle-info.js.map +1 -1
  13. package/dist/utils/get-cached-metadata.d.ts +7 -3
  14. package/dist/utils/get-cached-metadata.d.ts.map +1 -1
  15. package/dist/utils/get-cached-metadata.js +14 -10
  16. package/dist/utils/get-cached-metadata.js.map +1 -1
  17. package/dist/utils/index.d.ts +1 -0
  18. package/dist/utils/index.d.ts.map +1 -1
  19. package/dist/utils/index.js +1 -0
  20. package/dist/utils/index.js.map +1 -1
  21. package/dist/utils/supports-prefetch.d.ts +2 -0
  22. package/dist/utils/supports-prefetch.d.ts.map +1 -0
  23. package/dist/utils/supports-prefetch.js +11 -0
  24. package/dist/utils/supports-prefetch.js.map +1 -0
  25. package/dist/utils/ttl-cache.d.ts +3 -3
  26. package/dist/utils/ttl-cache.d.ts.map +1 -1
  27. package/dist/utils/ttl-cache.js.map +1 -1
  28. package/package.json +11 -11
  29. package/src/__tests__/use-prefetch.test.tsx +235 -0
  30. package/src/index.ts +1 -0
  31. package/src/use-prefetch.ts +74 -0
  32. package/src/utils/__tests__/supports-prefetch.test.ts +27 -0
  33. package/src/utils/get-bundle-info.ts +6 -1
  34. package/src/utils/get-cached-metadata.ts +23 -21
  35. package/src/utils/index.ts +1 -0
  36. package/src/utils/supports-prefetch.ts +9 -0
  37. 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
@@ -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;AAEpC,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,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
@@ -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
 
11
12
  //# sourceMappingURL=index.js.map
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;;;;;;;;;;;;;;GAiFpB"}
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 { data: metadata, url: packageUrl } = 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 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","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":"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"}
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":"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"}
@@ -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
- export declare function getCachedMetadata(props: GetMetadataProps & {
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;AAWzC,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;AA0BD,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE;;;;GA2B5F"}
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
- let metadataResponse = cache ? metadataResponseCache.get(cacheKey) : undefined;
34
- if (!metadataResponse) {
35
- metadataResponse = await getMetadata(props);
36
- const mfeHasUniqueChunkNamespace = getMFEHasUniqueChunkNamespace(metadataResponse.data.bundledWith);
37
- if (cache === true && !mfeHasUniqueChunkNamespace) {
38
- cache = -1;
39
- }
40
- if (cache) {
41
- metadataResponseCache.set(cacheKey, metadataResponse, typeof cache === 'number' ? cache : undefined);
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\nconst metadataResponseCache = new TtlCache<Awaited<ReturnType<typeof getMetadata>>>({\n ttlMs: 15 * MINUTE,\n});\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(props: GetMetadataProps & { cache: boolean | number }) {\n let cache = props.cache;\n const cacheKey = props.mainPackageUrl;\n\n let metadataResponse = cache ? metadataResponseCache.get(cacheKey) : undefined;\n\n if (!metadataResponse) {\n metadataResponse = await getMetadata(props);\n\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\n return metadataResponse;\n}\n"],"names":["getJson","getCacheSetting","getMFEHasUniqueChunkNamespace","TtlCache","MINUTE","metadataResponseCache","ttlMs","getMetadata","props","fallbackPackageUrl","mainPackageUrl","onRequestError","retries","signal","cache","e","getCachedMetadata","cacheKey","metadataResponse","get","undefined","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;AAEtB,MAAMC,wBAAwB,IAAIF,SAAkD;IAChFG,OAAO,KAAKF;AAChB;AAUA,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,kBAAkBR,KAAqD;IACzF,IAAIM,QAAQN,MAAMM,KAAK;IACvB,MAAMG,WAAWT,MAAME,cAAc;IAErC,IAAIQ,mBAAmBJ,QAAQT,sBAAsBc,GAAG,CAACF,YAAYG;IAErE,IAAI,CAACF,kBAAkB;QACnBA,mBAAmB,MAAMX,YAAYC;QAErC,MAAMa,6BAA6BnB,8BAC/BgB,iBAAiBI,IAAI,CAACC,WAAW;QAGrC,IAAIT,UAAU,QAAQ,CAACO,4BAA4B;YAC/CP,QAAQ,CAAC;QACb;QAEA,IAAIA,OAAO;YACPT,sBAAsBmB,GAAG,CACrBP,UACAC,kBACA,OAAOJ,UAAU,WAAWA,QAAQM;QAE5C;IACJ;IAEA,OAAOF;AACX"}
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"}
@@ -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"}
@@ -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,"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,2 @@
1
+ export declare function supportsPrefetch(): boolean;
2
+ //# sourceMappingURL=supports-prefetch.d.ts.map
@@ -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;IAC7B,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,GAAG,CAAS;gBAER,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
+ {"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,uBAAQO,QAAR,KAAA;QACA,uBAAQM,UAAR,KAAA;QACA,uBAAQR,OAAR,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"}
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.1",
3
+ "version": "32.4.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,17 +15,17 @@
15
15
  "src"
16
16
  ],
17
17
  "dependencies": {
18
- "@servicetitan/error-boundary": "32.3.1",
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"
22
22
  },
23
23
  "devDependencies": {
24
- "@servicetitan/anvil2": "^1.46.11",
24
+ "@servicetitan/anvil2": "^1.47.1",
25
25
  "@servicetitan/design-system": "~14.5.3",
26
- "@servicetitan/launchdarkly-service": "32.3.1",
27
- "@servicetitan/log-service": "32.3.1",
28
- "@servicetitan/react-ioc": "32.3.1",
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",
@@ -41,11 +41,11 @@
41
41
  "shadow-dom-testing-library": "^1.13.1"
42
42
  },
43
43
  "peerDependencies": {
44
- "@servicetitan/anvil2": ">=1.46.11",
44
+ "@servicetitan/anvil2": ">=1.47.1",
45
45
  "@servicetitan/design-system": ">=14.5.3",
46
- "@servicetitan/launchdarkly-service": "32.3.1",
47
- "@servicetitan/log-service": "32.3.1",
48
- "@servicetitan/react-ioc": "32.3.1",
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": "a2d0b80586285dbe4c657a4cf44b4c4fe69362c1"
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
@@ -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
 
11
12
  export type { JSON, JSONPrimitive, Serializable } from './types';
@@ -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 { data: metadata, url: packageUrl } = await getCachedMetadata({
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
- const metadataResponseCache = new TtlCache<Awaited<ReturnType<typeof getMetadata>>>({
10
- ttlMs: 15 * MINUTE,
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(props: GetMetadataProps & { cache: boolean | number }) {
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
- let metadataResponse = cache ? metadataResponseCache.get(cacheKey) : undefined;
56
+ const metadataResponse = await getMetadata(props);
57
+ const mfeHasUniqueChunkNamespace = getMFEHasUniqueChunkNamespace(
58
+ metadataResponse.data.bundledWith
59
+ );
50
60
 
51
- if (!metadataResponse) {
52
- metadataResponse = await getMetadata(props);
61
+ if (cache === true && !mfeHasUniqueChunkNamespace) {
62
+ cache = -1;
63
+ }
53
64
 
54
- const mfeHasUniqueChunkNamespace = getMFEHasUniqueChunkNamespace(
55
- metadataResponse.data.bundledWith
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;
@@ -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';
@@ -0,0 +1,9 @@
1
+ let result: boolean | undefined;
2
+
3
+ export function supportsPrefetch() {
4
+ if (result === undefined) {
5
+ const link = document.createElement('link');
6
+ result = !!link.relList?.supports?.('prefetch');
7
+ }
8
+ return result;
9
+ }
@@ -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) {