@vercel/microfrontends 2.0.0 → 2.1.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/CHANGELOG.md +17 -0
- package/README.md +1 -5
- package/dist/bin/cli.cjs +226 -79
- package/dist/config.cjs +5 -4
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +5 -4
- package/dist/config.js.map +1 -1
- package/dist/experimental/sveltekit.cjs +51 -20
- package/dist/experimental/sveltekit.cjs.map +1 -1
- package/dist/experimental/sveltekit.js +51 -20
- package/dist/experimental/sveltekit.js.map +1 -1
- package/dist/experimental/vite.cjs +51 -20
- package/dist/experimental/vite.cjs.map +1 -1
- package/dist/experimental/vite.js +51 -20
- package/dist/experimental/vite.js.map +1 -1
- package/dist/microfrontends/server.cjs +51 -20
- package/dist/microfrontends/server.cjs.map +1 -1
- package/dist/microfrontends/server.js +51 -20
- package/dist/microfrontends/server.js.map +1 -1
- package/dist/microfrontends/utils.cjs +32 -7
- package/dist/microfrontends/utils.cjs.map +1 -1
- package/dist/microfrontends/utils.d.ts +8 -2
- package/dist/microfrontends/utils.js +31 -7
- package/dist/microfrontends/utils.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 +15 -1
- package/dist/next/client.js +1 -1
- package/dist/next/client.js.map +1 -1
- package/dist/next/config.cjs +62 -20
- package/dist/next/config.cjs.map +1 -1
- package/dist/next/config.js +62 -20
- package/dist/next/config.js.map +1 -1
- package/dist/next/middleware.cjs +5 -4
- package/dist/next/middleware.cjs.map +1 -1
- package/dist/next/middleware.js +5 -4
- package/dist/next/middleware.js.map +1 -1
- package/dist/next/testing.cjs +5 -4
- package/dist/next/testing.cjs.map +1 -1
- package/dist/next/testing.js +5 -4
- package/dist/next/testing.js.map +1 -1
- package/dist/utils/mfe-port.cjs +66 -40
- package/dist/utils/mfe-port.cjs.map +1 -1
- package/dist/utils/mfe-port.js +66 -40
- package/dist/utils/mfe-port.js.map +1 -1
- package/package.json +4 -4
package/dist/next/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/client/link/microfrontends-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, useMemo } from 'react';\nimport NextLink, {\n type LinkProps as ExternalNextLinkProps,\n} 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}\n\n// fix for tsc inlining LinkProps from next\n// https://github.com/microsoft/TypeScript/issues/37151#issuecomment-756232934\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\ninterface NextLinkProps extends ExternalNextLinkProps {}\nexport type LinkProps = BaseProps &\n Omit<NextLinkProps, keyof BaseProps> &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps>;\n\nconst CURRENT_ZONE_HASH = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION_HASH;\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 const { isRelativePath, zoneOfHref } = useMemo(() => {\n const isRelative = typeof href === 'string' && href.startsWith('/');\n return {\n isRelativePath: isRelative,\n zoneOfHref: isRelative\n ? clientConfig.getApplicationNameForPath(href)\n : null,\n };\n }, [clientConfig, href]);\n\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_HASH !== zoneOfHref : false);\n return { zoneOfHref, isDifferentZone, isLoading };\n}\n\n/**\n * A Link component that works with microfrontend 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 = 'MicrofrontendsLink';\n","'use client';\n\nimport { useState, useEffect, useMemo } from 'react';\nimport type { WellKnownClientData } from '../well-known/types';\nimport { MicrofrontendConfigClient } from '../microfrontends-config/client';\n\nconst clientCache = new Map<string, MicrofrontendConfigClient>();\nconst cachedHasDynamicPaths = new Map<string, boolean>();\n\nconst getClient = (config: string | undefined) => {\n const existing = clientCache.get(config || '');\n if (existing) {\n return existing;\n }\n\n const client = MicrofrontendConfigClient.fromEnv(config);\n clientCache.set(config || '', client);\n return client;\n};\n\nlet cachedServerClientConfigPromise: Promise<MicrofrontendConfigClient | null> | null =\n null;\n\nlet cachedServerClient: MicrofrontendConfigClient | null = 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 const client = new MicrofrontendConfigClient(responseJson.config);\n cachedServerClient = client;\n return client;\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(config: string | undefined): {\n clientConfig: MicrofrontendConfigClient;\n isLoading: boolean;\n} {\n const [clientConfig, setClientConfig] = useState<MicrofrontendConfigClient>(\n () => cachedServerClient ?? getClient(config),\n );\n const canLoad = useMemo(() => {\n if (\n process.env.NODE_ENV === 'test' &&\n process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER !== '1'\n ) {\n return false;\n }\n // If we've already fetched the server config and it's resolved, we don't need\n // to enter the loading state at all\n if (cachedServerClient) return false;\n // If we've already checked this config for dynamic paths, we can use the\n // cached result from before instead of reevaluating.\n const existing = cachedHasDynamicPaths.get(config || '');\n if (existing !== undefined) return existing;\n // Get the original client config to determine if the config has any\n // dynamic paths.\n const originalClientConfig = getClient(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 = originalClientConfig.hasFlaggedPaths;\n cachedHasDynamicPaths.set(config || '', hasDynamicPaths);\n if (!hasDynamicPaths) {\n return false;\n }\n return true;\n }, [config]);\n const [isLoading, setIsLoading] = useState(canLoad);\n useEffect(() => {\n if (!canLoad) return;\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, canLoad]);\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\nexport interface MicrofrontendConfigClientOptions {\n removeFlaggedPaths?: boolean;\n}\n\nconst regexpCache = new Map<string, RegExp>();\nconst getRegexp = (path: string): RegExp => {\n const existing = regexpCache.get(path);\n if (existing) {\n return existing;\n }\n\n const regexp = pathToRegexp(path);\n regexpCache.set(path, regexp);\n return regexp;\n};\n\nexport class MicrofrontendConfigClient {\n applications: ClientConfig['applications'];\n hasFlaggedPaths: boolean;\n pathCache: Record<string, string> = {};\n private readonly serialized: ClientConfig;\n\n constructor(config: ClientConfig, opts?: MicrofrontendConfigClientOptions) {\n this.hasFlaggedPaths = config.hasFlaggedPaths ?? false;\n for (const app of Object.values(config.applications)) {\n if (app.routing) {\n if (app.routing.some((match) => match.flag)) {\n this.hasFlaggedPaths = true;\n }\n const newRouting = [];\n const pathsWithoutFlags = [];\n for (const group of app.routing) {\n if (group.flag) {\n if (opts?.removeFlaggedPaths) {\n continue;\n }\n if (group.group) {\n delete group.group;\n }\n newRouting.push(group);\n } else {\n pathsWithoutFlags.push(...group.paths);\n }\n }\n if (pathsWithoutFlags.length > 0) {\n newRouting.push({ paths: pathsWithoutFlags });\n }\n app.routing = newRouting;\n }\n }\n this.serialized = config;\n if (this.hasFlaggedPaths) {\n this.serialized.hasFlaggedPaths = this.hasFlaggedPaths;\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(config: string | undefined): MicrofrontendConfigClient {\n if (!config) {\n throw new Error(\n 'Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?',\n );\n }\n return new MicrofrontendConfigClient(JSON.parse(config) as ClientConfig);\n }\n\n isEqual(other: MicrofrontendConfigClient): boolean {\n return (\n this === other ||\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 = getRegexp(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 useRef,\n useMemo,\n useState,\n startTransition,\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 = useRef(\n typeof navigator !== 'undefined' &&\n (navigator.userAgent.includes('Firefox') ||\n (navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome'))),\n );\n\n // This useCallback must not have any dependencies because if it changes\n // its value, every component that uses this context will rerender.\n const prefetchHref = useCallback((href: string): void => {\n // It's not critical that we render the new preload `<link>` elements\n // immediately. We want to batch together `prefetchHref` calls that\n // occur in one synchronous pass and only render once after they've all\n // called this callback.\n startTransition(() => {\n setSeenHrefs((prevHrefs) => {\n if (prevHrefs.has(href)) return prevHrefs;\n return new Set(prevHrefs).add(href);\n });\n });\n }, []);\n\n const value = useMemo(() => ({ prefetchHref }), [prefetchHref]);\n\n if (!isSafariOrFirefox.current) {\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":["forwardRef","useContext","useMemo","NextLink","useState","useEffect","useMemo","pathToRegexp","regexpCache","getRegexp","path","existing","regexp","MicrofrontendConfigClient","config","opts","app","match","newRouting","pathsWithoutFlags","group","other","pathname","name","application","childPath","defaultApplication","clientCache","cachedHasDynamicPaths","getClient","config","existing","client","MicrofrontendConfigClient","cachedServerClientConfigPromise","cachedServerClient","fetchClientConfigFromServer","response","responseJson","useClientConfig","clientConfig","setClientConfig","useState","canLoad","useMemo","hasDynamicPaths","isLoading","setIsLoading","useEffect","newConfig","prevConfig","createContext","useCallback","useRef","useMemo","useState","startTransition","Fragment","jsx","jsxs","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","children","seenHrefs","setSeenHrefs","isSafariOrFirefox","prefetchHref","href","prevHrefs","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_HASH","useZoneForHref","href","clientConfig","isLoading","useClientConfig","isRelativePath","zoneOfHref","useMemo","isRelative","Link","forwardRef","children","props","ref","prefetchHref","useContext","PrefetchCrossZoneLinksContext","isDifferentZone","onHoverPrefetch","_","rest","NextLink"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/client/link/microfrontends-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, useMemo } from 'react';\nimport NextLink, {\n type LinkProps as ExternalNextLinkProps,\n} 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}\n\n// fix for tsc inlining LinkProps from next\n// https://github.com/microsoft/TypeScript/issues/37151#issuecomment-756232934\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\ninterface NextLinkProps extends ExternalNextLinkProps {}\nexport type LinkProps = BaseProps &\n Omit<NextLinkProps, keyof BaseProps> &\n Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof BaseProps>;\n\nconst CURRENT_ZONE_HASH = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION_HASH;\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 const { isRelativePath, zoneOfHref } = useMemo(() => {\n const isRelative = typeof href === 'string' && href.startsWith('/');\n return {\n isRelativePath: isRelative,\n zoneOfHref: isRelative\n ? clientConfig.getApplicationNameForPath(href)\n : null,\n };\n }, [clientConfig, href]);\n\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_HASH !== zoneOfHref : false);\n return { zoneOfHref, isDifferentZone, isLoading };\n}\n\n/**\n * A Link component that works with microfrontend 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 = 'MicrofrontendsLink';\n","'use client';\n\nimport { useState, useEffect, useMemo } from 'react';\nimport type { WellKnownClientData } from '../well-known/types';\nimport { MicrofrontendConfigClient } from '../microfrontends-config/client';\n\nconst clientCache = new Map<string, MicrofrontendConfigClient>();\nconst cachedHasDynamicPaths = new Map<string, boolean>();\n\nconst getClient = (config: string | undefined) => {\n const existing = clientCache.get(config || '');\n if (existing) {\n return existing;\n }\n\n const client = MicrofrontendConfigClient.fromEnv(config);\n clientCache.set(config || '', client);\n return client;\n};\n\nlet cachedServerClientConfigPromise: Promise<MicrofrontendConfigClient | null> | null =\n null;\n\nlet cachedServerClient: MicrofrontendConfigClient | null = 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 const client = new MicrofrontendConfigClient(responseJson.config);\n cachedServerClient = client;\n return client;\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(config: string | undefined): {\n clientConfig: MicrofrontendConfigClient;\n isLoading: boolean;\n} {\n const [clientConfig, setClientConfig] = useState<MicrofrontendConfigClient>(\n () => cachedServerClient ?? getClient(config),\n );\n const canLoad = useMemo(() => {\n if (\n process.env.NODE_ENV === 'test' &&\n process.env.MFE_FORCE_CLIENT_CONFIG_FROM_SERVER !== '1'\n ) {\n return false;\n }\n // If we've already fetched the server config and it's resolved, we don't need\n // to enter the loading state at all\n if (cachedServerClient) return false;\n // If we've already checked this config for dynamic paths, we can use the\n // cached result from before instead of reevaluating.\n const existing = cachedHasDynamicPaths.get(config || '');\n if (existing !== undefined) return existing;\n // Get the original client config to determine if the config has any\n // dynamic paths.\n const originalClientConfig = getClient(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 = originalClientConfig.hasFlaggedPaths;\n cachedHasDynamicPaths.set(config || '', hasDynamicPaths);\n if (!hasDynamicPaths) {\n return false;\n }\n return true;\n }, [config]);\n const [isLoading, setIsLoading] = useState(canLoad);\n useEffect(() => {\n if (!canLoad) return;\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, canLoad]);\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\nexport interface MicrofrontendConfigClientOptions {\n removeFlaggedPaths?: boolean;\n}\n\nconst regexpCache = new Map<string, RegExp>();\nconst getRegexp = (path: string): RegExp => {\n const existing = regexpCache.get(path);\n if (existing) {\n return existing;\n }\n\n const regexp = pathToRegexp(path);\n regexpCache.set(path, regexp);\n return regexp;\n};\n\nexport class MicrofrontendConfigClient {\n applications: ClientConfig['applications'];\n hasFlaggedPaths: boolean;\n pathCache: Record<string, string> = {};\n private readonly serialized: ClientConfig;\n\n constructor(config: ClientConfig, opts?: MicrofrontendConfigClientOptions) {\n this.hasFlaggedPaths = config.hasFlaggedPaths ?? false;\n for (const app of Object.values(config.applications)) {\n if (app.routing) {\n if (app.routing.some((match) => match.flag)) {\n this.hasFlaggedPaths = true;\n }\n const newRouting = [];\n const pathsWithoutFlags = [];\n for (const group of app.routing) {\n if (group.flag) {\n if (opts?.removeFlaggedPaths) {\n continue;\n }\n if (group.group) {\n delete group.group;\n }\n newRouting.push(group);\n } else {\n pathsWithoutFlags.push(...group.paths);\n }\n }\n if (pathsWithoutFlags.length > 0) {\n newRouting.push({ paths: pathsWithoutFlags });\n }\n app.routing = newRouting;\n }\n }\n this.serialized = config;\n if (this.hasFlaggedPaths) {\n this.serialized.hasFlaggedPaths = this.hasFlaggedPaths;\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(config: string | undefined): MicrofrontendConfigClient {\n if (!config) {\n throw new Error(\n 'Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`? Is the local proxy running and this application is being accessed via the proxy port? See https://vercel.com/docs/microfrontends/local-development#setting-up-microfrontends-proxy',\n );\n }\n return new MicrofrontendConfigClient(JSON.parse(config) as ClientConfig);\n }\n\n isEqual(other: MicrofrontendConfigClient): boolean {\n return (\n this === other ||\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 = getRegexp(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 useRef,\n useMemo,\n useState,\n startTransition,\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 = useRef(\n typeof navigator !== 'undefined' &&\n (navigator.userAgent.includes('Firefox') ||\n (navigator.userAgent.includes('Safari') &&\n !navigator.userAgent.includes('Chrome'))),\n );\n\n // This useCallback must not have any dependencies because if it changes\n // its value, every component that uses this context will rerender.\n const prefetchHref = useCallback((href: string): void => {\n // It's not critical that we render the new preload `<link>` elements\n // immediately. We want to batch together `prefetchHref` calls that\n // occur in one synchronous pass and only render once after they've all\n // called this callback.\n startTransition(() => {\n setSeenHrefs((prevHrefs) => {\n if (prevHrefs.has(href)) return prevHrefs;\n return new Set(prevHrefs).add(href);\n });\n });\n }, []);\n\n const value = useMemo(() => ({ prefetchHref }), [prefetchHref]);\n\n if (!isSafariOrFirefox.current) {\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\ninterface PrefetchCrossZoneLinksProps {\n /**\n * This attributes controls how eager the browser should be in prerendering\n * cross-zone links. Prerendering downloads the HTML and subresources of the page\n * and starts to render the page in the background. This consumes more resources\n * but provides a faster user experience if the user decides to visit that page.\n *\n * See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/speculationrules#eagerness\n * for more information.\n *\n * Default value is 'conservative'.\n */\n prerenderEagerness?: 'immediate' | 'eager' | 'moderate' | 'conservative';\n}\n\nexport function PrefetchCrossZoneLinks({\n prerenderEagerness = 'conservative',\n}: PrefetchCrossZoneLinksProps): 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: prerenderEagerness,\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","useMemo","NextLink","useState","useEffect","useMemo","pathToRegexp","regexpCache","getRegexp","path","existing","regexp","MicrofrontendConfigClient","config","opts","app","match","newRouting","pathsWithoutFlags","group","other","pathname","name","application","childPath","defaultApplication","clientCache","cachedHasDynamicPaths","getClient","config","existing","client","MicrofrontendConfigClient","cachedServerClientConfigPromise","cachedServerClient","fetchClientConfigFromServer","response","responseJson","useClientConfig","clientConfig","setClientConfig","useState","canLoad","useMemo","hasDynamicPaths","isLoading","setIsLoading","useEffect","newConfig","prevConfig","createContext","useCallback","useRef","useMemo","useState","startTransition","Fragment","jsx","jsxs","PrefetchCrossZoneLinksContext","PrefetchCrossZoneLinksProvider","children","seenHrefs","setSeenHrefs","isSafariOrFirefox","prefetchHref","href","prevHrefs","value","useEffect","useState","Script","jsx","PREFETCH_ATTR","DATA_ATTR_SELECTORS","PREFETCH_ON_HOVER_PREDICATES","PREFETCH_WHEN_VISIBLE_PREDICATES","checkVisibility","element","el","style","PrefetchCrossZoneLinks","prerenderEagerness","isLoading","useClientConfig","links","setLinks","useState","useEffect","observer","entries","entry","link","mutations","mutation","Script","jsx","CURRENT_ZONE_HASH","useZoneForHref","href","clientConfig","isLoading","useClientConfig","isRelativePath","zoneOfHref","useMemo","isRelative","Link","forwardRef","children","props","ref","prefetchHref","useContext","PrefetchCrossZoneLinksContext","isDifferentZone","onHoverPrefetch","_","rest","NextLink"]}
|
package/dist/next/config.cjs
CHANGED
|
@@ -232,22 +232,38 @@ var import_node_fs2 = require("fs");
|
|
|
232
232
|
var import_jsonc_parser = require("jsonc-parser");
|
|
233
233
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
234
234
|
|
|
235
|
-
// src/config/
|
|
236
|
-
var
|
|
235
|
+
// src/config/microfrontends/utils/get-config-file-name.ts
|
|
236
|
+
var DEFAULT_CONFIGURATION_FILENAMES = [
|
|
237
237
|
"microfrontends.jsonc",
|
|
238
238
|
"microfrontends.json"
|
|
239
239
|
];
|
|
240
|
+
function getPossibleConfigurationFilenames({
|
|
241
|
+
customConfigFilename
|
|
242
|
+
}) {
|
|
243
|
+
if (customConfigFilename) {
|
|
244
|
+
if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`The VC_MICROFRONTENDS_CONFIG_FILE_NAME environment variable must end with '.json' or '.jsonc'. Received: ${customConfigFilename}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return Array.from(
|
|
250
|
+
/* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
return DEFAULT_CONFIGURATION_FILENAMES;
|
|
254
|
+
}
|
|
240
255
|
|
|
241
256
|
// src/config/microfrontends/utils/infer-microfrontends-location.ts
|
|
242
257
|
var configCache = {};
|
|
243
258
|
function findPackageWithMicrofrontendsConfig({
|
|
244
259
|
repositoryRoot,
|
|
245
|
-
applicationContext
|
|
260
|
+
applicationContext,
|
|
261
|
+
customConfigFilename
|
|
246
262
|
}) {
|
|
247
263
|
const applicationName = applicationContext.name;
|
|
248
264
|
try {
|
|
249
265
|
const microfrontendsJsonPaths = import_fast_glob.default.globSync(
|
|
250
|
-
`**/{${
|
|
266
|
+
`**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
|
|
251
267
|
{
|
|
252
268
|
cwd: repositoryRoot,
|
|
253
269
|
absolute: true,
|
|
@@ -303,6 +319,8 @@ Names of applications in \`microfrontends.json\` must match the Vercel Project n
|
|
|
303
319
|
|
|
304
320
|
If your Vercel Microfrontends configuration is not in this repository, you can use the Vercel CLI to pull the Vercel Microfrontends configuration using the "vercel microfrontends pull" command, or you can specify the path manually using the VC_MICROFRONTENDS_CONFIG environment variable.
|
|
305
321
|
|
|
322
|
+
If your Vercel Microfrontends configuration has a custom name, ensure the VC_MICROFRONTENDS_CONFIG_FILE_NAME environment variable is set, you can pull the vercel project environment variables using the "vercel env pull" command.
|
|
323
|
+
|
|
306
324
|
If you suspect this is thrown in error, please reach out to the Vercel team.`,
|
|
307
325
|
{ type: "config", subtype: "inference_failed" }
|
|
308
326
|
);
|
|
@@ -317,7 +335,7 @@ If you suspect this is thrown in error, please reach out to the Vercel team.`,
|
|
|
317
335
|
}
|
|
318
336
|
}
|
|
319
337
|
function inferMicrofrontendsLocation(opts) {
|
|
320
|
-
const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
|
|
338
|
+
const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
|
|
321
339
|
if (configCache[cacheKey]) {
|
|
322
340
|
return configCache[cacheKey];
|
|
323
341
|
}
|
|
@@ -383,8 +401,13 @@ function findPackageRoot(startDir) {
|
|
|
383
401
|
// src/config/microfrontends/utils/find-config.ts
|
|
384
402
|
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
385
403
|
var import_node_path5 = require("path");
|
|
386
|
-
function findConfig({
|
|
387
|
-
|
|
404
|
+
function findConfig({
|
|
405
|
+
dir,
|
|
406
|
+
customConfigFilename
|
|
407
|
+
}) {
|
|
408
|
+
for (const filename of getPossibleConfigurationFilenames({
|
|
409
|
+
customConfigFilename
|
|
410
|
+
})) {
|
|
388
411
|
const maybeConfig = (0, import_node_path5.join)(dir, filename);
|
|
389
412
|
if (import_node_fs5.default.existsSync(maybeConfig)) {
|
|
390
413
|
return maybeConfig;
|
|
@@ -451,7 +474,7 @@ var MicrofrontendConfigClient = class {
|
|
|
451
474
|
static fromEnv(config) {
|
|
452
475
|
if (!config) {
|
|
453
476
|
throw new Error(
|
|
454
|
-
"Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
|
|
477
|
+
"Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`? Is the local proxy running and this application is being accessed via the proxy port? See https://vercel.com/docs/microfrontends/local-development#setting-up-microfrontends-proxy"
|
|
455
478
|
);
|
|
456
479
|
}
|
|
457
480
|
return new MicrofrontendConfigClient(JSON.parse(config));
|
|
@@ -591,10 +614,11 @@ function validatePathExpression(path6) {
|
|
|
591
614
|
}
|
|
592
615
|
if (token.pattern !== PATH_DEFAULT_PATTERN && // Allows (a|b|c) and ((?!a|b|c).*) regex
|
|
593
616
|
// Only limited regex is supported for now, due to performance considerations
|
|
594
|
-
|
|
595
|
-
|
|
617
|
+
// Allows all letters, numbers, and hyphens. Other characters must be escaped.
|
|
618
|
+
!/^(?<allowed>[\w-~]+(?:\|[^:|()]+)+)$|^\(\?!(?<disallowed>[\w-~]+(?:\|[^:|()]+)*)\)\.\*$/.test(
|
|
619
|
+
token.pattern.replace(/\\./g, "")
|
|
596
620
|
)) {
|
|
597
|
-
return `Path ${path6} cannot use unsupported regular expression wildcard`;
|
|
621
|
+
return `Path ${path6} cannot use unsupported regular expression wildcard. If the path includes special characters, they must be escaped with backslash (e.g. '\\(')`;
|
|
598
622
|
}
|
|
599
623
|
if (token.modifier && i !== tokens.length - 1) {
|
|
600
624
|
return `Modifier ${token.modifier} is not allowed on wildcard :${token.name} in ${path6}. Modifiers are only allowed in the last path component`;
|
|
@@ -1486,7 +1510,11 @@ var MicrofrontendsServer = class {
|
|
|
1486
1510
|
appName,
|
|
1487
1511
|
packageRoot
|
|
1488
1512
|
});
|
|
1489
|
-
const
|
|
1513
|
+
const customConfigFilename = process.env.VC_MICROFRONTENDS_CONFIG_FILE_NAME;
|
|
1514
|
+
const maybeConfig = findConfig({
|
|
1515
|
+
dir: packageRoot,
|
|
1516
|
+
customConfigFilename
|
|
1517
|
+
});
|
|
1490
1518
|
if (maybeConfig) {
|
|
1491
1519
|
return MicrofrontendsServer.fromFile({
|
|
1492
1520
|
filePath: maybeConfig,
|
|
@@ -1495,11 +1523,9 @@ var MicrofrontendsServer = class {
|
|
|
1495
1523
|
}
|
|
1496
1524
|
const repositoryRoot = findRepositoryRoot();
|
|
1497
1525
|
const isMonorepo2 = isMonorepo({ repositoryRoot });
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
process.env.VC_MICROFRONTENDS_CONFIG
|
|
1502
|
-
);
|
|
1526
|
+
const configFromEnv = process.env.VC_MICROFRONTENDS_CONFIG;
|
|
1527
|
+
if (typeof configFromEnv === "string") {
|
|
1528
|
+
const maybeConfigFromEnv = (0, import_node_path8.resolve)(packageRoot, configFromEnv);
|
|
1503
1529
|
if (maybeConfigFromEnv) {
|
|
1504
1530
|
return MicrofrontendsServer.fromFile({
|
|
1505
1531
|
filePath: maybeConfigFromEnv,
|
|
@@ -1508,7 +1534,8 @@ var MicrofrontendsServer = class {
|
|
|
1508
1534
|
}
|
|
1509
1535
|
} else {
|
|
1510
1536
|
const maybeConfigFromVercel = findConfig({
|
|
1511
|
-
dir: (0, import_node_path8.join)(packageRoot, ".vercel")
|
|
1537
|
+
dir: (0, import_node_path8.join)(packageRoot, ".vercel"),
|
|
1538
|
+
customConfigFilename
|
|
1512
1539
|
});
|
|
1513
1540
|
if (maybeConfigFromVercel) {
|
|
1514
1541
|
return MicrofrontendsServer.fromFile({
|
|
@@ -1519,9 +1546,13 @@ var MicrofrontendsServer = class {
|
|
|
1519
1546
|
if (isMonorepo2) {
|
|
1520
1547
|
const defaultPackage = inferMicrofrontendsLocation({
|
|
1521
1548
|
repositoryRoot,
|
|
1522
|
-
applicationContext
|
|
1549
|
+
applicationContext,
|
|
1550
|
+
customConfigFilename
|
|
1551
|
+
});
|
|
1552
|
+
const maybeConfigFromDefault = findConfig({
|
|
1553
|
+
dir: defaultPackage,
|
|
1554
|
+
customConfigFilename
|
|
1523
1555
|
});
|
|
1524
|
-
const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
|
|
1525
1556
|
if (maybeConfigFromDefault) {
|
|
1526
1557
|
return MicrofrontendsServer.fromFile({
|
|
1527
1558
|
filePath: maybeConfigFromDefault,
|
|
@@ -1760,6 +1791,14 @@ function transform6(args) {
|
|
|
1760
1791
|
pathname: "/.well-known/vercel/flags"
|
|
1761
1792
|
}
|
|
1762
1793
|
});
|
|
1794
|
+
rewrites.set(
|
|
1795
|
+
`/${app.getAssetPrefix()}/.well-known/vercel/rate-limit-api/:path*`,
|
|
1796
|
+
{
|
|
1797
|
+
destination: {
|
|
1798
|
+
pathname: "/.well-known/vercel/rate-limit-api/:path*"
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
);
|
|
1763
1802
|
rewrites.set(`/${app.getAssetPrefix()}/_vercel/:path*`, {
|
|
1764
1803
|
destination: {
|
|
1765
1804
|
pathname: "/_vercel/:path*"
|
|
@@ -1948,6 +1987,9 @@ function setEnvironment({
|
|
|
1948
1987
|
removeFlaggedPaths: true
|
|
1949
1988
|
}).serialize()
|
|
1950
1989
|
),
|
|
1990
|
+
...app.getAssetPrefix() ? {
|
|
1991
|
+
NEXT_PUBLIC_VERCEL_FIREWALL_PATH_PREFIX: `/${app.getAssetPrefix()}`
|
|
1992
|
+
} : {},
|
|
1951
1993
|
...process.env.ROUTE_OBSERVABILITY_TO_THIS_PROJECT && app.getAssetPrefix() ? {
|
|
1952
1994
|
NEXT_PUBLIC_VERCEL_OBSERVABILITY_BASEPATH: `/${app.getAssetPrefix()}/_vercel`
|
|
1953
1995
|
} : {}
|