UNPKG

@vercel/microfrontends

Version:

Defines configuration and utilities for microfrontends development

1,051 lines (1,027 loc) 32.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/next/middleware/index.ts var middleware_exports = {}; __export(middleware_exports, { getMicrofrontendsMiddleware: () => getMicrofrontendsMiddleware, runMicrofrontendsMiddleware: () => runMicrofrontendsMiddleware }); module.exports = __toCommonJS(middleware_exports); // src/next/middleware/middleware.ts var import_server = require("next/server"); var import_path_to_regexp3 = require("path-to-regexp"); // src/config/well-known/endpoints.ts async function getWellKnownClientData(config, flagValues = {}) { const clientConfig = config.toClientConfig(); for (const [applicationName, application] of Object.entries( clientConfig.applications )) { if (!application.routing) { continue; } const allPaths = []; for (const pathGroup of application.routing) { if (pathGroup.flag) { const flagName = pathGroup.flag; const flagFn = flagValues[flagName]; if (!flagFn) { throw new Error( `Flag "${flagName}" was specified to control routing for path group "${pathGroup.group}" in application ${applicationName} but not found in provided flag values.` ); } const flagEnabled = await flagFn(); if (flagEnabled) { allPaths.push(...pathGroup.paths); } } else { allPaths.push(...pathGroup.paths); } } application.routing = allPaths.length > 0 ? [{ paths: allPaths }] : []; } return { config: clientConfig.serialize() }; } // src/bin/local-proxy-is-running.ts function localProxyIsRunning() { return process.env.TURBO_TASK_HAS_MFE_PROXY === "true"; } // src/config/microfrontends-config/isomorphic/index.ts var import_jsonc_parser = require("jsonc-parser"); // src/config/errors.ts var MicrofrontendError = class extends Error { constructor(message, opts) { super(message, { cause: opts?.cause }); this.name = "MicrofrontendsError"; this.source = opts?.source ?? "@vercel/microfrontends"; this.type = opts?.type ?? "unknown"; this.subtype = opts?.subtype; Error.captureStackTrace(this, MicrofrontendError); } isKnown() { return this.type !== "unknown"; } isUnknown() { return !this.isKnown(); } /** * Converts an error to a MicrofrontendsError. * @param original - The original error to convert. * @returns The converted MicrofrontendsError. */ static convert(original, opts) { if (opts?.fileName) { const err = MicrofrontendError.convertFSError(original, opts.fileName); if (err) { return err; } } if (original.message.includes( "Code generation from strings disallowed for this context" )) { return new MicrofrontendError(original.message, { type: "config", subtype: "unsupported_validation_env", source: "ajv" }); } return new MicrofrontendError(original.message); } static convertFSError(original, fileName) { if (original instanceof Error && "code" in original) { if (original.code === "ENOENT") { return new MicrofrontendError(`Could not find "${fileName}"`, { type: "config", subtype: "unable_to_read_file", source: "fs" }); } if (original.code === "EACCES") { return new MicrofrontendError( `Permission denied while accessing "${fileName}"`, { type: "config", subtype: "invalid_permissions", source: "fs" } ); } } if (original instanceof SyntaxError) { return new MicrofrontendError( `Failed to parse "${fileName}": Invalid JSON format.`, { type: "config", subtype: "invalid_syntax", source: "fs" } ); } return null; } /** * Handles an unknown error and returns a MicrofrontendsError instance. * @param err - The error to handle. * @returns A MicrofrontendsError instance. */ static handle(err, opts) { if (err instanceof MicrofrontendError) { return err; } if (err instanceof Error) { return MicrofrontendError.convert(err, opts); } if (typeof err === "object" && err !== null) { if ("message" in err && typeof err.message === "string") { return MicrofrontendError.convert(new Error(err.message), opts); } } return new MicrofrontendError("An unknown error occurred"); } }; // src/config/microfrontends-config/utils/get-config-from-env.ts function getConfigStringFromEnv() { const config = process.env.MFE_CONFIG; if (!config) { throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, { type: "config", subtype: "not_found_in_env" }); } return config; } // src/config/schema/utils/is-default-app.ts function isDefaultApp(a) { return !("routing" in a); } // src/config/overrides/constants.ts var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override"; var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`; // src/config/overrides/is-override-cookie.ts function isOverrideCookie(cookie) { return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX)); } // src/config/overrides/get-override-from-cookie.ts function getOverrideFromCookie(cookie) { if (!isOverrideCookie(cookie) || !cookie.value) return; return { application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""), host: cookie.value }; } // src/config/overrides/parse-overrides.ts function parseOverrides(cookies) { const overridesConfig = { applications: {} }; cookies.forEach((cookie) => { const override = getOverrideFromCookie(cookie); if (!override) return; overridesConfig.applications[override.application] = { environment: { host: override.host } }; }); return overridesConfig; } // src/config/microfrontends-config/client/index.ts var import_path_to_regexp = require("path-to-regexp"); var regexpCache = /* @__PURE__ */ new Map(); var getRegexp = (path) => { const existing = regexpCache.get(path); if (existing) { return existing; } const regexp = (0, import_path_to_regexp.pathToRegexp)(path); regexpCache.set(path, regexp); return regexp; }; var MicrofrontendConfigClient = class { constructor(config, opts) { this.pathCache = {}; this.hasFlaggedPaths = config.hasFlaggedPaths ?? false; for (const app of Object.values(config.applications)) { if (app.routing) { if (app.routing.some((match) => match.flag)) { this.hasFlaggedPaths = true; } const newRouting = []; const pathsWithoutFlags = []; for (const group of app.routing) { if (group.flag) { if (opts?.removeFlaggedPaths) { continue; } if (group.group) { delete group.group; } newRouting.push(group); } else { pathsWithoutFlags.push(...group.paths); } } if (pathsWithoutFlags.length > 0) { newRouting.push({ paths: pathsWithoutFlags }); } app.routing = newRouting; } } this.serialized = config; if (this.hasFlaggedPaths) { this.serialized.hasFlaggedPaths = this.hasFlaggedPaths; } this.applications = config.applications; } /** * Create a new `MicrofrontendConfigClient` from a JSON string. * Config must be passed in to remain framework agnostic */ static fromEnv(config) { if (!config) { throw new Error( "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" ); } return new MicrofrontendConfigClient(JSON.parse(config)); } isEqual(other) { return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications); } getApplicationNameForPath(path) { if (!path.startsWith("/")) { throw new Error(`Path must start with a /`); } if (this.pathCache[path]) { return this.pathCache[path]; } const pathname = new URL(path, "https://example.com").pathname; for (const [name, application] of Object.entries(this.applications)) { if (application.routing) { for (const group of application.routing) { for (const childPath of group.paths) { const regexp = getRegexp(childPath); if (regexp.test(pathname)) { this.pathCache[path] = name; return name; } } } } } const defaultApplication = Object.entries(this.applications).find( ([, application]) => application.default ); if (!defaultApplication) { return null; } this.pathCache[path] = defaultApplication[0]; return defaultApplication[0]; } serialize() { return this.serialized; } }; // src/config/microfrontends-config/isomorphic/validation.ts var import_path_to_regexp2 = require("path-to-regexp"); var LIST_FORMATTER = new Intl.ListFormat("en", { style: "long", type: "conjunction" }); var VALID_ASSET_PREFIX_REGEXP = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/; var validateConfigPaths = (applicationConfigsById) => { if (!applicationConfigsById) { return; } const pathsByApplicationId = /* @__PURE__ */ new Map(); const errors = []; for (const [id, app] of Object.entries(applicationConfigsById)) { if (isDefaultApp(app)) { continue; } for (const pathMatch of app.routing) { for (const path of pathMatch.paths) { const maybeError = validatePathExpression(path); if (maybeError) { errors.push(maybeError); } else { const existing = pathsByApplicationId.get(path); if (existing) { existing.applications.push(id); } else { pathsByApplicationId.set(path, { applications: [id], matcher: (0, import_path_to_regexp2.pathToRegexp)(path), applicationId: id }); } } } } } const entries = Array.from(pathsByApplicationId.entries()); for (const [path, { applications: ids, matcher, applicationId }] of entries) { if (ids.length > 1) { errors.push( `Duplicate path "${path}" for applications "${ids.join(", ")}"` ); } for (const [ matchPath, { applications: matchIds, applicationId: matchApplicationId } ] of entries) { if (path === matchPath) { continue; } if (applicationId === matchApplicationId) { continue; } if (matcher.test(matchPath)) { const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`; const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`; errors.push( `Overlapping path detected between ${source} and ${destination}` ); } } } if (errors.length) { throw new MicrofrontendError( `Invalid paths: ${errors.join(", ")}. See supported paths in the documentation https://vercel.com/docs/microfrontends/path-routing#supported-path-expressions.`, { type: "config", subtype: "conflicting_paths" } ); } }; var PATH_DEFAULT_PATTERN = "[^\\/#\\?]+?"; function validatePathExpression(path) { try { const tokens = (0, import_path_to_regexp2.parse)(path); if (/(?<!\\)\{/.test(path)) { return `Optional paths are not supported: ${path}`; } if (/(?<!\\|\()\?/.test(path)) { return `Optional paths are not supported: ${path}`; } if (/\/[^/]*(?<!\\):[^/]*(?<!\\):[^/]*/.test(path)) { return `Only one wildcard is allowed per path segment: ${path}`; } for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token === void 0) { return `token ${i} in ${path} is undefined, this shouldn't happen`; } if (typeof token !== "string") { if (!token.name) { return `Only named wildcards are allowed: ${path} (hint: add ":path" to the wildcard)`; } if (token.pattern !== PATH_DEFAULT_PATTERN && // Allows (a|b|c) and ((?!a|b|c).*) regex // Only limited regex is supported for now, due to performance considerations // Allows all letters, numbers, and hyphens. Other characters must be escaped. !/^(?<allowed>[\w-~]+(?:\|[^:|()]+)+)$|^\(\?!(?<disallowed>[\w-~]+(?:\|[^:|()]+)*)\)\.\*$/.test( token.pattern.replace(/\\./g, "") )) { return `Path ${path} cannot use unsupported regular expression wildcard. If the path includes special characters, they must be escaped with backslash (e.g. '\\(')`; } if (token.modifier && i !== tokens.length - 1) { return `Modifier ${token.modifier} is not allowed on wildcard :${token.name} in ${path}. Modifiers are only allowed in the last path component`; } } } } catch (e) { const message = e instanceof Error ? e.message : String(e); return `Path ${path} could not be parsed into regexp: ${message}`; } return void 0; } var validateAppPaths = (name, app) => { for (const group of app.routing) { for (const p of group.paths) { if (p === "/") { continue; } if (p.endsWith("/")) { throw new MicrofrontendError( `Invalid path for application "${name}". ${p} must not end with a slash.`, { type: "application", subtype: "invalid_path" } ); } if (!p.startsWith("/")) { throw new MicrofrontendError( `Invalid path for application "${name}". ${p} must start with a slash.`, { type: "application", subtype: "invalid_path" } ); } } } if (app.assetPrefix) { if (!VALID_ASSET_PREFIX_REGEXP.test(app.assetPrefix)) { throw new MicrofrontendError( `Invalid asset prefix for application "${name}". ${app.assetPrefix} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`, { type: "application", subtype: "invalid_asset_prefix" } ); } if (app.assetPrefix !== `vc-ap-${name}` && !app.routing.some( (group) => group.paths.includes(`/${app.assetPrefix}/:path*`) && !group.flag )) { throw new MicrofrontendError( `When \`assetPrefix\` is specified, \`/${app.assetPrefix}/:path*\` must be added the routing paths for the application. Changing the asset prefix is not a forwards and backwards compatible change, and the custom asset prefix should be added to \`paths\` and deployed before setting the \`assetPrefix\` field.`, { type: "application", subtype: "invalid_asset_prefix" } ); } } }; var validateConfigDefaultApplication = (applicationConfigsById) => { if (!applicationConfigsById) { return; } const applicationsWithoutRouting = Object.entries( applicationConfigsById ).filter(([, app]) => isDefaultApp(app)); const numApplicationsWithoutRouting = applicationsWithoutRouting.reduce( (acc) => { return acc + 1; }, 0 ); if (numApplicationsWithoutRouting === 0) { throw new MicrofrontendError( "No default application found. At least one application needs to be the default by omitting routing.", { type: "config", subtype: "no_default_application" } ); } if (numApplicationsWithoutRouting > 1) { const applicationNamesMissingRouting = applicationsWithoutRouting.map( ([name]) => name ); throw new MicrofrontendError( `All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`, { type: "config", subtype: "multiple_default_applications" } ); } }; // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts var import_md5 = __toESM(require("md5"), 1); function hashApplicationName(name) { if (!name) { throw new Error("Application name is required to generate hash"); } return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0"); } // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts var PREFIX = "vc-ap"; function generateAssetPrefixFromName({ name }) { if (!name) { throw new Error("Name is required to generate an asset prefix"); } return `${PREFIX}-${hashApplicationName(name)}`; } // src/config/microfrontends-config/isomorphic/utils/generate-port.ts function generatePortFromName({ name, minPort = 3e3, maxPort = 8e3 }) { if (!name) { throw new Error("Name is required to generate a port"); } let hash = 0; for (let i = 0; i < name.length; i++) { hash = (hash << 5) - hash + name.charCodeAt(i); hash |= 0; } hash = Math.abs(hash); const range = maxPort - minPort; const port = minPort + hash % range; return port; } // src/config/microfrontends-config/isomorphic/host.ts var Host = class { constructor(hostConfig, options) { if (typeof hostConfig === "string") { ({ protocol: this.protocol, host: this.host, port: this.port } = Host.parseUrl(hostConfig)); } else { const { protocol = "https", host, port } = hostConfig; this.protocol = protocol; this.host = host; this.port = port; } this.local = options?.isLocal; } static parseUrl(url, defaultProtocol = "https") { let hostToParse = url; if (!/^https?:\/\//.exec(hostToParse)) { hostToParse = `${defaultProtocol}://${hostToParse}`; } const parsed = new URL(hostToParse); if (!parsed.hostname) { throw new Error(Host.getMicrofrontendsError(url, "requires a host")); } if (parsed.hash) { throw new Error( Host.getMicrofrontendsError(url, "cannot have a fragment") ); } if (parsed.username || parsed.password) { throw new Error( Host.getMicrofrontendsError( url, "cannot have authentication credentials (username and/or password)" ) ); } if (parsed.pathname !== "/") { throw new Error(Host.getMicrofrontendsError(url, "cannot have a path")); } if (parsed.search) { throw new Error( Host.getMicrofrontendsError(url, "cannot have query parameters") ); } const protocol = parsed.protocol.slice(0, -1); return { protocol, host: parsed.hostname, port: parsed.port ? Number.parseInt(parsed.port) : void 0 }; } static getMicrofrontendsError(url, message) { return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`; } isLocal() { return this.local || this.host === "localhost" || this.host === "127.0.0.1"; } toString() { const url = this.toUrl(); return url.toString().replace(/\/$/, ""); } toUrl() { const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`; return new URL(url); } }; var LocalHost = class extends Host { constructor({ appName, local }) { let protocol; let host; let port; if (typeof local === "number") { port = local; } else if (typeof local === "string") { if (/^\d+$/.test(local)) { port = Number.parseInt(local); } else { const parsed = Host.parseUrl(local, "http"); protocol = parsed.protocol; host = parsed.host; port = parsed.port; } } else if (local) { protocol = local.protocol; host = local.host; port = local.port; } super({ protocol: protocol ?? "http", host: host ?? "localhost", port: port ?? generatePortFromName({ name: appName }) }); } }; // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts function generateAutomationBypassEnvVarName({ name }) { return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`; } // src/config/microfrontends-config/isomorphic/application.ts var Application = class { constructor(name, { app, overrides, isDefault }) { this.name = name; this.development = { local: new LocalHost({ appName: name, local: app.development?.local }), fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0 }; if (app.development?.fallback) { this.fallback = new Host(app.development.fallback); } this.packageName = app.packageName; this.overrides = overrides?.environment ? { environment: new Host(overrides.environment) } : void 0; this.default = isDefault ?? false; this.serialized = app; } isDefault() { return this.default; } getAssetPrefix() { const generatedAssetPrefix = generateAssetPrefixFromName({ name: this.name }); if ("assetPrefix" in this.serialized) { return this.serialized.assetPrefix ?? generatedAssetPrefix; } return generatedAssetPrefix; } getAutomationBypassEnvVarName() { return generateAutomationBypassEnvVarName({ name: this.name }); } serialize() { return this.serialized; } }; var DefaultApplication = class extends Application { constructor(name, { app, overrides }) { super(name, { app, overrides, isDefault: true }); this.default = true; this.fallback = new Host(app.development.fallback); } getAssetPrefix() { return ""; } }; var ChildApplication = class extends Application { constructor(name, { app, overrides }) { ChildApplication.validate(name, app); super(name, { app, overrides, isDefault: false }); this.default = false; this.routing = app.routing; } static validate(name, app) { validateAppPaths(name, app); } }; // src/config/microfrontends-config/isomorphic/constants.ts var DEFAULT_LOCAL_PROXY_PORT = 3024; // src/config/microfrontends-config/isomorphic/index.ts var MicrofrontendConfigIsomorphic = class { constructor({ config, overrides }) { this.childApplications = {}; MicrofrontendConfigIsomorphic.validate(config); const disableOverrides = config.options?.disableOverrides ?? false; this.overrides = overrides && !disableOverrides ? overrides : void 0; let defaultApplication; for (const [appId, appConfig] of Object.entries(config.applications)) { const appOverrides = !disableOverrides ? this.overrides?.applications[appId] : void 0; if (isDefaultApp(appConfig)) { defaultApplication = new DefaultApplication(appId, { app: appConfig, overrides: appOverrides }); } else { this.childApplications[appId] = new ChildApplication(appId, { app: appConfig, overrides: appOverrides }); } } if (!defaultApplication) { throw new MicrofrontendError( "Could not find default application in microfrontends configuration", { type: "application", subtype: "not_found" } ); } this.defaultApplication = defaultApplication; this.config = config; this.options = config.options; this.serialized = { config, overrides }; } static validate(config) { const c = typeof config === "string" ? (0, import_jsonc_parser.parse)(config) : config; validateConfigPaths(c.applications); validateConfigDefaultApplication(c.applications); return c; } static fromEnv({ cookies }) { return new MicrofrontendConfigIsomorphic({ config: (0, import_jsonc_parser.parse)(getConfigStringFromEnv()), overrides: parseOverrides(cookies ?? []) }); } isOverridesDisabled() { return this.options?.disableOverrides ?? false; } getConfig() { return this.config; } getApplicationsByType() { return { defaultApplication: this.defaultApplication, applications: Object.values(this.childApplications) }; } getChildApplications() { return Object.values(this.childApplications); } getAllApplications() { return [ this.defaultApplication, ...Object.values(this.childApplications) ].filter(Boolean); } getApplication(name) { if (this.defaultApplication.name === name || this.defaultApplication.packageName === name) { return this.defaultApplication; } const app = this.childApplications[name] || Object.values(this.childApplications).find( (child) => child.packageName === name ); if (!app) { throw new MicrofrontendError( `Could not find microfrontends configuration for application "${name}". If the name in package.json differs from your Vercel Project name, set the \`packageName\` field for the application in \`microfrontends.json\` to ensure that the configuration can be found locally.`, { type: "application", subtype: "not_found" } ); } return app; } hasApplication(name) { try { this.getApplication(name); return true; } catch { return false; } } getApplicationByProjectName(projectName) { if (this.defaultApplication.name === projectName) { return this.defaultApplication; } return Object.values(this.childApplications).find( (app) => app.name === projectName ); } /** * Returns the default application. */ getDefaultApplication() { return this.defaultApplication; } /** * Returns the configured port for the local proxy */ getLocalProxyPort() { return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT; } toClientConfig(options) { const applications = Object.fromEntries( Object.entries(this.childApplications).map(([name, application]) => [ hashApplicationName(name), { default: false, routing: application.routing } ]) ); applications[hashApplicationName(this.defaultApplication.name)] = { default: true }; return new MicrofrontendConfigClient( { applications }, { removeFlaggedPaths: options?.removeFlaggedPaths } ); } /** * Serializes the class back to the Schema type. * * NOTE: This is used when writing the config to disk and must always match the input Schema */ toSchemaJson() { return this.serialized.config; } serialize() { return this.serialized; } }; // src/bin/logger.ts function debug(...args) { if (process.env.MFE_DEBUG) { console.log(...args); } } function info(...args) { console.log(...args); } function warn(...args) { console.warn(...args); } function error(...args) { console.error(...args); } var logger = { debug, info, warn, error }; // src/next/middleware/middleware.ts function getMfeFlagHeader(req) { const flagValue = req.headers.get("x-vercel-mfe-flag-value"); if (flagValue === "true") { return true; } if (flagValue === "false") { return false; } return null; } function getFlagHandler({ application, flagFn, flagName, pattern, localProxyPort }) { return async (req) => { try { const pathname = req.nextUrl.pathname; const localProxyRunning = localProxyIsRunning(); const flagValueFromHeader = localProxyRunning ? getMfeFlagHeader(req) : null; if (pattern.test(pathname) && (flagValueFromHeader ?? await flagFn())) { const headers = new Headers(req.headers); headers.set("x-vercel-mfe-zone", application.name); const middlewareResponseInit = { request: { headers } }; if (localProxyRunning) { logger.debug( `Routing flagged path "${pathname}" to local proxy for application "${application.name}"` ); const url = req.nextUrl; url.host = `localhost:${localProxyPort}`; return import_server.NextResponse.rewrite(url, middlewareResponseInit); } logger.debug( `Routing flagged path "${pathname}" to application "${application.name}"` ); return import_server.NextResponse.next(middlewareResponseInit); } } catch (e) { logger.error( `An error occured in the microfrontends middleware evaluating the flag "${flagName}":`, e ); throw e; } }; } function getWellKnownClientConfigMiddleware(config, flagValues) { return async (_) => { return import_server.NextResponse.json( await getWellKnownClientData(config, flagValues) ); }; } function getMicrofrontendsMiddleware({ request, flagValues }) { const microfrontends = MicrofrontendConfigIsomorphic.fromEnv({ cookies: request.cookies.getAll() }); const middlewares = []; if (!process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION) { throw new Error( "The NEXT_PUBLIC_MFE_CURRENT_APPLICATION environment variable is not set. Did you run `withMicrofrontends` in your Next.js config?" ); } const currentApplication = microfrontends.getApplication( process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION ); if (!currentApplication.isDefault()) { return middlewares; } middlewares.push({ src: "/.well-known/vercel/microfrontends/client-config", fn: getWellKnownClientConfigMiddleware(microfrontends, flagValues) }); const localProxyPort = microfrontends.getLocalProxyPort(); for (const application of microfrontends.getChildApplications()) { for (const pathGroup of application.routing) { const flagName = pathGroup.flag; if (flagName) { const flagFn = flagValues[flagName]; if (!flagFn) { throw new Error( `Flag "${flagName}" was specified to control routing for path group "${pathGroup.group}" in application ${application.name} but not found in provided flag values. See https://vercel.com/docs/microfrontends/path-routing#add-microfrontends-middleware for more information.` ); } for (const path of pathGroup.paths) { const pattern = (0, import_path_to_regexp3.pathToRegexp)(path); middlewares.push({ src: pattern, fn: getFlagHandler({ application, flagFn, flagName, pattern, localProxyPort }) }); } } } } return middlewares; } async function runMicrofrontendsMiddleware({ request, flagValues }) { const pathname = request.nextUrl.pathname; const middlewares = getMicrofrontendsMiddleware({ request, flagValues }); for (const mware of middlewares) { if (typeof mware.src === "string" ? pathname === mware.src : mware.src.test(pathname)) { const response = await mware.fn(request); if (response) { return response; } } } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { getMicrofrontendsMiddleware, runMicrofrontendsMiddleware }); //# sourceMappingURL=middleware.cjs.map