@vercel/microfrontends 0.17.3 → 0.18.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/README.md +7 -159
- package/dist/bin/cli.cjs +55 -72
- package/dist/config.cjs +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/{index-f094deb1.d.ts → index-24024799.d.ts} +3 -3
- package/dist/{index-5fcf0863.d.ts → index-ef8657e6.d.ts} +0 -10
- package/dist/microfrontends/server.cjs +33 -66
- package/dist/microfrontends/server.cjs.map +1 -1
- package/dist/microfrontends/server.d.ts +4 -4
- package/dist/microfrontends/server.js +33 -66
- package/dist/microfrontends/server.js.map +1 -1
- package/dist/microfrontends.cjs +1 -1
- package/dist/microfrontends.cjs.map +1 -1
- package/dist/microfrontends.d.ts +4 -4
- package/dist/microfrontends.js +1 -1
- package/dist/microfrontends.js.map +1 -1
- package/dist/next/client.cjs +1 -1
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.d.ts +1 -1
- package/dist/next/client.js +1 -1
- package/dist/next/client.js.map +1 -1
- package/dist/next/config.cjs +33 -66
- package/dist/next/config.cjs.map +1 -1
- package/dist/next/config.js +33 -66
- package/dist/next/config.js.map +1 -1
- package/dist/next/endpoints.d.ts +2 -2
- package/dist/next/middleware.cjs +1 -5
- package/dist/next/middleware.cjs.map +1 -1
- package/dist/next/middleware.js +1 -5
- package/dist/next/middleware.js.map +1 -1
- package/dist/next/testing.cjs +1 -1
- package/dist/next/testing.cjs.map +1 -1
- package/dist/next/testing.d.ts +4 -4
- package/dist/next/testing.js +1 -1
- package/dist/next/testing.js.map +1 -1
- package/dist/overrides.d.ts +3 -3
- package/dist/schema.d.ts +1 -1
- package/dist/{types-5900be7c.d.ts → types-089498fd.d.ts} +1 -1
- package/dist/{types-ecd7b91b.d.ts → types-9f161cec.d.ts} +1 -1
- package/dist/utils/mfe-port.cjs +33 -66
- package/dist/utils/mfe-port.cjs.map +1 -1
- package/dist/utils/mfe-port.js +33 -66
- package/dist/utils/mfe-port.js.map +1 -1
- package/dist/validation.cjs +31 -64
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +31 -64
- package/dist/validation.js.map +1 -1
- package/package.json +22 -6
- package/schema/schema.json +9 -33
package/dist/next/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/client/index.ts","../../src/next/client/link/multi-zones-link.tsx","../../src/config/react/use-client-config.ts","../../src/config/microfrontends-config/client/index.ts","../../src/next/client/prefetch/prefetch-cross-zone-links-context.tsx","../../src/next/client/prefetch/prefetch-cross-zone-links.tsx"],"sourcesContent":["export * from './link';\nexport * from './prefetch';\n","import type { AnchorHTMLAttributes } from 'react';\nimport { forwardRef, useContext } from 'react';\nimport NextLink, { type LinkProps as NextLinkProps } from 'next/link';\nimport { useClientConfig } from '../../../config/react/use-client-config';\nimport { PrefetchCrossZoneLinksContext } from '../prefetch';\n\ninterface BaseProps {\n children: React.ReactNode;\n href: string;\n}\nexport type LinkProps = BaseProps &\n Omit<NextLinkProps, keyof BaseProps> &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps>;\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\nexport function useZoneForHref(href: LinkProps['href'] | undefined): {\n zoneOfHref: string | null;\n isDifferentZone: boolean;\n isLoading: boolean;\n} {\n const { clientConfig, isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n {\n removeFlaggedPathsFromDefault: true,\n },\n );\n const isRelativePath = typeof href === 'string' && href.startsWith('/');\n const zoneOfHref = isRelativePath\n ? clientConfig.getApplicationNameForPath(href)\n : null;\n if (typeof href === 'string' && !href.length) {\n return {\n zoneOfHref: null,\n isDifferentZone: false,\n isLoading: false,\n };\n }\n const isDifferentZone =\n !isRelativePath || (zoneOfHref ? CURRENT_ZONE !== zoneOfHref : false);\n return { zoneOfHref, isDifferentZone, isLoading };\n}\n\n/**\n * A Link component that works with Multi-Zones set-ups and will prefetch the\n * cross zone links automatically.\n */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(\n ({ children, ...props }, ref): JSX.Element => {\n const { prefetchHref } = useContext(PrefetchCrossZoneLinksContext);\n const { zoneOfHref, isDifferentZone, isLoading } = useZoneForHref(\n props.href,\n );\n\n function onHoverPrefetch(): void {\n if (!props.href) {\n return;\n }\n prefetchHref(props.href);\n }\n\n if (isDifferentZone && zoneOfHref !== null) {\n const { prefetch: _, ...rest } = props;\n return (\n <a\n {...rest}\n data-zone={zoneOfHref}\n onFocus={props.prefetch !== false ? onHoverPrefetch : undefined}\n onMouseOver={props.prefetch !== false ? onHoverPrefetch : undefined}\n >\n {children}\n </a>\n );\n }\n\n return (\n <NextLink\n {...props}\n data-zone={!zoneOfHref ? 'null' : 'same'}\n prefetch={props.prefetch ?? (isLoading ? false : undefined)}\n ref={ref}\n >\n {children}\n </NextLink>\n );\n },\n);\nLink.displayName = 'MultiZonesLink';\n","'use client';\n\nimport { useState, useEffect } from 'react';\nimport type { WellKnownClientData } from '../well-known/types';\nimport { MicrofrontendConfigClient } from '../microfrontends-config/client';\n\nlet cachedServerClientConfigPromise: Promise<MicrofrontendConfigClient | null> | null =\n null;\n\nasync function fetchClientConfigFromServer(): Promise<MicrofrontendConfigClient | null> {\n try {\n const response = await fetch(\n '/.well-known/vercel/microfrontends/client-config',\n );\n if (response.status !== 200) {\n return null;\n }\n const responseJson = (await response.json()) as WellKnownClientData;\n return new MicrofrontendConfigClient(responseJson.config);\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Hook to use the client microfrontends configuration. This hook will resolve\n * dynamic paths by fetching the configuration from the server if necessary,\n * allowing the server to specify the values for dynamic paths.\n */\nexport function useClientConfig(\n config: string | undefined,\n {\n removeFlaggedPathsFromDefault,\n }: {\n removeFlaggedPathsFromDefault?: boolean;\n } = {},\n): {\n clientConfig: MicrofrontendConfigClient;\n isLoading: boolean;\n} {\n const [clientConfig, setClientConfig] = useState<MicrofrontendConfigClient>(\n MicrofrontendConfigClient.fromEnv(config, {\n removeFlaggedPaths: removeFlaggedPathsFromDefault,\n }),\n );\n const [isLoading, setIsLoading] = useState(true);\n useEffect(() => {\n if (\n process.env.NODE_ENV === 'test' &&\n process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER !== '1'\n ) {\n setIsLoading(false);\n return;\n }\n // Since we may remove flagged paths from the client config above, we need\n // to use the original client config to determine if the config has any\n // dynamic paths.\n const originalClientConfig = MicrofrontendConfigClient.fromEnv(config);\n // As an optimization, only fetch the config from the server if the\n // microfrontends configuration has any dynamic paths. If it doesn't,\n // then the server won't return any different values.\n const hasDynamicPaths = Object.values(\n originalClientConfig.applications,\n ).some((app) => app.routing?.some((group) => group.flag));\n if (!hasDynamicPaths) {\n setIsLoading(false);\n return;\n }\n if (!cachedServerClientConfigPromise) {\n cachedServerClientConfigPromise = fetchClientConfigFromServer();\n }\n void cachedServerClientConfigPromise\n .then((newConfig) => {\n if (newConfig) {\n setClientConfig((prevConfig) => {\n return prevConfig.isEqual(newConfig) ? prevConfig : newConfig;\n });\n }\n })\n .finally(() => {\n setIsLoading(false);\n });\n }, [config, clientConfig.applications]);\n\n return { clientConfig, isLoading };\n}\n\nexport function resetCachedServerClientConfigPromise(): void {\n cachedServerClientConfigPromise = null;\n}\n","import { pathToRegexp } from 'path-to-regexp';\nimport type { ClientConfig } from './types';\n\ninterface MicrofrontendConfigClientOptions {\n removeFlaggedPaths?: boolean;\n}\n\nexport class MicrofrontendConfigClient {\n applications: ClientConfig['applications'];\n pathCache: Record<string, string> = {};\n private readonly serialized: ClientConfig;\n\n constructor(config: ClientConfig, opts?: MicrofrontendConfigClientOptions) {\n this.serialized = config;\n if (opts?.removeFlaggedPaths) {\n for (const app of Object.values(config.applications)) {\n if (app.routing) {\n app.routing = app.routing.filter((match) => !match.flag);\n }\n }\n }\n this.applications = config.applications;\n }\n\n /**\n * Create a new `MicrofrontendConfigClient` from a JSON string.\n * Config must be passed in to remain framework agnostic\n */\n static fromEnv(\n config: string | undefined,\n opts?: MicrofrontendConfigClientOptions,\n ): MicrofrontendConfigClient {\n if (!config) {\n throw new Error('No microfrontends configuration found');\n }\n return new MicrofrontendConfigClient(\n JSON.parse(config) as ClientConfig,\n opts,\n );\n }\n\n isEqual(other: MicrofrontendConfigClient): boolean {\n return (\n JSON.stringify(this.applications) === JSON.stringify(other.applications)\n );\n }\n\n getApplicationNameForPath(path: string): string | null {\n if (!path.startsWith('/')) {\n throw new Error(`Path must start with a /`);\n }\n\n if (this.pathCache[path]) {\n return this.pathCache[path];\n }\n\n const pathname = new URL(path, 'https://example.com').pathname;\n for (const [name, application] of Object.entries(this.applications)) {\n if (application.routing) {\n for (const group of application.routing) {\n for (const childPath of group.paths) {\n const regexp = pathToRegexp(childPath);\n if (regexp.test(pathname)) {\n this.pathCache[path] = name;\n return name;\n }\n }\n }\n }\n }\n const defaultApplication = Object.entries(this.applications).find(\n ([, application]) => application.default,\n );\n if (!defaultApplication) {\n return null;\n }\n\n this.pathCache[path] = defaultApplication[0];\n return defaultApplication[0];\n }\n\n serialize(): ClientConfig {\n return this.serialized;\n }\n}\n","import React, {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nexport interface PrefetchCrossZoneLinksContext {\n prefetchHref: (href: string) => void;\n}\n\nexport const PrefetchCrossZoneLinksContext =\n createContext<PrefetchCrossZoneLinksContext>({\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n prefetchHref: () => {},\n });\n\nexport function PrefetchCrossZoneLinksProvider({\n children,\n}: {\n children: React.ReactNode;\n}): JSX.Element | null {\n const [seenHrefs, setSeenHrefs] = useState(new Set<string>());\n const [isSafariOrFirefox, setIsSafariOrFirefox] = useState(false);\n\n useEffect(() => {\n setIsSafariOrFirefox(\n typeof navigator !== 'undefined' &&\n (navigator.userAgent.includes('Firefox') ||\n (navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome'))),\n );\n }, []);\n\n const prefetchHref = useCallback(\n (href: string): void => {\n if (!seenHrefs.has(href)) {\n setSeenHrefs(new Set(seenHrefs).add(href));\n }\n },\n [seenHrefs],\n );\n\n const value = useMemo(() => ({ prefetchHref }), [prefetchHref]);\n\n if (!isSafariOrFirefox) {\n return <>{children}</>;\n }\n\n return (\n <PrefetchCrossZoneLinksContext.Provider value={value}>\n {children}\n {[...seenHrefs].map((href) => (\n <link as=\"fetch\" href={href} key={href} rel=\"preload\" />\n ))}\n </PrefetchCrossZoneLinksContext.Provider>\n );\n}\n","import { useEffect, useState } from 'react';\nimport Script from 'next/script';\nimport { useClientConfig } from '../../../config/react/use-client-config';\n\nconst PREFETCH_ATTR = 'data-prefetch';\nconst DATA_ATTR_SELECTORS = {\n anyZone: '[data-zone]',\n external: '[data-zone=\"null\"]',\n sameZone: '[data-zone=\"same\"]',\n prefetch: `[${PREFETCH_ATTR}]`,\n} as const;\n\nconst PREFETCH_ON_HOVER_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n ],\n};\n\nconst PREFETCH_WHEN_VISIBLE_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n { selector_matches: DATA_ATTR_SELECTORS.prefetch },\n ],\n};\n\nfunction checkVisibility(element: Element | null): boolean {\n if (!element) return true;\n\n if ('checkVisibility' in element) {\n return element.checkVisibility({ opacityProperty: true });\n }\n\n // hack to get around TS thinking element is never;\n const el = element as Element;\n const style = window.getComputedStyle(el);\n\n if (\n style.display === 'none' ||\n style.visibility === 'hidden' ||\n style.opacity === '0'\n ) {\n return false;\n }\n\n return checkVisibility(el.parentElement);\n}\n\nexport function PrefetchCrossZoneLinks(): JSX.Element | null {\n const { isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n );\n const [links, setLinks] = useState<HTMLAnchorElement[]>([]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Intersection observer to add the data-prefetch attribute to cross-zone\n * links that have yet to be prefetched and are visible.\n */\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (\n entry.isIntersecting &&\n !entry.target.hasAttribute(PREFETCH_ATTR) &&\n // lazy perform the visibility check for nodes that are intersecting the viewport\n // and have not been prefetched.\n checkVisibility(entry.target)\n ) {\n entry.target.setAttribute(PREFETCH_ATTR, 'true');\n }\n });\n },\n {\n root: null,\n rootMargin: '0px',\n threshold: 0.1,\n },\n );\n\n links.forEach((link) => observer.observe(link));\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading, links]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Mutation observer to notify when new nodes have entered/exited the document\n * or an href has changed.\n */\n const observer = new MutationObserver((mutations) => {\n const hasChanged = mutations.some((mutation) => {\n return (\n (mutation.type === 'childList' && mutation.addedNodes.length > 0) ||\n (mutation.type === 'attributes' && mutation.attributeName === 'href')\n );\n });\n\n if (hasChanged) {\n // Whenever there's a change, add all cross-zone links that haven't been\n // prefetched.\n setLinks(\n Array.from(\n document.querySelectorAll<HTMLAnchorElement>(\n `a${DATA_ATTR_SELECTORS.anyZone}:not(${DATA_ATTR_SELECTORS.prefetch}):not(${DATA_ATTR_SELECTORS.sameZone}):not(${DATA_ATTR_SELECTORS.external})`,\n ),\n ),\n );\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['href'],\n });\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading]);\n\n // Wait till the zone-config loads to take into consideration any\n // flagged routes.\n if (isLoading) {\n return null;\n }\n\n // Prefetch links with moderate eagerness by default, immediately when marked \"data-prefetch\".\n // Prerender links with conservative eagerness by default, immediately when marked \"data-prefetch\".\n const speculationRules = {\n prefetch: [\n {\n eagerness: 'moderate',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n {\n eagerness: 'immediate',\n where: PREFETCH_WHEN_VISIBLE_PREDICATES,\n },\n ],\n prerender: [\n {\n eagerness: 'conservative',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n ],\n };\n\n return (\n <Script\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(speculationRules),\n }}\n id=\"prefetch-zones-links\"\n type=\"speculationrules\"\n />\n );\n}\n"],"mappings":"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","names":["client_exports","__export","Link","PrefetchCrossZoneLinks","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","useZoneForHref","__toCommonJS","import_react","import_link","import_react","import_path_to_regexp","MicrofrontendConfigClient","config","opts","app","match","other","path","pathname","name","application","group","childPath","defaultApplication","cachedServerClientConfigPromise","fetchClientConfigFromServer","response","responseJson","MicrofrontendConfigClient","useClientConfig","config","removeFlaggedPathsFromDefault","clientConfig","setClientConfig","isLoading","setIsLoading","originalClientConfig","app","group","newConfig","prevConfig","import_react","import_jsx_runtime","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","children","seenHrefs","setSeenHrefs","isSafariOrFirefox","setIsSafariOrFirefox","prefetchHref","href","value","import_react","import_script","import_jsx_runtime","PREFETCH_ATTR","DATA_ATTR_SELECTORS","PREFETCH_ON_HOVER_PREDICATES","PREFETCH_WHEN_VISIBLE_PREDICATES","checkVisibility","element","el","style","PrefetchCrossZoneLinks","isLoading","useClientConfig","links","setLinks","observer","entries","entry","link","mutations","mutation","Script","import_jsx_runtime","CURRENT_ZONE","useZoneForHref","href","clientConfig","isLoading","useClientConfig","isRelativePath","zoneOfHref","Link","children","props","ref","prefetchHref","PrefetchCrossZoneLinksContext","isDifferentZone","onHoverPrefetch","_","rest","NextLink"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/client/index.ts","../../src/next/client/link/multi-zones-link.tsx","../../src/config/react/use-client-config.ts","../../src/config/microfrontends-config/client/index.ts","../../src/next/client/prefetch/prefetch-cross-zone-links-context.tsx","../../src/next/client/prefetch/prefetch-cross-zone-links.tsx"],"sourcesContent":["export * from './link';\nexport * from './prefetch';\n","import type { AnchorHTMLAttributes } from 'react';\nimport { forwardRef, useContext } from 'react';\nimport NextLink, { type LinkProps as NextLinkProps } from 'next/link.js';\nimport { useClientConfig } from '../../../config/react/use-client-config';\nimport { PrefetchCrossZoneLinksContext } from '../prefetch';\n\ninterface BaseProps {\n children: React.ReactNode;\n href: string;\n}\nexport type LinkProps = BaseProps &\n Omit<NextLinkProps, keyof BaseProps> &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps>;\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\nexport function useZoneForHref(href: LinkProps['href'] | undefined): {\n zoneOfHref: string | null;\n isDifferentZone: boolean;\n isLoading: boolean;\n} {\n const { clientConfig, isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n {\n removeFlaggedPathsFromDefault: true,\n },\n );\n const isRelativePath = typeof href === 'string' && href.startsWith('/');\n const zoneOfHref = isRelativePath\n ? clientConfig.getApplicationNameForPath(href)\n : null;\n if (typeof href === 'string' && !href.length) {\n return {\n zoneOfHref: null,\n isDifferentZone: false,\n isLoading: false,\n };\n }\n const isDifferentZone =\n !isRelativePath || (zoneOfHref ? CURRENT_ZONE !== zoneOfHref : false);\n return { zoneOfHref, isDifferentZone, isLoading };\n}\n\n/**\n * A Link component that works with Multi-Zones set-ups and will prefetch the\n * cross zone links automatically.\n */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(\n ({ children, ...props }, ref): JSX.Element => {\n const { prefetchHref } = useContext(PrefetchCrossZoneLinksContext);\n const { zoneOfHref, isDifferentZone, isLoading } = useZoneForHref(\n props.href,\n );\n\n function onHoverPrefetch(): void {\n if (!props.href) {\n return;\n }\n prefetchHref(props.href);\n }\n\n if (isDifferentZone && zoneOfHref !== null) {\n const { prefetch: _, ...rest } = props;\n return (\n <a\n {...rest}\n data-zone={zoneOfHref}\n onFocus={props.prefetch !== false ? onHoverPrefetch : undefined}\n onMouseOver={props.prefetch !== false ? onHoverPrefetch : undefined}\n >\n {children}\n </a>\n );\n }\n\n return (\n <NextLink\n {...props}\n data-zone={!zoneOfHref ? 'null' : 'same'}\n prefetch={props.prefetch ?? (isLoading ? false : undefined)}\n ref={ref}\n >\n {children}\n </NextLink>\n );\n },\n);\nLink.displayName = 'MultiZonesLink';\n","'use client';\n\nimport { useState, useEffect } from 'react';\nimport type { WellKnownClientData } from '../well-known/types';\nimport { MicrofrontendConfigClient } from '../microfrontends-config/client';\n\nlet cachedServerClientConfigPromise: Promise<MicrofrontendConfigClient | null> | null =\n null;\n\nasync function fetchClientConfigFromServer(): Promise<MicrofrontendConfigClient | null> {\n try {\n const response = await fetch(\n '/.well-known/vercel/microfrontends/client-config',\n );\n if (response.status !== 200) {\n return null;\n }\n const responseJson = (await response.json()) as WellKnownClientData;\n return new MicrofrontendConfigClient(responseJson.config);\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Hook to use the client microfrontends configuration. This hook will resolve\n * dynamic paths by fetching the configuration from the server if necessary,\n * allowing the server to specify the values for dynamic paths.\n */\nexport function useClientConfig(\n config: string | undefined,\n {\n removeFlaggedPathsFromDefault,\n }: {\n removeFlaggedPathsFromDefault?: boolean;\n } = {},\n): {\n clientConfig: MicrofrontendConfigClient;\n isLoading: boolean;\n} {\n const [clientConfig, setClientConfig] = useState<MicrofrontendConfigClient>(\n MicrofrontendConfigClient.fromEnv(config, {\n removeFlaggedPaths: removeFlaggedPathsFromDefault,\n }),\n );\n const [isLoading, setIsLoading] = useState(true);\n useEffect(() => {\n if (\n process.env.NODE_ENV === 'test' &&\n process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER !== '1'\n ) {\n setIsLoading(false);\n return;\n }\n // Since we may remove flagged paths from the client config above, we need\n // to use the original client config to determine if the config has any\n // dynamic paths.\n const originalClientConfig = MicrofrontendConfigClient.fromEnv(config);\n // As an optimization, only fetch the config from the server if the\n // microfrontends configuration has any dynamic paths. If it doesn't,\n // then the server won't return any different values.\n const hasDynamicPaths = Object.values(\n originalClientConfig.applications,\n ).some((app) => app.routing?.some((group) => group.flag));\n if (!hasDynamicPaths) {\n setIsLoading(false);\n return;\n }\n if (!cachedServerClientConfigPromise) {\n cachedServerClientConfigPromise = fetchClientConfigFromServer();\n }\n void cachedServerClientConfigPromise\n .then((newConfig) => {\n if (newConfig) {\n setClientConfig((prevConfig) => {\n return prevConfig.isEqual(newConfig) ? prevConfig : newConfig;\n });\n }\n })\n .finally(() => {\n setIsLoading(false);\n });\n }, [config, clientConfig.applications]);\n\n return { clientConfig, isLoading };\n}\n\nexport function resetCachedServerClientConfigPromise(): void {\n cachedServerClientConfigPromise = null;\n}\n","import { pathToRegexp } from 'path-to-regexp';\nimport type { ClientConfig } from './types';\n\ninterface MicrofrontendConfigClientOptions {\n removeFlaggedPaths?: boolean;\n}\n\nexport class MicrofrontendConfigClient {\n applications: ClientConfig['applications'];\n pathCache: Record<string, string> = {};\n private readonly serialized: ClientConfig;\n\n constructor(config: ClientConfig, opts?: MicrofrontendConfigClientOptions) {\n this.serialized = config;\n if (opts?.removeFlaggedPaths) {\n for (const app of Object.values(config.applications)) {\n if (app.routing) {\n app.routing = app.routing.filter((match) => !match.flag);\n }\n }\n }\n this.applications = config.applications;\n }\n\n /**\n * Create a new `MicrofrontendConfigClient` from a JSON string.\n * Config must be passed in to remain framework agnostic\n */\n static fromEnv(\n config: string | undefined,\n opts?: MicrofrontendConfigClientOptions,\n ): MicrofrontendConfigClient {\n if (!config) {\n throw new Error('No microfrontends configuration found');\n }\n return new MicrofrontendConfigClient(\n JSON.parse(config) as ClientConfig,\n opts,\n );\n }\n\n isEqual(other: MicrofrontendConfigClient): boolean {\n return (\n JSON.stringify(this.applications) === JSON.stringify(other.applications)\n );\n }\n\n getApplicationNameForPath(path: string): string | null {\n if (!path.startsWith('/')) {\n throw new Error(`Path must start with a /`);\n }\n\n if (this.pathCache[path]) {\n return this.pathCache[path];\n }\n\n const pathname = new URL(path, 'https://example.com').pathname;\n for (const [name, application] of Object.entries(this.applications)) {\n if (application.routing) {\n for (const group of application.routing) {\n for (const childPath of group.paths) {\n const regexp = pathToRegexp(childPath);\n if (regexp.test(pathname)) {\n this.pathCache[path] = name;\n return name;\n }\n }\n }\n }\n }\n const defaultApplication = Object.entries(this.applications).find(\n ([, application]) => application.default,\n );\n if (!defaultApplication) {\n return null;\n }\n\n this.pathCache[path] = defaultApplication[0];\n return defaultApplication[0];\n }\n\n serialize(): ClientConfig {\n return this.serialized;\n }\n}\n","import React, {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nexport interface PrefetchCrossZoneLinksContext {\n prefetchHref: (href: string) => void;\n}\n\nexport const PrefetchCrossZoneLinksContext =\n createContext<PrefetchCrossZoneLinksContext>({\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n prefetchHref: () => {},\n });\n\nexport function PrefetchCrossZoneLinksProvider({\n children,\n}: {\n children: React.ReactNode;\n}): JSX.Element | null {\n const [seenHrefs, setSeenHrefs] = useState(new Set<string>());\n const [isSafariOrFirefox, setIsSafariOrFirefox] = useState(false);\n\n useEffect(() => {\n setIsSafariOrFirefox(\n typeof navigator !== 'undefined' &&\n (navigator.userAgent.includes('Firefox') ||\n (navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome'))),\n );\n }, []);\n\n const prefetchHref = useCallback(\n (href: string): void => {\n if (!seenHrefs.has(href)) {\n setSeenHrefs(new Set(seenHrefs).add(href));\n }\n },\n [seenHrefs],\n );\n\n const value = useMemo(() => ({ prefetchHref }), [prefetchHref]);\n\n if (!isSafariOrFirefox) {\n return <>{children}</>;\n }\n\n return (\n <PrefetchCrossZoneLinksContext.Provider value={value}>\n {children}\n {[...seenHrefs].map((href) => (\n <link as=\"fetch\" href={href} key={href} rel=\"preload\" />\n ))}\n </PrefetchCrossZoneLinksContext.Provider>\n );\n}\n","import { useEffect, useState } from 'react';\nimport Script from 'next/script.js';\nimport { useClientConfig } from '../../../config/react/use-client-config';\n\nconst PREFETCH_ATTR = 'data-prefetch';\nconst DATA_ATTR_SELECTORS = {\n anyZone: '[data-zone]',\n external: '[data-zone=\"null\"]',\n sameZone: '[data-zone=\"same\"]',\n prefetch: `[${PREFETCH_ATTR}]`,\n} as const;\n\nconst PREFETCH_ON_HOVER_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n ],\n};\n\nconst PREFETCH_WHEN_VISIBLE_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n { selector_matches: DATA_ATTR_SELECTORS.prefetch },\n ],\n};\n\nfunction checkVisibility(element: Element | null): boolean {\n if (!element) return true;\n\n if ('checkVisibility' in element) {\n return element.checkVisibility({ opacityProperty: true });\n }\n\n // hack to get around TS thinking element is never;\n const el = element as Element;\n const style = window.getComputedStyle(el);\n\n if (\n style.display === 'none' ||\n style.visibility === 'hidden' ||\n style.opacity === '0'\n ) {\n return false;\n }\n\n return checkVisibility(el.parentElement);\n}\n\nexport function PrefetchCrossZoneLinks(): JSX.Element | null {\n const { isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n );\n const [links, setLinks] = useState<HTMLAnchorElement[]>([]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Intersection observer to add the data-prefetch attribute to cross-zone\n * links that have yet to be prefetched and are visible.\n */\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (\n entry.isIntersecting &&\n !entry.target.hasAttribute(PREFETCH_ATTR) &&\n // lazy perform the visibility check for nodes that are intersecting the viewport\n // and have not been prefetched.\n checkVisibility(entry.target)\n ) {\n entry.target.setAttribute(PREFETCH_ATTR, 'true');\n }\n });\n },\n {\n root: null,\n rootMargin: '0px',\n threshold: 0.1,\n },\n );\n\n links.forEach((link) => observer.observe(link));\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading, links]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Mutation observer to notify when new nodes have entered/exited the document\n * or an href has changed.\n */\n const observer = new MutationObserver((mutations) => {\n const hasChanged = mutations.some((mutation) => {\n return (\n (mutation.type === 'childList' && mutation.addedNodes.length > 0) ||\n (mutation.type === 'attributes' && mutation.attributeName === 'href')\n );\n });\n\n if (hasChanged) {\n // Whenever there's a change, add all cross-zone links that haven't been\n // prefetched.\n setLinks(\n Array.from(\n document.querySelectorAll<HTMLAnchorElement>(\n `a${DATA_ATTR_SELECTORS.anyZone}:not(${DATA_ATTR_SELECTORS.prefetch}):not(${DATA_ATTR_SELECTORS.sameZone}):not(${DATA_ATTR_SELECTORS.external})`,\n ),\n ),\n );\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['href'],\n });\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading]);\n\n // Wait till the zone-config loads to take into consideration any\n // flagged routes.\n if (isLoading) {\n return null;\n }\n\n // Prefetch links with moderate eagerness by default, immediately when marked \"data-prefetch\".\n // Prerender links with conservative eagerness by default, immediately when marked \"data-prefetch\".\n const speculationRules = {\n prefetch: [\n {\n eagerness: 'moderate',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n {\n eagerness: 'immediate',\n where: PREFETCH_WHEN_VISIBLE_PREDICATES,\n },\n ],\n prerender: [\n {\n eagerness: 'conservative',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n ],\n };\n\n return (\n <Script\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(speculationRules),\n }}\n id=\"prefetch-zones-links\"\n type=\"speculationrules\"\n />\n );\n}\n"],"mappings":"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","names":["client_exports","__export","Link","PrefetchCrossZoneLinks","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","useZoneForHref","__toCommonJS","import_react","import_link","import_react","import_path_to_regexp","MicrofrontendConfigClient","config","opts","app","match","other","path","pathname","name","application","group","childPath","defaultApplication","cachedServerClientConfigPromise","fetchClientConfigFromServer","response","responseJson","MicrofrontendConfigClient","useClientConfig","config","removeFlaggedPathsFromDefault","clientConfig","setClientConfig","isLoading","setIsLoading","originalClientConfig","app","group","newConfig","prevConfig","import_react","import_jsx_runtime","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","children","seenHrefs","setSeenHrefs","isSafariOrFirefox","setIsSafariOrFirefox","prefetchHref","href","value","import_react","import_script","import_jsx_runtime","PREFETCH_ATTR","DATA_ATTR_SELECTORS","PREFETCH_ON_HOVER_PREDICATES","PREFETCH_WHEN_VISIBLE_PREDICATES","checkVisibility","element","el","style","PrefetchCrossZoneLinks","isLoading","useClientConfig","links","setLinks","observer","entries","entry","link","mutations","mutation","Script","import_jsx_runtime","CURRENT_ZONE","useZoneForHref","href","clientConfig","isLoading","useClientConfig","isRelativePath","zoneOfHref","Link","children","props","ref","prefetchHref","PrefetchCrossZoneLinksContext","isDifferentZone","onHoverPrefetch","_","rest","NextLink"]}
|
package/dist/next/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import react__default, { AnchorHTMLAttributes } from 'react';
|
|
3
3
|
import * as url from 'url';
|
|
4
|
-
import { LinkProps as LinkProps$1 } from 'next/link';
|
|
4
|
+
import { LinkProps as LinkProps$1 } from 'next/link.js';
|
|
5
5
|
|
|
6
6
|
interface BaseProps {
|
|
7
7
|
children: React.ReactNode;
|
package/dist/next/client.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import{forwardRef as H,useContext as Z}from"react";import I from"next/link";import{useState as g,useEffect as N}from"react";import{pathToRegexp as _}from"path-to-regexp";var l=class{constructor(e,r){this.pathCache={};if(this.serialized=e,r?.removeFlaggedPaths)for(let i of Object.values(e.applications))i.routing&&(i.routing=i.routing.filter(n=>!n.flag));this.applications=e.applications}static fromEnv(e,r){if(!e)throw new Error("No microfrontends configuration found");return new l(JSON.parse(e),r)}isEqual(e){return JSON.stringify(this.applications)===JSON.stringify(e.applications)}getApplicationNameForPath(e){if(!e.startsWith("/"))throw new Error("Path must start with a /");if(this.pathCache[e])return this.pathCache[e];let r=new URL(e,"https://example.com").pathname;for(let[n,o]of Object.entries(this.applications))if(o.routing){for(let a of o.routing)for(let s of a.paths)if(_(s).test(r))return this.pathCache[e]=n,n}let i=Object.entries(this.applications).find(([,n])=>n.default);return i?(this.pathCache[e]=i[0],i[0]):null}serialize(){return this.serialized}};var h=null;async function O(){try{let t=await fetch("/.well-known/vercel/microfrontends/client-config");if(t.status!==200)return null;let e=await t.json();return new l(e.config)}catch{return null}}function p(t,{removeFlaggedPathsFromDefault:e}={}){let[r,i]=g(l.fromEnv(t,{removeFlaggedPaths:e})),[n,o]=g(!0);return N(()=>{if(process.env.NODE_ENV==="test"&&process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER!=="1"){o(!1);return}let a=l.fromEnv(t);if(!Object.values(a.applications).some(c=>c.routing?.some(u=>u.flag))){o(!1);return}h||(h=O()),h.then(c=>{c&&i(u=>u.isEqual(c)?u:c)}).finally(()=>{o(!1)})},[t,r.applications]),{clientConfig:r,isLoading:n}}import{createContext as b,useCallback as x,useEffect as k,useMemo as R,useState as C}from"react";import{Fragment as S,jsx as E,jsxs as F}from"react/jsx-runtime";var d=b({prefetchHref:()=>{}});function G({children:t}){let[e,r]=C(new Set),[i,n]=C(!1);k(()=>{n(typeof navigator<"u"&&(navigator.userAgent.includes("Firefox")||navigator.userAgent.includes("Safari")&&!navigator.userAgent.includes("Chrome")))},[]);let o=x(s=>{e.has(s)||r(new Set(e).add(s))},[e]),a=R(()=>({prefetchHref:o}),[o]);return i?F(d.Provider,{value:a,children:[t,[...e].map(s=>E("link",{as:"fetch",href:s,rel:"preload"},s))]}):E(S,{children:t})}import{useEffect as P,useState as T}from"react";import A from"next/script";import{jsx as w}from"react/jsx-runtime";var m="data-prefetch",f={anyZone:"[data-zone]",external:'[data-zone="null"]',sameZone:'[data-zone="same"]',prefetch:`[${m}]`},v={and:[{href_matches:"/*"},{selector_matches:f.anyZone},{not:{selector_matches:f.sameZone}},{not:{selector_matches:f.external}}]},M={and:[{href_matches:"/*"},{selector_matches:f.anyZone},{not:{selector_matches:f.sameZone}},{not:{selector_matches:f.external}},{selector_matches:f.prefetch}]};function L(t){if(!t)return!0;if("checkVisibility"in t)return t.checkVisibility({opacityProperty:!0});let e=t,r=window.getComputedStyle(e);return r.display==="none"||r.visibility==="hidden"||r.opacity==="0"?!1:L(e.parentElement)}function te(){let{isLoading:t}=p(process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG),[e,r]=T([]);return P(()=>{if(t)return;let n=new IntersectionObserver(o=>{o.forEach(a=>{a.isIntersecting&&!a.target.hasAttribute(m)&&L(a.target)&&a.target.setAttribute(m,"true")})},{root:null,rootMargin:"0px",threshold:.1});return e.forEach(o=>n.observe(o)),()=>{n.disconnect()}},[t,e]),P(()=>{if(t)return;let n=new MutationObserver(o=>{o.some(s=>s.type==="childList"&&s.addedNodes.length>0||s.type==="attributes"&&s.attributeName==="href")&&r(Array.from(document.querySelectorAll(`a${f.anyZone}:not(${f.prefetch}):not(${f.sameZone}):not(${f.external})`)))});return n.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["href"]}),()=>{n.disconnect()}},[t]),t?null:w(A,{dangerouslySetInnerHTML:{__html:JSON.stringify({prefetch:[{eagerness:"moderate",where:v},{eagerness:"immediate",where:M}],prerender:[{eagerness:"conservative",where:v}]})},id:"prefetch-zones-links",type:"speculationrules"})}import{jsx as y}from"react/jsx-runtime";var D=process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;function z(t){let{clientConfig:e,isLoading:r}=p(process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,{removeFlaggedPathsFromDefault:!0}),i=typeof t=="string"&&t.startsWith("/"),n=i?e.getApplicationNameForPath(t):null;return typeof t=="string"&&!t.length?{zoneOfHref:null,isDifferentZone:!1,isLoading:!1}:{zoneOfHref:n,isDifferentZone:!i||(n?D!==n:!1),isLoading:r}}var B=H(({children:t,...e},r)=>{let{prefetchHref:i}=Z(d),{zoneOfHref:n,isDifferentZone:o,isLoading:a}=z(e.href);function s(){e.href&&i(e.href)}if(o&&n!==null){let{prefetch:c,...u}=e;return y("a",{...u,"data-zone":n,onFocus:e.prefetch!==!1?s:void 0,onMouseOver:e.prefetch!==!1?s:void 0,children:t})}return y(I,{...e,"data-zone":n?"same":"null",prefetch:e.prefetch??(a?!1:void 0),ref:r,children:t})});B.displayName="MultiZonesLink";export{B as Link,te as PrefetchCrossZoneLinks,d as PrefetchCrossZoneLinksContext,G as PrefetchCrossZoneLinksProvider,z as useZoneForHref};
|
|
2
|
+
import{forwardRef as H,useContext as Z}from"react";import I from"next/link.js";import{useState as g,useEffect as N}from"react";import{pathToRegexp as _}from"path-to-regexp";var l=class{constructor(e,r){this.pathCache={};if(this.serialized=e,r?.removeFlaggedPaths)for(let i of Object.values(e.applications))i.routing&&(i.routing=i.routing.filter(n=>!n.flag));this.applications=e.applications}static fromEnv(e,r){if(!e)throw new Error("No microfrontends configuration found");return new l(JSON.parse(e),r)}isEqual(e){return JSON.stringify(this.applications)===JSON.stringify(e.applications)}getApplicationNameForPath(e){if(!e.startsWith("/"))throw new Error("Path must start with a /");if(this.pathCache[e])return this.pathCache[e];let r=new URL(e,"https://example.com").pathname;for(let[n,o]of Object.entries(this.applications))if(o.routing){for(let a of o.routing)for(let s of a.paths)if(_(s).test(r))return this.pathCache[e]=n,n}let i=Object.entries(this.applications).find(([,n])=>n.default);return i?(this.pathCache[e]=i[0],i[0]):null}serialize(){return this.serialized}};var h=null;async function O(){try{let t=await fetch("/.well-known/vercel/microfrontends/client-config");if(t.status!==200)return null;let e=await t.json();return new l(e.config)}catch{return null}}function p(t,{removeFlaggedPathsFromDefault:e}={}){let[r,i]=g(l.fromEnv(t,{removeFlaggedPaths:e})),[n,o]=g(!0);return N(()=>{if(process.env.NODE_ENV==="test"&&process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER!=="1"){o(!1);return}let a=l.fromEnv(t);if(!Object.values(a.applications).some(c=>c.routing?.some(u=>u.flag))){o(!1);return}h||(h=O()),h.then(c=>{c&&i(u=>u.isEqual(c)?u:c)}).finally(()=>{o(!1)})},[t,r.applications]),{clientConfig:r,isLoading:n}}import{createContext as b,useCallback as x,useEffect as k,useMemo as R,useState as C}from"react";import{Fragment as S,jsx as E,jsxs as F}from"react/jsx-runtime";var d=b({prefetchHref:()=>{}});function G({children:t}){let[e,r]=C(new Set),[i,n]=C(!1);k(()=>{n(typeof navigator<"u"&&(navigator.userAgent.includes("Firefox")||navigator.userAgent.includes("Safari")&&!navigator.userAgent.includes("Chrome")))},[]);let o=x(s=>{e.has(s)||r(new Set(e).add(s))},[e]),a=R(()=>({prefetchHref:o}),[o]);return i?F(d.Provider,{value:a,children:[t,[...e].map(s=>E("link",{as:"fetch",href:s,rel:"preload"},s))]}):E(S,{children:t})}import{useEffect as P,useState as T}from"react";import A from"next/script.js";import{jsx as w}from"react/jsx-runtime";var m="data-prefetch",f={anyZone:"[data-zone]",external:'[data-zone="null"]',sameZone:'[data-zone="same"]',prefetch:`[${m}]`},v={and:[{href_matches:"/*"},{selector_matches:f.anyZone},{not:{selector_matches:f.sameZone}},{not:{selector_matches:f.external}}]},M={and:[{href_matches:"/*"},{selector_matches:f.anyZone},{not:{selector_matches:f.sameZone}},{not:{selector_matches:f.external}},{selector_matches:f.prefetch}]};function L(t){if(!t)return!0;if("checkVisibility"in t)return t.checkVisibility({opacityProperty:!0});let e=t,r=window.getComputedStyle(e);return r.display==="none"||r.visibility==="hidden"||r.opacity==="0"?!1:L(e.parentElement)}function te(){let{isLoading:t}=p(process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG),[e,r]=T([]);return P(()=>{if(t)return;let n=new IntersectionObserver(o=>{o.forEach(a=>{a.isIntersecting&&!a.target.hasAttribute(m)&&L(a.target)&&a.target.setAttribute(m,"true")})},{root:null,rootMargin:"0px",threshold:.1});return e.forEach(o=>n.observe(o)),()=>{n.disconnect()}},[t,e]),P(()=>{if(t)return;let n=new MutationObserver(o=>{o.some(s=>s.type==="childList"&&s.addedNodes.length>0||s.type==="attributes"&&s.attributeName==="href")&&r(Array.from(document.querySelectorAll(`a${f.anyZone}:not(${f.prefetch}):not(${f.sameZone}):not(${f.external})`)))});return n.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["href"]}),()=>{n.disconnect()}},[t]),t?null:w(A,{dangerouslySetInnerHTML:{__html:JSON.stringify({prefetch:[{eagerness:"moderate",where:v},{eagerness:"immediate",where:M}],prerender:[{eagerness:"conservative",where:v}]})},id:"prefetch-zones-links",type:"speculationrules"})}import{jsx as y}from"react/jsx-runtime";var D=process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;function z(t){let{clientConfig:e,isLoading:r}=p(process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,{removeFlaggedPathsFromDefault:!0}),i=typeof t=="string"&&t.startsWith("/"),n=i?e.getApplicationNameForPath(t):null;return typeof t=="string"&&!t.length?{zoneOfHref:null,isDifferentZone:!1,isLoading:!1}:{zoneOfHref:n,isDifferentZone:!i||(n?D!==n:!1),isLoading:r}}var B=H(({children:t,...e},r)=>{let{prefetchHref:i}=Z(d),{zoneOfHref:n,isDifferentZone:o,isLoading:a}=z(e.href);function s(){e.href&&i(e.href)}if(o&&n!==null){let{prefetch:c,...u}=e;return y("a",{...u,"data-zone":n,onFocus:e.prefetch!==!1?s:void 0,onMouseOver:e.prefetch!==!1?s:void 0,children:t})}return y(I,{...e,"data-zone":n?"same":"null",prefetch:e.prefetch??(a?!1:void 0),ref:r,children:t})});B.displayName="MultiZonesLink";export{B as Link,te as PrefetchCrossZoneLinks,d as PrefetchCrossZoneLinksContext,G as PrefetchCrossZoneLinksProvider,z as useZoneForHref};
|
|
3
3
|
//# sourceMappingURL=client.js.map
|
package/dist/next/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/client/link/multi-zones-link.tsx","../../src/config/react/use-client-config.ts","../../src/config/microfrontends-config/client/index.ts","../../src/next/client/prefetch/prefetch-cross-zone-links-context.tsx","../../src/next/client/prefetch/prefetch-cross-zone-links.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes } from 'react';\nimport { forwardRef, useContext } from 'react';\nimport NextLink, { type LinkProps as NextLinkProps } from 'next/link';\nimport { useClientConfig } from '../../../config/react/use-client-config';\nimport { PrefetchCrossZoneLinksContext } from '../prefetch';\n\ninterface BaseProps {\n children: React.ReactNode;\n href: string;\n}\nexport type LinkProps = BaseProps &\n Omit<NextLinkProps, keyof BaseProps> &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps>;\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\nexport function useZoneForHref(href: LinkProps['href'] | undefined): {\n zoneOfHref: string | null;\n isDifferentZone: boolean;\n isLoading: boolean;\n} {\n const { clientConfig, isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n {\n removeFlaggedPathsFromDefault: true,\n },\n );\n const isRelativePath = typeof href === 'string' && href.startsWith('/');\n const zoneOfHref = isRelativePath\n ? clientConfig.getApplicationNameForPath(href)\n : null;\n if (typeof href === 'string' && !href.length) {\n return {\n zoneOfHref: null,\n isDifferentZone: false,\n isLoading: false,\n };\n }\n const isDifferentZone =\n !isRelativePath || (zoneOfHref ? CURRENT_ZONE !== zoneOfHref : false);\n return { zoneOfHref, isDifferentZone, isLoading };\n}\n\n/**\n * A Link component that works with Multi-Zones set-ups and will prefetch the\n * cross zone links automatically.\n */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(\n ({ children, ...props }, ref): JSX.Element => {\n const { prefetchHref } = useContext(PrefetchCrossZoneLinksContext);\n const { zoneOfHref, isDifferentZone, isLoading } = useZoneForHref(\n props.href,\n );\n\n function onHoverPrefetch(): void {\n if (!props.href) {\n return;\n }\n prefetchHref(props.href);\n }\n\n if (isDifferentZone && zoneOfHref !== null) {\n const { prefetch: _, ...rest } = props;\n return (\n <a\n {...rest}\n data-zone={zoneOfHref}\n onFocus={props.prefetch !== false ? onHoverPrefetch : undefined}\n onMouseOver={props.prefetch !== false ? onHoverPrefetch : undefined}\n >\n {children}\n </a>\n );\n }\n\n return (\n <NextLink\n {...props}\n data-zone={!zoneOfHref ? 'null' : 'same'}\n prefetch={props.prefetch ?? (isLoading ? false : undefined)}\n ref={ref}\n >\n {children}\n </NextLink>\n );\n },\n);\nLink.displayName = 'MultiZonesLink';\n","'use client';\n\nimport { useState, useEffect } from 'react';\nimport type { WellKnownClientData } from '../well-known/types';\nimport { MicrofrontendConfigClient } from '../microfrontends-config/client';\n\nlet cachedServerClientConfigPromise: Promise<MicrofrontendConfigClient | null> | null =\n null;\n\nasync function fetchClientConfigFromServer(): Promise<MicrofrontendConfigClient | null> {\n try {\n const response = await fetch(\n '/.well-known/vercel/microfrontends/client-config',\n );\n if (response.status !== 200) {\n return null;\n }\n const responseJson = (await response.json()) as WellKnownClientData;\n return new MicrofrontendConfigClient(responseJson.config);\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Hook to use the client microfrontends configuration. This hook will resolve\n * dynamic paths by fetching the configuration from the server if necessary,\n * allowing the server to specify the values for dynamic paths.\n */\nexport function useClientConfig(\n config: string | undefined,\n {\n removeFlaggedPathsFromDefault,\n }: {\n removeFlaggedPathsFromDefault?: boolean;\n } = {},\n): {\n clientConfig: MicrofrontendConfigClient;\n isLoading: boolean;\n} {\n const [clientConfig, setClientConfig] = useState<MicrofrontendConfigClient>(\n MicrofrontendConfigClient.fromEnv(config, {\n removeFlaggedPaths: removeFlaggedPathsFromDefault,\n }),\n );\n const [isLoading, setIsLoading] = useState(true);\n useEffect(() => {\n if (\n process.env.NODE_ENV === 'test' &&\n process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER !== '1'\n ) {\n setIsLoading(false);\n return;\n }\n // Since we may remove flagged paths from the client config above, we need\n // to use the original client config to determine if the config has any\n // dynamic paths.\n const originalClientConfig = MicrofrontendConfigClient.fromEnv(config);\n // As an optimization, only fetch the config from the server if the\n // microfrontends configuration has any dynamic paths. If it doesn't,\n // then the server won't return any different values.\n const hasDynamicPaths = Object.values(\n originalClientConfig.applications,\n ).some((app) => app.routing?.some((group) => group.flag));\n if (!hasDynamicPaths) {\n setIsLoading(false);\n return;\n }\n if (!cachedServerClientConfigPromise) {\n cachedServerClientConfigPromise = fetchClientConfigFromServer();\n }\n void cachedServerClientConfigPromise\n .then((newConfig) => {\n if (newConfig) {\n setClientConfig((prevConfig) => {\n return prevConfig.isEqual(newConfig) ? prevConfig : newConfig;\n });\n }\n })\n .finally(() => {\n setIsLoading(false);\n });\n }, [config, clientConfig.applications]);\n\n return { clientConfig, isLoading };\n}\n\nexport function resetCachedServerClientConfigPromise(): void {\n cachedServerClientConfigPromise = null;\n}\n","import { pathToRegexp } from 'path-to-regexp';\nimport type { ClientConfig } from './types';\n\ninterface MicrofrontendConfigClientOptions {\n removeFlaggedPaths?: boolean;\n}\n\nexport class MicrofrontendConfigClient {\n applications: ClientConfig['applications'];\n pathCache: Record<string, string> = {};\n private readonly serialized: ClientConfig;\n\n constructor(config: ClientConfig, opts?: MicrofrontendConfigClientOptions) {\n this.serialized = config;\n if (opts?.removeFlaggedPaths) {\n for (const app of Object.values(config.applications)) {\n if (app.routing) {\n app.routing = app.routing.filter((match) => !match.flag);\n }\n }\n }\n this.applications = config.applications;\n }\n\n /**\n * Create a new `MicrofrontendConfigClient` from a JSON string.\n * Config must be passed in to remain framework agnostic\n */\n static fromEnv(\n config: string | undefined,\n opts?: MicrofrontendConfigClientOptions,\n ): MicrofrontendConfigClient {\n if (!config) {\n throw new Error('No microfrontends configuration found');\n }\n return new MicrofrontendConfigClient(\n JSON.parse(config) as ClientConfig,\n opts,\n );\n }\n\n isEqual(other: MicrofrontendConfigClient): boolean {\n return (\n JSON.stringify(this.applications) === JSON.stringify(other.applications)\n );\n }\n\n getApplicationNameForPath(path: string): string | null {\n if (!path.startsWith('/')) {\n throw new Error(`Path must start with a /`);\n }\n\n if (this.pathCache[path]) {\n return this.pathCache[path];\n }\n\n const pathname = new URL(path, 'https://example.com').pathname;\n for (const [name, application] of Object.entries(this.applications)) {\n if (application.routing) {\n for (const group of application.routing) {\n for (const childPath of group.paths) {\n const regexp = pathToRegexp(childPath);\n if (regexp.test(pathname)) {\n this.pathCache[path] = name;\n return name;\n }\n }\n }\n }\n }\n const defaultApplication = Object.entries(this.applications).find(\n ([, application]) => application.default,\n );\n if (!defaultApplication) {\n return null;\n }\n\n this.pathCache[path] = defaultApplication[0];\n return defaultApplication[0];\n }\n\n serialize(): ClientConfig {\n return this.serialized;\n }\n}\n","import React, {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nexport interface PrefetchCrossZoneLinksContext {\n prefetchHref: (href: string) => void;\n}\n\nexport const PrefetchCrossZoneLinksContext =\n createContext<PrefetchCrossZoneLinksContext>({\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n prefetchHref: () => {},\n });\n\nexport function PrefetchCrossZoneLinksProvider({\n children,\n}: {\n children: React.ReactNode;\n}): JSX.Element | null {\n const [seenHrefs, setSeenHrefs] = useState(new Set<string>());\n const [isSafariOrFirefox, setIsSafariOrFirefox] = useState(false);\n\n useEffect(() => {\n setIsSafariOrFirefox(\n typeof navigator !== 'undefined' &&\n (navigator.userAgent.includes('Firefox') ||\n (navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome'))),\n );\n }, []);\n\n const prefetchHref = useCallback(\n (href: string): void => {\n if (!seenHrefs.has(href)) {\n setSeenHrefs(new Set(seenHrefs).add(href));\n }\n },\n [seenHrefs],\n );\n\n const value = useMemo(() => ({ prefetchHref }), [prefetchHref]);\n\n if (!isSafariOrFirefox) {\n return <>{children}</>;\n }\n\n return (\n <PrefetchCrossZoneLinksContext.Provider value={value}>\n {children}\n {[...seenHrefs].map((href) => (\n <link as=\"fetch\" href={href} key={href} rel=\"preload\" />\n ))}\n </PrefetchCrossZoneLinksContext.Provider>\n );\n}\n","import { useEffect, useState } from 'react';\nimport Script from 'next/script';\nimport { useClientConfig } from '../../../config/react/use-client-config';\n\nconst PREFETCH_ATTR = 'data-prefetch';\nconst DATA_ATTR_SELECTORS = {\n anyZone: '[data-zone]',\n external: '[data-zone=\"null\"]',\n sameZone: '[data-zone=\"same\"]',\n prefetch: `[${PREFETCH_ATTR}]`,\n} as const;\n\nconst PREFETCH_ON_HOVER_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n ],\n};\n\nconst PREFETCH_WHEN_VISIBLE_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n { selector_matches: DATA_ATTR_SELECTORS.prefetch },\n ],\n};\n\nfunction checkVisibility(element: Element | null): boolean {\n if (!element) return true;\n\n if ('checkVisibility' in element) {\n return element.checkVisibility({ opacityProperty: true });\n }\n\n // hack to get around TS thinking element is never;\n const el = element as Element;\n const style = window.getComputedStyle(el);\n\n if (\n style.display === 'none' ||\n style.visibility === 'hidden' ||\n style.opacity === '0'\n ) {\n return false;\n }\n\n return checkVisibility(el.parentElement);\n}\n\nexport function PrefetchCrossZoneLinks(): JSX.Element | null {\n const { isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n );\n const [links, setLinks] = useState<HTMLAnchorElement[]>([]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Intersection observer to add the data-prefetch attribute to cross-zone\n * links that have yet to be prefetched and are visible.\n */\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (\n entry.isIntersecting &&\n !entry.target.hasAttribute(PREFETCH_ATTR) &&\n // lazy perform the visibility check for nodes that are intersecting the viewport\n // and have not been prefetched.\n checkVisibility(entry.target)\n ) {\n entry.target.setAttribute(PREFETCH_ATTR, 'true');\n }\n });\n },\n {\n root: null,\n rootMargin: '0px',\n threshold: 0.1,\n },\n );\n\n links.forEach((link) => observer.observe(link));\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading, links]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Mutation observer to notify when new nodes have entered/exited the document\n * or an href has changed.\n */\n const observer = new MutationObserver((mutations) => {\n const hasChanged = mutations.some((mutation) => {\n return (\n (mutation.type === 'childList' && mutation.addedNodes.length > 0) ||\n (mutation.type === 'attributes' && mutation.attributeName === 'href')\n );\n });\n\n if (hasChanged) {\n // Whenever there's a change, add all cross-zone links that haven't been\n // prefetched.\n setLinks(\n Array.from(\n document.querySelectorAll<HTMLAnchorElement>(\n `a${DATA_ATTR_SELECTORS.anyZone}:not(${DATA_ATTR_SELECTORS.prefetch}):not(${DATA_ATTR_SELECTORS.sameZone}):not(${DATA_ATTR_SELECTORS.external})`,\n ),\n ),\n );\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['href'],\n });\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading]);\n\n // Wait till the zone-config loads to take into consideration any\n // flagged routes.\n if (isLoading) {\n return null;\n }\n\n // Prefetch links with moderate eagerness by default, immediately when marked \"data-prefetch\".\n // Prerender links with conservative eagerness by default, immediately when marked \"data-prefetch\".\n const speculationRules = {\n prefetch: [\n {\n eagerness: 'moderate',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n {\n eagerness: 'immediate',\n where: PREFETCH_WHEN_VISIBLE_PREDICATES,\n },\n ],\n prerender: [\n {\n eagerness: 'conservative',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n ],\n };\n\n return (\n <Script\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(speculationRules),\n }}\n id=\"prefetch-zones-links\"\n type=\"speculationrules\"\n />\n );\n}\n"],"mappings":"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","names":["forwardRef","useContext","NextLink","useState","useEffect","pathToRegexp","MicrofrontendConfigClient","config","opts","app","match","other","path","pathname","name","application","group","childPath","defaultApplication","cachedServerClientConfigPromise","fetchClientConfigFromServer","response","responseJson","MicrofrontendConfigClient","useClientConfig","config","removeFlaggedPathsFromDefault","clientConfig","setClientConfig","useState","isLoading","setIsLoading","useEffect","originalClientConfig","app","group","newConfig","prevConfig","createContext","useCallback","useEffect","useMemo","useState","Fragment","jsx","jsxs","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","children","seenHrefs","setSeenHrefs","isSafariOrFirefox","setIsSafariOrFirefox","prefetchHref","href","value","useEffect","useState","Script","jsx","PREFETCH_ATTR","DATA_ATTR_SELECTORS","PREFETCH_ON_HOVER_PREDICATES","PREFETCH_WHEN_VISIBLE_PREDICATES","checkVisibility","element","el","style","PrefetchCrossZoneLinks","isLoading","useClientConfig","links","setLinks","useState","useEffect","observer","entries","entry","link","mutations","mutation","Script","jsx","CURRENT_ZONE","useZoneForHref","href","clientConfig","isLoading","useClientConfig","isRelativePath","zoneOfHref","Link","forwardRef","children","props","ref","prefetchHref","useContext","PrefetchCrossZoneLinksContext","isDifferentZone","onHoverPrefetch","_","rest","NextLink"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/client/link/multi-zones-link.tsx","../../src/config/react/use-client-config.ts","../../src/config/microfrontends-config/client/index.ts","../../src/next/client/prefetch/prefetch-cross-zone-links-context.tsx","../../src/next/client/prefetch/prefetch-cross-zone-links.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes } from 'react';\nimport { forwardRef, useContext } from 'react';\nimport NextLink, { type LinkProps as NextLinkProps } from 'next/link.js';\nimport { useClientConfig } from '../../../config/react/use-client-config';\nimport { PrefetchCrossZoneLinksContext } from '../prefetch';\n\ninterface BaseProps {\n children: React.ReactNode;\n href: string;\n}\nexport type LinkProps = BaseProps &\n Omit<NextLinkProps, keyof BaseProps> &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps>;\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\nexport function useZoneForHref(href: LinkProps['href'] | undefined): {\n zoneOfHref: string | null;\n isDifferentZone: boolean;\n isLoading: boolean;\n} {\n const { clientConfig, isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n {\n removeFlaggedPathsFromDefault: true,\n },\n );\n const isRelativePath = typeof href === 'string' && href.startsWith('/');\n const zoneOfHref = isRelativePath\n ? clientConfig.getApplicationNameForPath(href)\n : null;\n if (typeof href === 'string' && !href.length) {\n return {\n zoneOfHref: null,\n isDifferentZone: false,\n isLoading: false,\n };\n }\n const isDifferentZone =\n !isRelativePath || (zoneOfHref ? CURRENT_ZONE !== zoneOfHref : false);\n return { zoneOfHref, isDifferentZone, isLoading };\n}\n\n/**\n * A Link component that works with Multi-Zones set-ups and will prefetch the\n * cross zone links automatically.\n */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(\n ({ children, ...props }, ref): JSX.Element => {\n const { prefetchHref } = useContext(PrefetchCrossZoneLinksContext);\n const { zoneOfHref, isDifferentZone, isLoading } = useZoneForHref(\n props.href,\n );\n\n function onHoverPrefetch(): void {\n if (!props.href) {\n return;\n }\n prefetchHref(props.href);\n }\n\n if (isDifferentZone && zoneOfHref !== null) {\n const { prefetch: _, ...rest } = props;\n return (\n <a\n {...rest}\n data-zone={zoneOfHref}\n onFocus={props.prefetch !== false ? onHoverPrefetch : undefined}\n onMouseOver={props.prefetch !== false ? onHoverPrefetch : undefined}\n >\n {children}\n </a>\n );\n }\n\n return (\n <NextLink\n {...props}\n data-zone={!zoneOfHref ? 'null' : 'same'}\n prefetch={props.prefetch ?? (isLoading ? false : undefined)}\n ref={ref}\n >\n {children}\n </NextLink>\n );\n },\n);\nLink.displayName = 'MultiZonesLink';\n","'use client';\n\nimport { useState, useEffect } from 'react';\nimport type { WellKnownClientData } from '../well-known/types';\nimport { MicrofrontendConfigClient } from '../microfrontends-config/client';\n\nlet cachedServerClientConfigPromise: Promise<MicrofrontendConfigClient | null> | null =\n null;\n\nasync function fetchClientConfigFromServer(): Promise<MicrofrontendConfigClient | null> {\n try {\n const response = await fetch(\n '/.well-known/vercel/microfrontends/client-config',\n );\n if (response.status !== 200) {\n return null;\n }\n const responseJson = (await response.json()) as WellKnownClientData;\n return new MicrofrontendConfigClient(responseJson.config);\n } catch (err) {\n return null;\n }\n}\n\n/**\n * Hook to use the client microfrontends configuration. This hook will resolve\n * dynamic paths by fetching the configuration from the server if necessary,\n * allowing the server to specify the values for dynamic paths.\n */\nexport function useClientConfig(\n config: string | undefined,\n {\n removeFlaggedPathsFromDefault,\n }: {\n removeFlaggedPathsFromDefault?: boolean;\n } = {},\n): {\n clientConfig: MicrofrontendConfigClient;\n isLoading: boolean;\n} {\n const [clientConfig, setClientConfig] = useState<MicrofrontendConfigClient>(\n MicrofrontendConfigClient.fromEnv(config, {\n removeFlaggedPaths: removeFlaggedPathsFromDefault,\n }),\n );\n const [isLoading, setIsLoading] = useState(true);\n useEffect(() => {\n if (\n process.env.NODE_ENV === 'test' &&\n process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER !== '1'\n ) {\n setIsLoading(false);\n return;\n }\n // Since we may remove flagged paths from the client config above, we need\n // to use the original client config to determine if the config has any\n // dynamic paths.\n const originalClientConfig = MicrofrontendConfigClient.fromEnv(config);\n // As an optimization, only fetch the config from the server if the\n // microfrontends configuration has any dynamic paths. If it doesn't,\n // then the server won't return any different values.\n const hasDynamicPaths = Object.values(\n originalClientConfig.applications,\n ).some((app) => app.routing?.some((group) => group.flag));\n if (!hasDynamicPaths) {\n setIsLoading(false);\n return;\n }\n if (!cachedServerClientConfigPromise) {\n cachedServerClientConfigPromise = fetchClientConfigFromServer();\n }\n void cachedServerClientConfigPromise\n .then((newConfig) => {\n if (newConfig) {\n setClientConfig((prevConfig) => {\n return prevConfig.isEqual(newConfig) ? prevConfig : newConfig;\n });\n }\n })\n .finally(() => {\n setIsLoading(false);\n });\n }, [config, clientConfig.applications]);\n\n return { clientConfig, isLoading };\n}\n\nexport function resetCachedServerClientConfigPromise(): void {\n cachedServerClientConfigPromise = null;\n}\n","import { pathToRegexp } from 'path-to-regexp';\nimport type { ClientConfig } from './types';\n\ninterface MicrofrontendConfigClientOptions {\n removeFlaggedPaths?: boolean;\n}\n\nexport class MicrofrontendConfigClient {\n applications: ClientConfig['applications'];\n pathCache: Record<string, string> = {};\n private readonly serialized: ClientConfig;\n\n constructor(config: ClientConfig, opts?: MicrofrontendConfigClientOptions) {\n this.serialized = config;\n if (opts?.removeFlaggedPaths) {\n for (const app of Object.values(config.applications)) {\n if (app.routing) {\n app.routing = app.routing.filter((match) => !match.flag);\n }\n }\n }\n this.applications = config.applications;\n }\n\n /**\n * Create a new `MicrofrontendConfigClient` from a JSON string.\n * Config must be passed in to remain framework agnostic\n */\n static fromEnv(\n config: string | undefined,\n opts?: MicrofrontendConfigClientOptions,\n ): MicrofrontendConfigClient {\n if (!config) {\n throw new Error('No microfrontends configuration found');\n }\n return new MicrofrontendConfigClient(\n JSON.parse(config) as ClientConfig,\n opts,\n );\n }\n\n isEqual(other: MicrofrontendConfigClient): boolean {\n return (\n JSON.stringify(this.applications) === JSON.stringify(other.applications)\n );\n }\n\n getApplicationNameForPath(path: string): string | null {\n if (!path.startsWith('/')) {\n throw new Error(`Path must start with a /`);\n }\n\n if (this.pathCache[path]) {\n return this.pathCache[path];\n }\n\n const pathname = new URL(path, 'https://example.com').pathname;\n for (const [name, application] of Object.entries(this.applications)) {\n if (application.routing) {\n for (const group of application.routing) {\n for (const childPath of group.paths) {\n const regexp = pathToRegexp(childPath);\n if (regexp.test(pathname)) {\n this.pathCache[path] = name;\n return name;\n }\n }\n }\n }\n }\n const defaultApplication = Object.entries(this.applications).find(\n ([, application]) => application.default,\n );\n if (!defaultApplication) {\n return null;\n }\n\n this.pathCache[path] = defaultApplication[0];\n return defaultApplication[0];\n }\n\n serialize(): ClientConfig {\n return this.serialized;\n }\n}\n","import React, {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nexport interface PrefetchCrossZoneLinksContext {\n prefetchHref: (href: string) => void;\n}\n\nexport const PrefetchCrossZoneLinksContext =\n createContext<PrefetchCrossZoneLinksContext>({\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n prefetchHref: () => {},\n });\n\nexport function PrefetchCrossZoneLinksProvider({\n children,\n}: {\n children: React.ReactNode;\n}): JSX.Element | null {\n const [seenHrefs, setSeenHrefs] = useState(new Set<string>());\n const [isSafariOrFirefox, setIsSafariOrFirefox] = useState(false);\n\n useEffect(() => {\n setIsSafariOrFirefox(\n typeof navigator !== 'undefined' &&\n (navigator.userAgent.includes('Firefox') ||\n (navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome'))),\n );\n }, []);\n\n const prefetchHref = useCallback(\n (href: string): void => {\n if (!seenHrefs.has(href)) {\n setSeenHrefs(new Set(seenHrefs).add(href));\n }\n },\n [seenHrefs],\n );\n\n const value = useMemo(() => ({ prefetchHref }), [prefetchHref]);\n\n if (!isSafariOrFirefox) {\n return <>{children}</>;\n }\n\n return (\n <PrefetchCrossZoneLinksContext.Provider value={value}>\n {children}\n {[...seenHrefs].map((href) => (\n <link as=\"fetch\" href={href} key={href} rel=\"preload\" />\n ))}\n </PrefetchCrossZoneLinksContext.Provider>\n );\n}\n","import { useEffect, useState } from 'react';\nimport Script from 'next/script.js';\nimport { useClientConfig } from '../../../config/react/use-client-config';\n\nconst PREFETCH_ATTR = 'data-prefetch';\nconst DATA_ATTR_SELECTORS = {\n anyZone: '[data-zone]',\n external: '[data-zone=\"null\"]',\n sameZone: '[data-zone=\"same\"]',\n prefetch: `[${PREFETCH_ATTR}]`,\n} as const;\n\nconst PREFETCH_ON_HOVER_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n ],\n};\n\nconst PREFETCH_WHEN_VISIBLE_PREDICATES = {\n and: [\n { href_matches: '/*' },\n { selector_matches: DATA_ATTR_SELECTORS.anyZone },\n { not: { selector_matches: DATA_ATTR_SELECTORS.sameZone } },\n { not: { selector_matches: DATA_ATTR_SELECTORS.external } },\n { selector_matches: DATA_ATTR_SELECTORS.prefetch },\n ],\n};\n\nfunction checkVisibility(element: Element | null): boolean {\n if (!element) return true;\n\n if ('checkVisibility' in element) {\n return element.checkVisibility({ opacityProperty: true });\n }\n\n // hack to get around TS thinking element is never;\n const el = element as Element;\n const style = window.getComputedStyle(el);\n\n if (\n style.display === 'none' ||\n style.visibility === 'hidden' ||\n style.opacity === '0'\n ) {\n return false;\n }\n\n return checkVisibility(el.parentElement);\n}\n\nexport function PrefetchCrossZoneLinks(): JSX.Element | null {\n const { isLoading } = useClientConfig(\n process.env.NEXT_PUBLIC_MFE_CLIENT_CONFIG,\n );\n const [links, setLinks] = useState<HTMLAnchorElement[]>([]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Intersection observer to add the data-prefetch attribute to cross-zone\n * links that have yet to be prefetched and are visible.\n */\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (\n entry.isIntersecting &&\n !entry.target.hasAttribute(PREFETCH_ATTR) &&\n // lazy perform the visibility check for nodes that are intersecting the viewport\n // and have not been prefetched.\n checkVisibility(entry.target)\n ) {\n entry.target.setAttribute(PREFETCH_ATTR, 'true');\n }\n });\n },\n {\n root: null,\n rootMargin: '0px',\n threshold: 0.1,\n },\n );\n\n links.forEach((link) => observer.observe(link));\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading, links]);\n\n useEffect(() => {\n if (isLoading) {\n return;\n }\n\n /**\n * Mutation observer to notify when new nodes have entered/exited the document\n * or an href has changed.\n */\n const observer = new MutationObserver((mutations) => {\n const hasChanged = mutations.some((mutation) => {\n return (\n (mutation.type === 'childList' && mutation.addedNodes.length > 0) ||\n (mutation.type === 'attributes' && mutation.attributeName === 'href')\n );\n });\n\n if (hasChanged) {\n // Whenever there's a change, add all cross-zone links that haven't been\n // prefetched.\n setLinks(\n Array.from(\n document.querySelectorAll<HTMLAnchorElement>(\n `a${DATA_ATTR_SELECTORS.anyZone}:not(${DATA_ATTR_SELECTORS.prefetch}):not(${DATA_ATTR_SELECTORS.sameZone}):not(${DATA_ATTR_SELECTORS.external})`,\n ),\n ),\n );\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['href'],\n });\n\n return () => {\n observer.disconnect();\n };\n }, [isLoading]);\n\n // Wait till the zone-config loads to take into consideration any\n // flagged routes.\n if (isLoading) {\n return null;\n }\n\n // Prefetch links with moderate eagerness by default, immediately when marked \"data-prefetch\".\n // Prerender links with conservative eagerness by default, immediately when marked \"data-prefetch\".\n const speculationRules = {\n prefetch: [\n {\n eagerness: 'moderate',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n {\n eagerness: 'immediate',\n where: PREFETCH_WHEN_VISIBLE_PREDICATES,\n },\n ],\n prerender: [\n {\n eagerness: 'conservative',\n where: PREFETCH_ON_HOVER_PREDICATES,\n },\n ],\n };\n\n return (\n <Script\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(speculationRules),\n }}\n id=\"prefetch-zones-links\"\n type=\"speculationrules\"\n />\n );\n}\n"],"mappings":";AACA,OAAS,cAAAA,EAAY,cAAAC,MAAkB,QACvC,OAAOC,MAAmD,eCA1D,OAAS,YAAAC,EAAU,aAAAC,MAAiB,QCFpC,OAAS,gBAAAC,MAAoB,iBAOtB,IAAMC,EAAN,KAAgC,CAKrC,YAAYC,EAAsBC,EAAyC,CAH3E,eAAoC,CAAC,EAKnC,GADA,KAAK,WAAaD,EACdC,GAAM,mBACR,QAAWC,KAAO,OAAO,OAAOF,EAAO,YAAY,EAC7CE,EAAI,UACNA,EAAI,QAAUA,EAAI,QAAQ,OAAQC,GAAU,CAACA,EAAM,IAAI,GAI7D,KAAK,aAAeH,EAAO,YAC7B,CAMA,OAAO,QACLA,EACAC,EAC2B,CAC3B,GAAI,CAACD,EACH,MAAM,IAAI,MAAM,uCAAuC,EAEzD,OAAO,IAAID,EACT,KAAK,MAAMC,CAAM,EACjBC,CACF,CACF,CAEA,QAAQG,EAA2C,CACjD,OACE,KAAK,UAAU,KAAK,YAAY,IAAM,KAAK,UAAUA,EAAM,YAAY,CAE3E,CAEA,0BAA0BC,EAA6B,CACrD,GAAI,CAACA,EAAK,WAAW,GAAG,EACtB,MAAM,IAAI,MAAM,0BAA0B,EAG5C,GAAI,KAAK,UAAUA,CAAI,EACrB,OAAO,KAAK,UAAUA,CAAI,EAG5B,IAAMC,EAAW,IAAI,IAAID,EAAM,qBAAqB,EAAE,SACtD,OAAW,CAACE,EAAMC,CAAW,IAAK,OAAO,QAAQ,KAAK,YAAY,EAChE,GAAIA,EAAY,SACd,QAAWC,KAASD,EAAY,QAC9B,QAAWE,KAAaD,EAAM,MAE5B,GADeX,EAAaY,CAAS,EAC1B,KAAKJ,CAAQ,EACtB,YAAK,UAAUD,CAAI,EAAIE,EAChBA,EAMjB,IAAMI,EAAqB,OAAO,QAAQ,KAAK,YAAY,EAAE,KAC3D,CAAC,CAAC,CAAEH,CAAW,IAAMA,EAAY,OACnC,EACA,OAAKG,GAIL,KAAK,UAAUN,CAAI,EAAIM,EAAmB,CAAC,EACpCA,EAAmB,CAAC,GAJlB,IAKX,CAEA,WAA0B,CACxB,OAAO,KAAK,UACd,CACF,ED9EA,IAAIC,EACF,KAEF,eAAeC,GAAyE,CACtF,GAAI,CACF,IAAMC,EAAW,MAAM,MACrB,kDACF,EACA,GAAIA,EAAS,SAAW,IACtB,OAAO,KAET,IAAMC,EAAgB,MAAMD,EAAS,KAAK,EAC1C,OAAO,IAAIE,EAA0BD,EAAa,MAAM,CAC1D,MAAE,CACA,OAAO,IACT,CACF,CAOO,SAASE,EACdC,EACA,CACE,8BAAAC,CACF,EAEI,CAAC,EAIL,CACA,GAAM,CAACC,EAAcC,CAAe,EAAIC,EACtCN,EAA0B,QAAQE,EAAQ,CACxC,mBAAoBC,CACtB,CAAC,CACH,EACM,CAACI,EAAWC,CAAY,EAAIF,EAAS,EAAI,EAC/C,OAAAG,EAAU,IAAM,CACd,GACE,QAAQ,IAAI,WAAa,QACzB,QAAQ,IAAI,sCAAwC,IACpD,CACAD,EAAa,EAAK,EAClB,OAKF,IAAME,EAAuBV,EAA0B,QAAQE,CAAM,EAOrE,GAAI,CAHoB,OAAO,OAC7BQ,EAAqB,YACvB,EAAE,KAAMC,GAAQA,EAAI,SAAS,KAAMC,GAAUA,EAAM,IAAI,CAAC,EAClC,CACpBJ,EAAa,EAAK,EAClB,OAEGZ,IACHA,EAAkCC,EAA4B,GAE3DD,EACF,KAAMiB,GAAc,CACfA,GACFR,EAAiBS,GACRA,EAAW,QAAQD,CAAS,EAAIC,EAAaD,CACrD,CAEL,CAAC,EACA,QAAQ,IAAM,CACbL,EAAa,EAAK,CACpB,CAAC,CACL,EAAG,CAACN,EAAQE,EAAa,YAAY,CAAC,EAE/B,CAAE,aAAAA,EAAc,UAAAG,CAAU,CACnC,CErFA,OACE,iBAAAQ,EACA,eAAAC,EACA,aAAAC,EACA,WAAAC,EACA,YAAAC,MACK,QAyCI,mBAAAC,EAAA,OAAAC,EAIP,QAAAC,MAJO,oBAnCJ,IAAMC,EACXR,EAA6C,CAE3C,aAAc,IAAM,CAAC,CACvB,CAAC,EAEI,SAASS,EAA+B,CAC7C,SAAAC,CACF,EAEuB,CACrB,GAAM,CAACC,EAAWC,CAAY,EAAIR,EAAS,IAAI,GAAa,EACtD,CAACS,EAAmBC,CAAoB,EAAIV,EAAS,EAAK,EAEhEF,EAAU,IAAM,CACdY,EACE,OAAO,UAAc,MAClB,UAAU,UAAU,SAAS,SAAS,GACpC,UAAU,UAAU,SAAS,QAAQ,GACpC,CAAC,UAAU,UAAU,SAAS,QAAQ,EAC9C,CACF,EAAG,CAAC,CAAC,EAEL,IAAMC,EAAed,EAClBe,GAAuB,CACjBL,EAAU,IAAIK,CAAI,GACrBJ,EAAa,IAAI,IAAID,CAAS,EAAE,IAAIK,CAAI,CAAC,CAE7C,EACA,CAACL,CAAS,CACZ,EAEMM,EAAQd,EAAQ,KAAO,CAAE,aAAAY,CAAa,GAAI,CAACA,CAAY,CAAC,EAE9D,OAAKF,EAKHN,EAACC,EAA8B,SAA9B,CAAuC,MAAOS,EAC5C,UAAAP,EACA,CAAC,GAAGC,CAAS,EAAE,IAAKK,GACnBV,EAAC,QAAK,GAAG,QAAQ,KAAMU,EAAiB,IAAI,WAAVA,CAAoB,CACvD,GACH,EATOV,EAAAD,EAAA,CAAG,SAAAK,EAAS,CAWvB,CC1DA,OAAS,aAAAQ,EAAW,YAAAC,MAAgB,QACpC,OAAOC,MAAY,iBAqKf,cAAAC,MAAA,oBAlKJ,IAAMC,EAAgB,gBAChBC,EAAsB,CAC1B,QAAS,cACT,SAAU,qBACV,SAAU,qBACV,SAAU,IAAID,IAChB,EAEME,EAA+B,CACnC,IAAK,CACH,CAAE,aAAc,IAAK,EACrB,CAAE,iBAAkBD,EAAoB,OAAQ,EAChD,CAAE,IAAK,CAAE,iBAAkBA,EAAoB,QAAS,CAAE,EAC1D,CAAE,IAAK,CAAE,iBAAkBA,EAAoB,QAAS,CAAE,CAC5D,CACF,EAEME,EAAmC,CACvC,IAAK,CACH,CAAE,aAAc,IAAK,EACrB,CAAE,iBAAkBF,EAAoB,OAAQ,EAChD,CAAE,IAAK,CAAE,iBAAkBA,EAAoB,QAAS,CAAE,EAC1D,CAAE,IAAK,CAAE,iBAAkBA,EAAoB,QAAS,CAAE,EAC1D,CAAE,iBAAkBA,EAAoB,QAAS,CACnD,CACF,EAEA,SAASG,EAAgBC,EAAkC,CACzD,GAAI,CAACA,EAAS,MAAO,GAErB,GAAI,oBAAqBA,EACvB,OAAOA,EAAQ,gBAAgB,CAAE,gBAAiB,EAAK,CAAC,EAI1D,IAAMC,EAAKD,EACLE,EAAQ,OAAO,iBAAiBD,CAAE,EAExC,OACEC,EAAM,UAAY,QAClBA,EAAM,aAAe,UACrBA,EAAM,UAAY,IAEX,GAGFH,EAAgBE,EAAG,aAAa,CACzC,CAEO,SAASE,IAA6C,CAC3D,GAAM,CAAE,UAAAC,CAAU,EAAIC,EACpB,QAAQ,IAAI,6BACd,EACM,CAACC,EAAOC,CAAQ,EAAIC,EAA8B,CAAC,CAAC,EAmF1D,OAjFAC,EAAU,IAAM,CACd,GAAIL,EACF,OAOF,IAAMM,EAAW,IAAI,qBAClBC,GAAY,CACXA,EAAQ,QAASC,GAAU,CAEvBA,EAAM,gBACN,CAACA,EAAM,OAAO,aAAajB,CAAa,GAGxCI,EAAgBa,EAAM,MAAM,GAE5BA,EAAM,OAAO,aAAajB,EAAe,MAAM,CAEnD,CAAC,CACH,EACA,CACE,KAAM,KACN,WAAY,MACZ,UAAW,EACb,CACF,EAEA,OAAAW,EAAM,QAASO,GAASH,EAAS,QAAQG,CAAI,CAAC,EAEvC,IAAM,CACXH,EAAS,WAAW,CACtB,CACF,EAAG,CAACN,EAAWE,CAAK,CAAC,EAErBG,EAAU,IAAM,CACd,GAAIL,EACF,OAOF,IAAMM,EAAW,IAAI,iBAAkBI,GAAc,CAChCA,EAAU,KAAMC,GAE9BA,EAAS,OAAS,aAAeA,EAAS,WAAW,OAAS,GAC9DA,EAAS,OAAS,cAAgBA,EAAS,gBAAkB,MAEjE,GAKCR,EACE,MAAM,KACJ,SAAS,iBACP,IAAIX,EAAoB,eAAeA,EAAoB,iBAAiBA,EAAoB,iBAAiBA,EAAoB,WACvI,CACF,CACF,CAEJ,CAAC,EAED,OAAAc,EAAS,QAAQ,SAAS,KAAM,CAC9B,UAAW,GACX,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,MAAM,CAC1B,CAAC,EAEM,IAAM,CACXA,EAAS,WAAW,CACtB,CACF,EAAG,CAACN,CAAS,CAAC,EAIVA,EACK,KAyBPV,EAACsB,EAAA,CACC,wBAAyB,CACvB,OAAQ,KAAK,UAtBM,CACvB,SAAU,CACR,CACE,UAAW,WACX,MAAOnB,CACT,EACA,CACE,UAAW,YACX,MAAOC,CACT,CACF,EACA,UAAW,CACT,CACE,UAAW,eACX,MAAOD,CACT,CACF,CACF,CAK6C,CACzC,EACA,GAAG,uBACH,KAAK,mBACP,CAEJ,CJ9GQ,cAAAoB,MAAA,oBAlDR,IAAMC,EAAe,QAAQ,IAAI,oCAE1B,SAASC,EAAeC,EAI7B,CACA,GAAM,CAAE,aAAAC,EAAc,UAAAC,CAAU,EAAIC,EAClC,QAAQ,IAAI,8BACZ,CACE,8BAA+B,EACjC,CACF,EACMC,EAAiB,OAAOJ,GAAS,UAAYA,EAAK,WAAW,GAAG,EAChEK,EAAaD,EACfH,EAAa,0BAA0BD,CAAI,EAC3C,KACJ,OAAI,OAAOA,GAAS,UAAY,CAACA,EAAK,OAC7B,CACL,WAAY,KACZ,gBAAiB,GACjB,UAAW,EACb,EAIK,CAAE,WAAAK,EAAY,gBADnB,CAACD,IAAmBC,EAAaP,IAAiBO,EAAa,IAC3B,UAAAH,CAAU,CAClD,CAMO,IAAMI,EAAOC,EAClB,CAAC,CAAE,SAAAC,EAAU,GAAGC,CAAM,EAAGC,IAAqB,CAC5C,GAAM,CAAE,aAAAC,CAAa,EAAIC,EAAWC,CAA6B,EAC3D,CAAE,WAAAR,EAAY,gBAAAS,EAAiB,UAAAZ,CAAU,EAAIH,EACjDU,EAAM,IACR,EAEA,SAASM,GAAwB,CAC1BN,EAAM,MAGXE,EAAaF,EAAM,IAAI,CACzB,CAEA,GAAIK,GAAmBT,IAAe,KAAM,CAC1C,GAAM,CAAE,SAAUW,EAAG,GAAGC,CAAK,EAAIR,EACjC,OACEZ,EAAC,KACE,GAAGoB,EACJ,YAAWZ,EACX,QAASI,EAAM,WAAa,GAAQM,EAAkB,OACtD,YAAaN,EAAM,WAAa,GAAQM,EAAkB,OAEzD,SAAAP,EACH,EAIJ,OACEX,EAACqB,EAAA,CACE,GAAGT,EACJ,YAAYJ,EAAsB,OAAT,OACzB,SAAUI,EAAM,WAAaP,EAAY,GAAQ,QACjD,IAAKQ,EAEJ,SAAAF,EACH,CAEJ,CACF,EACAF,EAAK,YAAc","names":["forwardRef","useContext","NextLink","useState","useEffect","pathToRegexp","MicrofrontendConfigClient","config","opts","app","match","other","path","pathname","name","application","group","childPath","defaultApplication","cachedServerClientConfigPromise","fetchClientConfigFromServer","response","responseJson","MicrofrontendConfigClient","useClientConfig","config","removeFlaggedPathsFromDefault","clientConfig","setClientConfig","useState","isLoading","setIsLoading","useEffect","originalClientConfig","app","group","newConfig","prevConfig","createContext","useCallback","useEffect","useMemo","useState","Fragment","jsx","jsxs","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","children","seenHrefs","setSeenHrefs","isSafariOrFirefox","setIsSafariOrFirefox","prefetchHref","href","value","useEffect","useState","Script","jsx","PREFETCH_ATTR","DATA_ATTR_SELECTORS","PREFETCH_ON_HOVER_PREDICATES","PREFETCH_WHEN_VISIBLE_PREDICATES","checkVisibility","element","el","style","PrefetchCrossZoneLinks","isLoading","useClientConfig","links","setLinks","useState","useEffect","observer","entries","entry","link","mutations","mutation","Script","jsx","CURRENT_ZONE","useZoneForHref","href","clientConfig","isLoading","useClientConfig","isRelativePath","zoneOfHref","Link","forwardRef","children","props","ref","prefetchHref","useContext","PrefetchCrossZoneLinksContext","isDifferentZone","onHoverPrefetch","_","rest","NextLink"]}
|
package/dist/next/config.cjs
CHANGED
|
@@ -75,7 +75,7 @@ function parseOverrides(cookies) {
|
|
|
75
75
|
// src/config/errors.ts
|
|
76
76
|
var MicrofrontendError = class extends Error {
|
|
77
77
|
constructor(message, opts) {
|
|
78
|
-
super(message);
|
|
78
|
+
super(message, { cause: opts?.cause });
|
|
79
79
|
this.name = "MicrofrontendsError";
|
|
80
80
|
this.source = opts?.source ?? "@vercel/microfrontends";
|
|
81
81
|
this.type = opts?.type ?? "unknown";
|
|
@@ -1052,24 +1052,12 @@ var schema_default = {
|
|
|
1052
1052
|
options: {
|
|
1053
1053
|
$ref: "#/definitions/Options"
|
|
1054
1054
|
},
|
|
1055
|
-
remotes: {
|
|
1056
|
-
type: "object",
|
|
1057
|
-
additionalProperties: {
|
|
1058
|
-
$ref: "#/definitions/Application"
|
|
1059
|
-
},
|
|
1060
|
-
propertyNames: {
|
|
1061
|
-
description: "The unique identifier for a Microfrontend Application. Must match the `name` field of the application's `package.json`."
|
|
1062
|
-
},
|
|
1063
|
-
description: "Applications that only serve a subset of the microfrontend routes only need to reference the name of the primary application that owns the full microfrontends configuration."
|
|
1064
|
-
},
|
|
1065
1055
|
applications: {
|
|
1066
1056
|
$ref: "#/definitions/ApplicationRouting",
|
|
1067
1057
|
description: "Mapping of application names to the routes that they host. Only needs to be defined in the application that owns the primary microfrontend domain"
|
|
1068
1058
|
}
|
|
1069
1059
|
},
|
|
1070
|
-
required: [
|
|
1071
|
-
"applications"
|
|
1072
|
-
],
|
|
1060
|
+
required: ["applications"],
|
|
1073
1061
|
additionalProperties: false
|
|
1074
1062
|
},
|
|
1075
1063
|
Options: {
|
|
@@ -1089,10 +1077,6 @@ var schema_default = {
|
|
|
1089
1077
|
VercelOptions: {
|
|
1090
1078
|
type: "object",
|
|
1091
1079
|
properties: {
|
|
1092
|
-
teamSlug: {
|
|
1093
|
-
type: "string",
|
|
1094
|
-
description: "Team slug for the Vercel team"
|
|
1095
|
-
},
|
|
1096
1080
|
disableOverrides: {
|
|
1097
1081
|
type: "boolean",
|
|
1098
1082
|
description: "If you want to disable the overrides for the site. For example, if you are managing rewrites between applications externally, you may wish to disable the overrides on the toolbar as they will have no effect."
|
|
@@ -1110,6 +1094,15 @@ var schema_default = {
|
|
|
1110
1094
|
},
|
|
1111
1095
|
additionalProperties: false
|
|
1112
1096
|
},
|
|
1097
|
+
ApplicationRouting: {
|
|
1098
|
+
type: "object",
|
|
1099
|
+
additionalProperties: {
|
|
1100
|
+
$ref: "#/definitions/Application"
|
|
1101
|
+
},
|
|
1102
|
+
propertyNames: {
|
|
1103
|
+
description: "The unique identifier for a Microfrontend Application. Must match the `name` field of the application's `package.json`."
|
|
1104
|
+
}
|
|
1105
|
+
},
|
|
1113
1106
|
Application: {
|
|
1114
1107
|
anyOf: [
|
|
1115
1108
|
{
|
|
@@ -1133,9 +1126,7 @@ var schema_default = {
|
|
|
1133
1126
|
$ref: "#/definitions/HostConfig"
|
|
1134
1127
|
}
|
|
1135
1128
|
},
|
|
1136
|
-
required: [
|
|
1137
|
-
"production"
|
|
1138
|
-
],
|
|
1129
|
+
required: ["production"],
|
|
1139
1130
|
additionalProperties: false
|
|
1140
1131
|
},
|
|
1141
1132
|
Vercel: {
|
|
@@ -1146,9 +1137,7 @@ var schema_default = {
|
|
|
1146
1137
|
description: "Vercel project ID"
|
|
1147
1138
|
}
|
|
1148
1139
|
},
|
|
1149
|
-
required: [
|
|
1150
|
-
"projectId"
|
|
1151
|
-
],
|
|
1140
|
+
required: ["projectId"],
|
|
1152
1141
|
additionalProperties: false
|
|
1153
1142
|
},
|
|
1154
1143
|
Development: {
|
|
@@ -1178,10 +1167,7 @@ var schema_default = {
|
|
|
1178
1167
|
},
|
|
1179
1168
|
protocol: {
|
|
1180
1169
|
type: "string",
|
|
1181
|
-
enum: [
|
|
1182
|
-
"http",
|
|
1183
|
-
"https"
|
|
1184
|
-
],
|
|
1170
|
+
enum: ["http", "https"],
|
|
1185
1171
|
description: "The protocol to be used for the connection.\n- `http`: Hypertext Transfer Protocol (HTTP).\n- `https`: Secure Hypertext Transfer Protocol (HTTPS).\n\n*"
|
|
1186
1172
|
},
|
|
1187
1173
|
port: {
|
|
@@ -1195,10 +1181,7 @@ var schema_default = {
|
|
|
1195
1181
|
properties: {
|
|
1196
1182
|
protocol: {
|
|
1197
1183
|
type: "string",
|
|
1198
|
-
enum: [
|
|
1199
|
-
"http",
|
|
1200
|
-
"https"
|
|
1201
|
-
],
|
|
1184
|
+
enum: ["http", "https"],
|
|
1202
1185
|
description: "The protocol to be used for the connection.\n- `http`: Hypertext Transfer Protocol (HTTP).\n- `https`: Secure Hypertext Transfer Protocol (HTTPS).\n\n*"
|
|
1203
1186
|
},
|
|
1204
1187
|
host: {
|
|
@@ -1210,9 +1193,7 @@ var schema_default = {
|
|
|
1210
1193
|
description: "The port number to be used for the connection. Common values include `80` for HTTP and `443` for HTTPS."
|
|
1211
1194
|
}
|
|
1212
1195
|
},
|
|
1213
|
-
required: [
|
|
1214
|
-
"host"
|
|
1215
|
-
],
|
|
1196
|
+
required: ["host"],
|
|
1216
1197
|
additionalProperties: false
|
|
1217
1198
|
},
|
|
1218
1199
|
ChildApplication: {
|
|
@@ -1232,9 +1213,7 @@ var schema_default = {
|
|
|
1232
1213
|
$ref: "#/definitions/HostConfig"
|
|
1233
1214
|
}
|
|
1234
1215
|
},
|
|
1235
|
-
required: [
|
|
1236
|
-
"routing"
|
|
1237
|
-
],
|
|
1216
|
+
required: ["routing"],
|
|
1238
1217
|
additionalProperties: false
|
|
1239
1218
|
},
|
|
1240
1219
|
Routing: {
|
|
@@ -1261,20 +1240,9 @@ var schema_default = {
|
|
|
1261
1240
|
}
|
|
1262
1241
|
}
|
|
1263
1242
|
},
|
|
1264
|
-
required: [
|
|
1265
|
-
"paths"
|
|
1266
|
-
],
|
|
1243
|
+
required: ["paths"],
|
|
1267
1244
|
additionalProperties: false
|
|
1268
1245
|
},
|
|
1269
|
-
ApplicationRouting: {
|
|
1270
|
-
type: "object",
|
|
1271
|
-
additionalProperties: {
|
|
1272
|
-
$ref: "#/definitions/Application"
|
|
1273
|
-
},
|
|
1274
|
-
propertyNames: {
|
|
1275
|
-
description: "The unique identifier for a Microfrontend Application. Must match the `name` field of the application's `package.json`."
|
|
1276
|
-
}
|
|
1277
|
-
},
|
|
1278
1246
|
ChildConfig: {
|
|
1279
1247
|
type: "object",
|
|
1280
1248
|
properties: {
|
|
@@ -1288,24 +1256,12 @@ var schema_default = {
|
|
|
1288
1256
|
options: {
|
|
1289
1257
|
$ref: "#/definitions/Options"
|
|
1290
1258
|
},
|
|
1291
|
-
remotes: {
|
|
1292
|
-
type: "object",
|
|
1293
|
-
additionalProperties: {
|
|
1294
|
-
$ref: "#/definitions/Application"
|
|
1295
|
-
},
|
|
1296
|
-
propertyNames: {
|
|
1297
|
-
description: "The unique identifier for a Microfrontend Application. Must match the `name` field of the application's `package.json`."
|
|
1298
|
-
},
|
|
1299
|
-
description: "Applications that only serve a subset of the microfrontend routes only need to reference the name of the primary application that owns the full microfrontends configuration."
|
|
1300
|
-
},
|
|
1301
1259
|
partOf: {
|
|
1302
1260
|
type: "string",
|
|
1303
1261
|
description: "Applications that only serve a subset of the microfrontend routes only need to reference the name of the primary application that owns the full microfrontends configuration."
|
|
1304
1262
|
}
|
|
1305
1263
|
},
|
|
1306
|
-
required: [
|
|
1307
|
-
"partOf"
|
|
1308
|
-
],
|
|
1264
|
+
required: ["partOf"],
|
|
1309
1265
|
additionalProperties: false
|
|
1310
1266
|
}
|
|
1311
1267
|
}
|
|
@@ -1315,6 +1271,14 @@ var schema_default = {
|
|
|
1315
1271
|
var SCHEMA = schema_default;
|
|
1316
1272
|
|
|
1317
1273
|
// src/config/microfrontends/server/validation.ts
|
|
1274
|
+
function filterAjvErrors(errors) {
|
|
1275
|
+
if (!errors) {
|
|
1276
|
+
return [];
|
|
1277
|
+
}
|
|
1278
|
+
return errors.filter((error) => {
|
|
1279
|
+
return error.keyword === "additionalProperties" || error.keyword === "required";
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1318
1282
|
function validateSchema(configString) {
|
|
1319
1283
|
const parsedConfig = (0, import_jsonc_parser3.parse)(configString);
|
|
1320
1284
|
const ajv = new import_ajv.Ajv();
|
|
@@ -1322,7 +1286,10 @@ function validateSchema(configString) {
|
|
|
1322
1286
|
const isValid = validate(parsedConfig);
|
|
1323
1287
|
if (!isValid) {
|
|
1324
1288
|
throw new MicrofrontendError(
|
|
1325
|
-
`Invalid config:
|
|
1289
|
+
`Invalid microfrontends config:
|
|
1290
|
+
- ${ajv.errorsText(filterAjvErrors(validate.errors), { separator: "\n - " })}
|
|
1291
|
+
|
|
1292
|
+
See https://openapi.vercel.sh/microfrontends.json for the schema.`,
|
|
1326
1293
|
{ type: "config", subtype: "does_not_match_schema" }
|
|
1327
1294
|
);
|
|
1328
1295
|
}
|
|
@@ -1462,8 +1429,8 @@ var MicrofrontendsServer = class extends Microfrontends {
|
|
|
1462
1429
|
throw new Error("Unable to infer");
|
|
1463
1430
|
} catch (e) {
|
|
1464
1431
|
throw new MicrofrontendError(
|
|
1465
|
-
"Unable to
|
|
1466
|
-
{ type: "config", subtype: "inference_failed" }
|
|
1432
|
+
"Unable to locate and parse microfrontends configuration",
|
|
1433
|
+
{ cause: e, type: "config", subtype: "inference_failed" }
|
|
1467
1434
|
);
|
|
1468
1435
|
}
|
|
1469
1436
|
}
|