@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @vercel/microfrontends
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c856a5d:
|
|
8
|
+
- Add support for custom `microfrontends.json` file names. This enables a single application to be deployed as multiple different vercel projects / microfrontends.
|
|
9
|
+
- Strip asset prefix from Vercel Firewall rate limit paths. Support Vercel Firewall rate limit requests when they go to a child application.
|
|
10
|
+
|
|
11
|
+
## 2.0.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 8c11bdc:
|
|
16
|
+
- Support hyphens and escaped special characters in supported path matching regex https://vercel.com/docs/microfrontends/path-routing#supported-path-expressions
|
|
17
|
+
- Improve error message for when local development proxy can't determine the port
|
|
18
|
+
- Update local proxy double slash routing behaviour to match the production proxy
|
|
19
|
+
|
|
3
20
|
## 2.0.0
|
|
4
21
|
|
|
5
22
|
> **Check out our [Public Beta](https://vercel.com/changelog/microfrontends-support-is-now-in-public-beta) changelog to learn more about this release.**
|
package/README.md
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
<source srcset="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-package-banner-dark.png" media="(prefers-color-scheme: dark)">
|
|
3
|
-
<source srcset="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-package-banner-light.png" media="(prefers-color-scheme: light)">
|
|
4
|
-
<img src="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-package-banner-dark.png" alt="hero banner">
|
|
5
|
-
</picture>
|
|
1
|
+

|
|
6
2
|
|
|
7
3
|
# @vercel/microfrontends
|
|
8
4
|
|
package/dist/bin/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var import_env = require("@next/env");
|
|
|
30
30
|
// package.json
|
|
31
31
|
var package_default = {
|
|
32
32
|
name: "@vercel/microfrontends",
|
|
33
|
-
version: "2.
|
|
33
|
+
version: "2.1.0",
|
|
34
34
|
private: false,
|
|
35
35
|
description: "Defines configuration and utilities for microfrontends development",
|
|
36
36
|
keywords: [
|
|
@@ -169,11 +169,11 @@ var package_default = {
|
|
|
169
169
|
typecheck: "tsc --noEmit"
|
|
170
170
|
},
|
|
171
171
|
dependencies: {
|
|
172
|
-
"@next/env": "15.4
|
|
172
|
+
"@next/env": "15.5.4",
|
|
173
173
|
"@types/md5": "^2.3.5",
|
|
174
174
|
ajv: "^8.17.1",
|
|
175
175
|
commander: "^12.1.0",
|
|
176
|
-
cookie: "0.
|
|
176
|
+
cookie: "1.0.2",
|
|
177
177
|
"fast-glob": "^3.3.2",
|
|
178
178
|
"http-proxy": "^1.18.1",
|
|
179
179
|
"jsonc-parser": "^3.3.1",
|
|
@@ -198,7 +198,7 @@ var package_default = {
|
|
|
198
198
|
"eslint-config-custom": "workspace:*",
|
|
199
199
|
jest: "^29.7.0",
|
|
200
200
|
"jest-environment-jsdom": "29.2.2",
|
|
201
|
-
next: "15.4
|
|
201
|
+
next: "15.5.4",
|
|
202
202
|
react: "19.0.0",
|
|
203
203
|
"react-dom": "19.0.0",
|
|
204
204
|
"ts-config": "workspace:*",
|
|
@@ -452,7 +452,7 @@ var MicrofrontendConfigClient = class {
|
|
|
452
452
|
static fromEnv(config) {
|
|
453
453
|
if (!config) {
|
|
454
454
|
throw new Error(
|
|
455
|
-
"Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
|
|
455
|
+
"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"
|
|
456
456
|
);
|
|
457
457
|
}
|
|
458
458
|
return new MicrofrontendConfigClient(JSON.parse(config));
|
|
@@ -592,10 +592,11 @@ function validatePathExpression(path7) {
|
|
|
592
592
|
}
|
|
593
593
|
if (token.pattern !== PATH_DEFAULT_PATTERN && // Allows (a|b|c) and ((?!a|b|c).*) regex
|
|
594
594
|
// Only limited regex is supported for now, due to performance considerations
|
|
595
|
-
|
|
596
|
-
|
|
595
|
+
// Allows all letters, numbers, and hyphens. Other characters must be escaped.
|
|
596
|
+
!/^(?<allowed>[\w-~]+(?:\|[^:|()]+)+)$|^\(\?!(?<disallowed>[\w-~]+(?:\|[^:|()]+)*)\)\.\*$/.test(
|
|
597
|
+
token.pattern.replace(/\\./g, "")
|
|
597
598
|
)) {
|
|
598
|
-
return `Path ${path7} cannot use unsupported regular expression wildcard`;
|
|
599
|
+
return `Path ${path7} cannot use unsupported regular expression wildcard. If the path includes special characters, they must be escaped with backslash (e.g. '\\(')`;
|
|
599
600
|
}
|
|
600
601
|
if (token.modifier && i !== tokens.length - 1) {
|
|
601
602
|
return `Modifier ${token.modifier} is not allowed on wildcard :${token.name} in ${path7}. Modifiers are only allowed in the last path component`;
|
|
@@ -1111,22 +1112,38 @@ var import_node_fs2 = require("fs");
|
|
|
1111
1112
|
var import_jsonc_parser2 = require("jsonc-parser");
|
|
1112
1113
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
1113
1114
|
|
|
1114
|
-
// src/config/
|
|
1115
|
-
var
|
|
1115
|
+
// src/config/microfrontends/utils/get-config-file-name.ts
|
|
1116
|
+
var DEFAULT_CONFIGURATION_FILENAMES = [
|
|
1116
1117
|
"microfrontends.jsonc",
|
|
1117
1118
|
"microfrontends.json"
|
|
1118
1119
|
];
|
|
1120
|
+
function getPossibleConfigurationFilenames({
|
|
1121
|
+
customConfigFilename
|
|
1122
|
+
}) {
|
|
1123
|
+
if (customConfigFilename) {
|
|
1124
|
+
if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
`The VC_MICROFRONTENDS_CONFIG_FILE_NAME environment variable must end with '.json' or '.jsonc'. Received: ${customConfigFilename}`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
return Array.from(
|
|
1130
|
+
/* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
return DEFAULT_CONFIGURATION_FILENAMES;
|
|
1134
|
+
}
|
|
1119
1135
|
|
|
1120
1136
|
// src/config/microfrontends/utils/infer-microfrontends-location.ts
|
|
1121
1137
|
var configCache = {};
|
|
1122
1138
|
function findPackageWithMicrofrontendsConfig({
|
|
1123
1139
|
repositoryRoot,
|
|
1124
|
-
applicationContext
|
|
1140
|
+
applicationContext,
|
|
1141
|
+
customConfigFilename
|
|
1125
1142
|
}) {
|
|
1126
1143
|
const applicationName = applicationContext.name;
|
|
1127
1144
|
try {
|
|
1128
1145
|
const microfrontendsJsonPaths = import_fast_glob.default.globSync(
|
|
1129
|
-
`**/{${
|
|
1146
|
+
`**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
|
|
1130
1147
|
{
|
|
1131
1148
|
cwd: repositoryRoot,
|
|
1132
1149
|
absolute: true,
|
|
@@ -1182,6 +1199,8 @@ Names of applications in \`microfrontends.json\` must match the Vercel Project n
|
|
|
1182
1199
|
|
|
1183
1200
|
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.
|
|
1184
1201
|
|
|
1202
|
+
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.
|
|
1203
|
+
|
|
1185
1204
|
If you suspect this is thrown in error, please reach out to the Vercel team.`,
|
|
1186
1205
|
{ type: "config", subtype: "inference_failed" }
|
|
1187
1206
|
);
|
|
@@ -1196,7 +1215,7 @@ If you suspect this is thrown in error, please reach out to the Vercel team.`,
|
|
|
1196
1215
|
}
|
|
1197
1216
|
}
|
|
1198
1217
|
function inferMicrofrontendsLocation(opts) {
|
|
1199
|
-
const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
|
|
1218
|
+
const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
|
|
1200
1219
|
if (configCache[cacheKey]) {
|
|
1201
1220
|
return configCache[cacheKey];
|
|
1202
1221
|
}
|
|
@@ -1262,8 +1281,13 @@ function findPackageRoot(startDir) {
|
|
|
1262
1281
|
// src/config/microfrontends/utils/find-config.ts
|
|
1263
1282
|
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
1264
1283
|
var import_node_path5 = require("path");
|
|
1265
|
-
function findConfig({
|
|
1266
|
-
|
|
1284
|
+
function findConfig({
|
|
1285
|
+
dir,
|
|
1286
|
+
customConfigFilename
|
|
1287
|
+
}) {
|
|
1288
|
+
for (const filename of getPossibleConfigurationFilenames({
|
|
1289
|
+
customConfigFilename
|
|
1290
|
+
})) {
|
|
1267
1291
|
const maybeConfig = (0, import_node_path5.join)(dir, filename);
|
|
1268
1292
|
if (import_node_fs5.default.existsSync(maybeConfig)) {
|
|
1269
1293
|
return maybeConfig;
|
|
@@ -1695,7 +1719,11 @@ var MicrofrontendsServer = class {
|
|
|
1695
1719
|
appName,
|
|
1696
1720
|
packageRoot
|
|
1697
1721
|
});
|
|
1698
|
-
const
|
|
1722
|
+
const customConfigFilename = process.env.VC_MICROFRONTENDS_CONFIG_FILE_NAME;
|
|
1723
|
+
const maybeConfig = findConfig({
|
|
1724
|
+
dir: packageRoot,
|
|
1725
|
+
customConfigFilename
|
|
1726
|
+
});
|
|
1699
1727
|
if (maybeConfig) {
|
|
1700
1728
|
return MicrofrontendsServer.fromFile({
|
|
1701
1729
|
filePath: maybeConfig,
|
|
@@ -1704,11 +1732,9 @@ var MicrofrontendsServer = class {
|
|
|
1704
1732
|
}
|
|
1705
1733
|
const repositoryRoot = findRepositoryRoot();
|
|
1706
1734
|
const isMonorepo2 = isMonorepo({ repositoryRoot });
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
process.env.VC_MICROFRONTENDS_CONFIG
|
|
1711
|
-
);
|
|
1735
|
+
const configFromEnv = process.env.VC_MICROFRONTENDS_CONFIG;
|
|
1736
|
+
if (typeof configFromEnv === "string") {
|
|
1737
|
+
const maybeConfigFromEnv = (0, import_node_path8.resolve)(packageRoot, configFromEnv);
|
|
1712
1738
|
if (maybeConfigFromEnv) {
|
|
1713
1739
|
return MicrofrontendsServer.fromFile({
|
|
1714
1740
|
filePath: maybeConfigFromEnv,
|
|
@@ -1717,7 +1743,8 @@ var MicrofrontendsServer = class {
|
|
|
1717
1743
|
}
|
|
1718
1744
|
} else {
|
|
1719
1745
|
const maybeConfigFromVercel = findConfig({
|
|
1720
|
-
dir: (0, import_node_path8.join)(packageRoot, ".vercel")
|
|
1746
|
+
dir: (0, import_node_path8.join)(packageRoot, ".vercel"),
|
|
1747
|
+
customConfigFilename
|
|
1721
1748
|
});
|
|
1722
1749
|
if (maybeConfigFromVercel) {
|
|
1723
1750
|
return MicrofrontendsServer.fromFile({
|
|
@@ -1728,9 +1755,13 @@ var MicrofrontendsServer = class {
|
|
|
1728
1755
|
if (isMonorepo2) {
|
|
1729
1756
|
const defaultPackage = inferMicrofrontendsLocation({
|
|
1730
1757
|
repositoryRoot,
|
|
1731
|
-
applicationContext
|
|
1758
|
+
applicationContext,
|
|
1759
|
+
customConfigFilename
|
|
1760
|
+
});
|
|
1761
|
+
const maybeConfigFromDefault = findConfig({
|
|
1762
|
+
dir: defaultPackage,
|
|
1763
|
+
customConfigFilename
|
|
1732
1764
|
});
|
|
1733
|
-
const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
|
|
1734
1765
|
if (maybeConfigFromDefault) {
|
|
1735
1766
|
return MicrofrontendsServer.fromFile({
|
|
1736
1767
|
filePath: maybeConfigFromDefault,
|
|
@@ -2112,12 +2143,6 @@ var localAuthHtml = ({
|
|
|
2112
2143
|
var MFE_LOCAL_PROXY_HEADER = "x-vercel-mfe-local-proxy-origin";
|
|
2113
2144
|
var MFE_FLAG_VALUE = "vercel-mfe-flag-value";
|
|
2114
2145
|
var MFE_FLAG_VALUE_HEADER = `x-${MFE_FLAG_VALUE}`;
|
|
2115
|
-
var MFE_DEBUG = process.env.MFE_DEBUG;
|
|
2116
|
-
var mfeDebug = (message) => {
|
|
2117
|
-
if (MFE_DEBUG === "true" || MFE_DEBUG === "1") {
|
|
2118
|
-
console.log(message);
|
|
2119
|
-
}
|
|
2120
|
-
};
|
|
2121
2146
|
var ProxyRequestRouter = class {
|
|
2122
2147
|
constructor(config, {
|
|
2123
2148
|
localApps
|
|
@@ -2130,8 +2155,10 @@ var ProxyRequestRouter = class {
|
|
|
2130
2155
|
return this.getApplicationTarget(defaultApp);
|
|
2131
2156
|
}
|
|
2132
2157
|
getApplicationTarget(application) {
|
|
2133
|
-
const useDev =
|
|
2134
|
-
|
|
2158
|
+
const useDev = Boolean(
|
|
2159
|
+
this.localApps.find(
|
|
2160
|
+
(name) => name === application.name || name === application.packageName
|
|
2161
|
+
)
|
|
2135
2162
|
);
|
|
2136
2163
|
let applicationName = application.name;
|
|
2137
2164
|
let host = useDev ? application.development.local : application.fallback;
|
|
@@ -2151,7 +2178,9 @@ var ProxyRequestRouter = class {
|
|
|
2151
2178
|
protocol,
|
|
2152
2179
|
hostname,
|
|
2153
2180
|
port,
|
|
2154
|
-
application: applicationName
|
|
2181
|
+
application: applicationName,
|
|
2182
|
+
isLocal: useDev,
|
|
2183
|
+
originalApplication: application.name
|
|
2155
2184
|
};
|
|
2156
2185
|
}
|
|
2157
2186
|
/**
|
|
@@ -2225,8 +2254,8 @@ var ProxyRequestRouter = class {
|
|
|
2225
2254
|
if (target)
|
|
2226
2255
|
return target;
|
|
2227
2256
|
const defaultHost = this.getDefaultHost(config);
|
|
2228
|
-
|
|
2229
|
-
`
|
|
2257
|
+
console.log(
|
|
2258
|
+
` ${path7} - Did not match any routes. Routing to default app: ${formatProxyTarget(defaultHost)}`
|
|
2230
2259
|
);
|
|
2231
2260
|
return { path: path7, ...defaultHost };
|
|
2232
2261
|
}
|
|
@@ -2242,8 +2271,8 @@ var ProxyRequestRouter = class {
|
|
|
2242
2271
|
const target = this.getApplicationTarget(application);
|
|
2243
2272
|
if (middlewareMfeZone) {
|
|
2244
2273
|
if (middlewareMfeZone === application.name) {
|
|
2245
|
-
|
|
2246
|
-
`
|
|
2274
|
+
console.log(
|
|
2275
|
+
` ${path7} - Routing to ${formatProxyTarget(target)} according to 'x-vercel-mfe-zone' header`
|
|
2247
2276
|
);
|
|
2248
2277
|
return { path: path7, ...target };
|
|
2249
2278
|
}
|
|
@@ -2262,15 +2291,12 @@ var ProxyRequestRouter = class {
|
|
|
2262
2291
|
for (const childPath of group.paths) {
|
|
2263
2292
|
const regexp = (0, import_path_to_regexp3.pathToRegexp)(childPath);
|
|
2264
2293
|
if (regexp.test(url.pathname)) {
|
|
2265
|
-
mfeDebug(
|
|
2266
|
-
`routing ${path7} to '${target.application}' at ${target.hostname}`
|
|
2267
|
-
);
|
|
2268
2294
|
if (group.flag) {
|
|
2269
2295
|
if (mfeFlagValue === true) {
|
|
2270
2296
|
} else if (mfeFlagValue === false) {
|
|
2271
2297
|
continue;
|
|
2272
2298
|
} else {
|
|
2273
|
-
|
|
2299
|
+
console.log(
|
|
2274
2300
|
"Routing group is behind flag. Routing to default app to check flag via middleware."
|
|
2275
2301
|
);
|
|
2276
2302
|
if (!this.isDefaultAppLocal()) {
|
|
@@ -2282,6 +2308,9 @@ var ProxyRequestRouter = class {
|
|
|
2282
2308
|
return null;
|
|
2283
2309
|
}
|
|
2284
2310
|
}
|
|
2311
|
+
console.log(
|
|
2312
|
+
` ${path7} - Matched ${childPath}. Routing to ${formatProxyTarget(target)}`
|
|
2313
|
+
);
|
|
2285
2314
|
return { path: path7, ...target };
|
|
2286
2315
|
}
|
|
2287
2316
|
}
|
|
@@ -2310,8 +2339,8 @@ var ProxyRequestRouter = class {
|
|
|
2310
2339
|
for (const rewrite of rewrites) {
|
|
2311
2340
|
for (const assetPrefix of assetPrefixes) {
|
|
2312
2341
|
if ((0, import_path_to_regexp3.pathToRegexp)(`/${assetPrefix}${rewrite}`).test(pathname)) {
|
|
2313
|
-
|
|
2314
|
-
`
|
|
2342
|
+
console.log(
|
|
2343
|
+
` ${pathname} - Matched asset prefix. Routing to ${formatProxyTarget(target)}`
|
|
2315
2344
|
);
|
|
2316
2345
|
return {
|
|
2317
2346
|
path: path7,
|
|
@@ -2339,13 +2368,16 @@ var ProxyRequestRouter = class {
|
|
|
2339
2368
|
url: refererURL,
|
|
2340
2369
|
applications
|
|
2341
2370
|
});
|
|
2342
|
-
|
|
2343
|
-
|
|
2371
|
+
if (!refererApp) {
|
|
2372
|
+
return null;
|
|
2373
|
+
}
|
|
2374
|
+
console.log(
|
|
2375
|
+
` ${refererURL.pathname} - Routing nextjs stack frame request to ${formatProxyTarget(refererApp)}`
|
|
2344
2376
|
);
|
|
2345
|
-
return
|
|
2377
|
+
return {
|
|
2346
2378
|
...refererApp,
|
|
2347
2379
|
path: `${url.pathname}${url.search}`
|
|
2348
|
-
}
|
|
2380
|
+
};
|
|
2349
2381
|
}
|
|
2350
2382
|
checkNextSourceMap({ url }) {
|
|
2351
2383
|
const isSourceMap = (0, import_path_to_regexp3.pathToRegexp)("/__nextjs_source-map").test(url.pathname);
|
|
@@ -2354,11 +2386,15 @@ var ProxyRequestRouter = class {
|
|
|
2354
2386
|
}
|
|
2355
2387
|
const localApp = this.getArbitraryLocalApp();
|
|
2356
2388
|
if (!localApp) {
|
|
2357
|
-
|
|
2389
|
+
console.error(
|
|
2390
|
+
` ${url.pathname} - No locally running application to route request to`
|
|
2391
|
+
);
|
|
2358
2392
|
return null;
|
|
2359
2393
|
}
|
|
2360
2394
|
const target = this.getApplicationTarget(localApp);
|
|
2361
|
-
|
|
2395
|
+
console.log(
|
|
2396
|
+
` ${url.pathname} - Routing nextjs source map request to randomly selected local application: ${formatProxyTarget(target)}`
|
|
2397
|
+
);
|
|
2362
2398
|
return {
|
|
2363
2399
|
...target,
|
|
2364
2400
|
path: `${url.pathname}${url.search}`
|
|
@@ -2374,7 +2410,9 @@ var ProxyRequestRouter = class {
|
|
|
2374
2410
|
}
|
|
2375
2411
|
const imageUrl = url.searchParams.get("url");
|
|
2376
2412
|
if (!imageUrl) {
|
|
2377
|
-
|
|
2413
|
+
console.error(
|
|
2414
|
+
` ${url.pathname}?${url.search} - No url parameter found in _next/image request`
|
|
2415
|
+
);
|
|
2378
2416
|
return null;
|
|
2379
2417
|
}
|
|
2380
2418
|
const decodedPath = decodeURIComponent(imageUrl);
|
|
@@ -2384,11 +2422,16 @@ var ProxyRequestRouter = class {
|
|
|
2384
2422
|
url: imageURL,
|
|
2385
2423
|
applications
|
|
2386
2424
|
});
|
|
2387
|
-
|
|
2388
|
-
|
|
2425
|
+
if (!imageApp) {
|
|
2426
|
+
return null;
|
|
2427
|
+
}
|
|
2428
|
+
console.log(
|
|
2429
|
+
` ${url.pathname}?${url.search} - Routing nextjs image request to ${formatProxyTarget(imageApp)}`
|
|
2430
|
+
);
|
|
2431
|
+
return {
|
|
2389
2432
|
...imageApp,
|
|
2390
2433
|
path: `${url.pathname}${url.search}`
|
|
2391
|
-
}
|
|
2434
|
+
};
|
|
2392
2435
|
}
|
|
2393
2436
|
isDefaultAppLocal() {
|
|
2394
2437
|
const defaultApp = this.config.getDefaultApplication();
|
|
@@ -2410,10 +2453,12 @@ var ProxyRequestRouter = class {
|
|
|
2410
2453
|
var LocalProxy = class {
|
|
2411
2454
|
constructor(config, {
|
|
2412
2455
|
localApps,
|
|
2413
|
-
proxyPort
|
|
2456
|
+
proxyPort,
|
|
2457
|
+
configFilePath
|
|
2414
2458
|
}) {
|
|
2415
2459
|
this.router = new ProxyRequestRouter(config, { localApps });
|
|
2416
2460
|
this.proxyPort = proxyPort ?? this.router.config.getLocalProxyPort();
|
|
2461
|
+
this.configFilePath = configFilePath;
|
|
2417
2462
|
this.proxy = import_http_proxy.default.createProxyServer({ secure: true });
|
|
2418
2463
|
this.proxy.on("error", (err, req, res) => {
|
|
2419
2464
|
if (res instanceof http.ServerResponse) {
|
|
@@ -2423,9 +2468,12 @@ var LocalProxy = class {
|
|
|
2423
2468
|
}
|
|
2424
2469
|
const target = this.router.getTarget(req);
|
|
2425
2470
|
res.end(
|
|
2426
|
-
`Error proxying request to ${target
|
|
2471
|
+
`Error proxying request to ${formatProxyTarget(target)}. Is the server running locally on port ${target.port}?`
|
|
2472
|
+
);
|
|
2473
|
+
console.error(
|
|
2474
|
+
`Error proxying request for ${formatProxyTarget(target)}: `,
|
|
2475
|
+
err
|
|
2427
2476
|
);
|
|
2428
|
-
console.error(`Error proxying request for ${target.application}: `, err);
|
|
2429
2477
|
});
|
|
2430
2478
|
}
|
|
2431
2479
|
static fromFile(filePath, {
|
|
@@ -2441,7 +2489,11 @@ var LocalProxy = class {
|
|
|
2441
2489
|
microfrontends = MicrofrontendsServer.infer();
|
|
2442
2490
|
}
|
|
2443
2491
|
LocalProxy.validateLocalApps(localApps, microfrontends.config);
|
|
2444
|
-
return new LocalProxy(microfrontends.config, {
|
|
2492
|
+
return new LocalProxy(microfrontends.config, {
|
|
2493
|
+
localApps,
|
|
2494
|
+
proxyPort,
|
|
2495
|
+
configFilePath: filePath
|
|
2496
|
+
});
|
|
2445
2497
|
}
|
|
2446
2498
|
static validateLocalApps(localApps, config) {
|
|
2447
2499
|
const unknownApps = [];
|
|
@@ -2478,13 +2530,37 @@ var LocalProxy = class {
|
|
|
2478
2530
|
}
|
|
2479
2531
|
});
|
|
2480
2532
|
httpServer.listen(this.proxyPort, () => {
|
|
2481
|
-
|
|
2533
|
+
this.displayStartupMessage();
|
|
2482
2534
|
});
|
|
2483
2535
|
}
|
|
2484
2536
|
handleRequest(req, res) {
|
|
2485
2537
|
if (this.handleProxyInfoRequest(req.url, res)) {
|
|
2486
2538
|
return;
|
|
2487
2539
|
}
|
|
2540
|
+
if (req.url?.includes("//")) {
|
|
2541
|
+
const originalUrl = req.url;
|
|
2542
|
+
if (originalUrl) {
|
|
2543
|
+
const normalizedUrl = originalUrl.replaceAll(/\/[\\/]+/g, "/");
|
|
2544
|
+
if (normalizedUrl !== originalUrl) {
|
|
2545
|
+
res.writeHead(307, {
|
|
2546
|
+
Location: normalizedUrl,
|
|
2547
|
+
// Copy incoming request headers except hop-by-hop headers and Location
|
|
2548
|
+
...Object.fromEntries(
|
|
2549
|
+
Object.entries(req.headers).filter(
|
|
2550
|
+
([key]) => ![
|
|
2551
|
+
"connection",
|
|
2552
|
+
"content-length",
|
|
2553
|
+
"transfer-encoding",
|
|
2554
|
+
"location"
|
|
2555
|
+
].includes(key.toLowerCase())
|
|
2556
|
+
)
|
|
2557
|
+
)
|
|
2558
|
+
});
|
|
2559
|
+
res.end();
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2488
2564
|
const target = this.router.getTarget(req);
|
|
2489
2565
|
const { req: strippedReq, mfeFlagValue } = removeMfeFlagQuery(req);
|
|
2490
2566
|
if (target.protocol === "https") {
|
|
@@ -2518,7 +2594,7 @@ var LocalProxy = class {
|
|
|
2518
2594
|
})
|
|
2519
2595
|
);
|
|
2520
2596
|
}
|
|
2521
|
-
if (realRes.statusCode === 307) {
|
|
2597
|
+
if (realRes.statusCode === 307 || realRes.statusCode === 308 || realRes.statusCode === 302 || realRes.statusCode === 301) {
|
|
2522
2598
|
const locationHeader = realRes.headers.location;
|
|
2523
2599
|
if (locationHeader) {
|
|
2524
2600
|
const redirectUrl = new import_node_url.URL(
|
|
@@ -2579,6 +2655,79 @@ var LocalProxy = class {
|
|
|
2579
2655
|
}
|
|
2580
2656
|
return false;
|
|
2581
2657
|
}
|
|
2658
|
+
displayStartupMessage() {
|
|
2659
|
+
const allApps = this.router.config.getAllApplications();
|
|
2660
|
+
const localApps = [];
|
|
2661
|
+
const fallbackApps = [];
|
|
2662
|
+
const defaultApp = this.router.config.getDefaultApplication();
|
|
2663
|
+
const defaultFallback = defaultApp.fallback.host;
|
|
2664
|
+
for (const app of allApps) {
|
|
2665
|
+
const isLocal = this.router.localApps.find(
|
|
2666
|
+
(name) => name === app.name || name === app.packageName
|
|
2667
|
+
);
|
|
2668
|
+
if (isLocal) {
|
|
2669
|
+
localApps.push({
|
|
2670
|
+
name: app.name,
|
|
2671
|
+
port: app.development.local.port
|
|
2672
|
+
});
|
|
2673
|
+
} else {
|
|
2674
|
+
const target = this.router.getApplicationTarget(app);
|
|
2675
|
+
let fallbackHost = target.hostname;
|
|
2676
|
+
if (!app.fallback) {
|
|
2677
|
+
fallbackHost = defaultFallback;
|
|
2678
|
+
}
|
|
2679
|
+
fallbackApps.push({
|
|
2680
|
+
name: app.name,
|
|
2681
|
+
fallback: fallbackHost
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
console.log(`
|
|
2686
|
+
\u25B2 Microfrontends Proxy (${package_default.version}) Started`);
|
|
2687
|
+
console.log(` - Proxy URL: http://localhost:${this.proxyPort}`);
|
|
2688
|
+
if (this.configFilePath) {
|
|
2689
|
+
console.log(` - Config: ${this.configFilePath}`);
|
|
2690
|
+
}
|
|
2691
|
+
if (localApps.length > 0) {
|
|
2692
|
+
console.log(" - Local Applications:");
|
|
2693
|
+
const displayLocalApps = localApps.length > 5 ? [
|
|
2694
|
+
...localApps.slice(0, 5),
|
|
2695
|
+
{ name: `... and ${localApps.length - 5} more`, port: void 0 }
|
|
2696
|
+
] : localApps;
|
|
2697
|
+
for (const app of displayLocalApps) {
|
|
2698
|
+
if (app.port !== void 0) {
|
|
2699
|
+
console.log(` \u2022 ${app.name} (port ${app.port})`);
|
|
2700
|
+
} else {
|
|
2701
|
+
console.log(` \u2022 ${app.name}`);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
if (fallbackApps.length > 0) {
|
|
2706
|
+
console.log(" - Fallback Applications:");
|
|
2707
|
+
const displayFallbackApps = fallbackApps.length > 5 ? [
|
|
2708
|
+
...fallbackApps.slice(0, 5),
|
|
2709
|
+
{ name: `... and ${fallbackApps.length - 5} more`, fallback: "" }
|
|
2710
|
+
] : fallbackApps;
|
|
2711
|
+
for (const app of displayFallbackApps) {
|
|
2712
|
+
if (app.fallback) {
|
|
2713
|
+
console.log(` \u2022 ${app.name} \u2192 ${app.fallback}`);
|
|
2714
|
+
} else {
|
|
2715
|
+
console.log(` \u2022 ${app.name}`);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
if (localApps.length === 0 && fallbackApps.length === 0) {
|
|
2720
|
+
console.log(" - No applications configured\n");
|
|
2721
|
+
}
|
|
2722
|
+
if (localApps.length > 0) {
|
|
2723
|
+
console.log(
|
|
2724
|
+
"\nRequests directly to the ports of these applications may be automatically\nredirected to this proxy. Set the MFE_DISABLE_LOCAL_PROXY_REWRITE=1\nenvironment variable to disable this behavior."
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
console.log(`
|
|
2728
|
+
${"\u2500".repeat(50)}
|
|
2729
|
+
`);
|
|
2730
|
+
}
|
|
2582
2731
|
};
|
|
2583
2732
|
function extractMfeFlagValue(path7) {
|
|
2584
2733
|
const host = "http://example.com";
|
|
@@ -2608,6 +2757,9 @@ function removeMfeFlagQuery(req) {
|
|
|
2608
2757
|
req.url = path7;
|
|
2609
2758
|
return { req, mfeFlagValue };
|
|
2610
2759
|
}
|
|
2760
|
+
function formatProxyTarget(target) {
|
|
2761
|
+
return `${target.originalApplication} (${target.isLocal ? "local" : "fallback"})`;
|
|
2762
|
+
}
|
|
2611
2763
|
|
|
2612
2764
|
// src/bin/port.ts
|
|
2613
2765
|
var import_node_process = require("process");
|
|
@@ -2617,19 +2769,19 @@ var import_node_path9 = __toESM(require("path"), 1);
|
|
|
2617
2769
|
var import_node_fs8 = __toESM(require("fs"), 1);
|
|
2618
2770
|
function mfePort(packageDir) {
|
|
2619
2771
|
const { name: appName, version } = getPackageJson(packageDir);
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2772
|
+
try {
|
|
2773
|
+
const result = loadConfig({ packageDir, appName });
|
|
2774
|
+
const { port } = result;
|
|
2775
|
+
return {
|
|
2776
|
+
name: appName,
|
|
2777
|
+
version,
|
|
2778
|
+
port
|
|
2779
|
+
};
|
|
2780
|
+
} catch (e) {
|
|
2781
|
+
throw new Error(`Unable to determine configured port for ${appName}`, {
|
|
2782
|
+
cause: e
|
|
2783
|
+
});
|
|
2626
2784
|
}
|
|
2627
|
-
const { port } = result;
|
|
2628
|
-
return {
|
|
2629
|
-
name: appName,
|
|
2630
|
-
version,
|
|
2631
|
-
port
|
|
2632
|
-
};
|
|
2633
2785
|
}
|
|
2634
2786
|
function getPackageJson(packageDir) {
|
|
2635
2787
|
const filePath = import_node_path9.default.join(packageDir, "package.json");
|
|
@@ -2639,14 +2791,9 @@ function loadConfig({
|
|
|
2639
2791
|
packageDir,
|
|
2640
2792
|
appName
|
|
2641
2793
|
}) {
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
directory: packageDir
|
|
2646
|
-
});
|
|
2647
|
-
} catch (e) {
|
|
2648
|
-
return void 0;
|
|
2649
|
-
}
|
|
2794
|
+
const config = MicrofrontendsServer.infer({
|
|
2795
|
+
directory: packageDir
|
|
2796
|
+
});
|
|
2650
2797
|
const app = config.config.getApplication(appName);
|
|
2651
2798
|
const port = app.development.local.port ?? (app.development.local.protocol === "https" ? 443 : 80);
|
|
2652
2799
|
return { port };
|
package/dist/config.cjs
CHANGED
|
@@ -232,7 +232,7 @@ var MicrofrontendConfigClient = class {
|
|
|
232
232
|
static fromEnv(config) {
|
|
233
233
|
if (!config) {
|
|
234
234
|
throw new Error(
|
|
235
|
-
"Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
|
|
235
|
+
"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"
|
|
236
236
|
);
|
|
237
237
|
}
|
|
238
238
|
return new MicrofrontendConfigClient(JSON.parse(config));
|
|
@@ -372,10 +372,11 @@ function validatePathExpression(path) {
|
|
|
372
372
|
}
|
|
373
373
|
if (token.pattern !== PATH_DEFAULT_PATTERN && // Allows (a|b|c) and ((?!a|b|c).*) regex
|
|
374
374
|
// Only limited regex is supported for now, due to performance considerations
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
// Allows all letters, numbers, and hyphens. Other characters must be escaped.
|
|
376
|
+
!/^(?<allowed>[\w-~]+(?:\|[^:|()]+)+)$|^\(\?!(?<disallowed>[\w-~]+(?:\|[^:|()]+)*)\)\.\*$/.test(
|
|
377
|
+
token.pattern.replace(/\\./g, "")
|
|
377
378
|
)) {
|
|
378
|
-
return `Path ${path} cannot use unsupported regular expression wildcard`;
|
|
379
|
+
return `Path ${path} cannot use unsupported regular expression wildcard. If the path includes special characters, they must be escaped with backslash (e.g. '\\(')`;
|
|
379
380
|
}
|
|
380
381
|
if (token.modifier && i !== tokens.length - 1) {
|
|
381
382
|
return `Modifier ${token.modifier} is not allowed on wildcard :${token.name} in ${path}. Modifiers are only allowed in the last path component`;
|