import { Log } from '@servicetitan/log-service'; import { useOptionalDependencies } from '@servicetitan/react-ioc'; import { EXPOSED_DEPENDENCIES_TOKEN, EXPOSED_INSTANCE_DEPENDENCIES_TOKEN } from './common'; import { LoaderProps } from './loader'; import { getBundleInfo, supportsPrefetch } from './utils'; /** * Prefetch and cache bundle info for a given MFE, so that Loader can load it faster later. * @param src - The main package URL for the MFE * @returns Promise resolving to the BundleInfo */ export function usePrefetch() { const [logService, exposedDependencies = EXPOSED_DEPENDENCIES] = useOptionalDependencies( Log, EXPOSED_DEPENDENCIES_TOKEN ); const [exposedInstanceDependencies = EXPOSED_INSTANCE_DEPENDENCIES] = useOptionalDependencies( EXPOSED_INSTANCE_DEPENDENCIES_TOKEN ); const prefetch = async (src: string, { cache = -1 }: Pick, 'cache'> = {}) => { try { const bundleInfo = await getBundleInfo({ exposedDependencies, exposedInstanceDependencies, mainPackageUrl: src, cache, retries: 0, }); if (bundleInfo.cacheHit) { return; } const { css, js } = bundleInfo.urls; if (supportsPrefetch()) { addLinks([ ...css.map(href => ({ href, as: 'style' })), ...js.map(href => ({ href, as: 'script' })), ]); } else { await Promise.all([...css, ...js].map(url => fetch(url, { priority: 'low' }))); } } catch (e: any) { logService?.warning({ category: 'Microfrontends.Prefetch', message: `Failed to prefetch ${src}`, data: e, }); } }; return { prefetch }; } interface Link { as: string; href: string; } function addLinks(links: Link[]) { const existingLinks = document.head.getElementsByTagName('link'); const existingHrefs = new Set(Array.from(existingLinks).map(({ href }) => href)); links.forEach(({ as, href }) => { if (!existingHrefs.has(href)) { const link = document.createElement('link'); link.as = as; link.crossOrigin = 'anonymous'; link.href = href; link.rel = 'prefetch'; document.head.append(link); } }); }