@vercel/microfrontends 2.3.6 → 2.4.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 +6 -0
- package/README.md +6 -0
- package/dist/bin/cli.cjs +66 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @vercel/microfrontends
|
|
2
2
|
|
|
3
|
+
## 2.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7a743be: Add an optional configured origin to the local development proxy via `--origin <origin>` or the `MFE_LOCAL_PROXY_ORIGIN` environment variable (for example `https://web.localhost`). When set, the proxy forwards that origin to your applications as `x-forwarded-proto`/`x-forwarded-host`/`x-forwarded-port` and rewrites fallback redirects onto it, so apps served behind a custom local hostname or a TLS terminator see the URL the browser actually used. When no origin is configured, inbound forwarded headers pass through unchanged.
|
|
8
|
+
|
|
3
9
|
## 2.3.6
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -21,6 +21,12 @@ Follow the [quickstart](https://vercel.com/docs/microfrontends/quickstart) docum
|
|
|
21
21
|
|
|
22
22
|
You can use the frameworks and microfrontends frameworks of your choice. `@vercel/microfrontends` supports Next.js, SvelteKit, React Router, Vite, and React.
|
|
23
23
|
|
|
24
|
+
## Local proxy origin
|
|
25
|
+
|
|
26
|
+
The local development proxy can preserve the origin used to reach it. Set it explicitly with `microfrontends proxy --origin <origin>` or `MFE_LOCAL_PROXY_ORIGIN`.
|
|
27
|
+
|
|
28
|
+
The origin must be an absolute `http` or `https` origin, such as `https://myapp.localhost`, with no path, query string, hash, username, or password. The proxy uses this origin anywhere it needs to preserve the URL clients used to reach the proxy.
|
|
29
|
+
|
|
24
30
|
## Need help?
|
|
25
31
|
|
|
26
32
|
Reach out for help in the [Vercel Community](https://community.vercel.com).
|
package/dist/bin/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var import_commander = require("commander");
|
|
|
30
30
|
// package.json
|
|
31
31
|
var package_default = {
|
|
32
32
|
name: "@vercel/microfrontends",
|
|
33
|
-
version: "2.
|
|
33
|
+
version: "2.4.0",
|
|
34
34
|
private: false,
|
|
35
35
|
description: "Defines configuration and utilities for microfrontends development",
|
|
36
36
|
keywords: [
|
|
@@ -2371,19 +2371,56 @@ var MFE_LOCAL_PROXY_HEADER = "x-vercel-mfe-local-proxy-origin";
|
|
|
2371
2371
|
var MFE_FLAG_VALUE = "vercel-mfe-flag-value";
|
|
2372
2372
|
var MFE_FLAG_VALUE_HEADER = `x-${MFE_FLAG_VALUE}`;
|
|
2373
2373
|
var VERCEL_TOOLBAR_PROXY_BASE_PATH = "/.well-known/vercel-toolbar";
|
|
2374
|
+
var ORIGIN_ENV = "MFE_LOCAL_PROXY_ORIGIN";
|
|
2374
2375
|
function normalizeRepeatedSlashesInPath(url) {
|
|
2375
2376
|
const searchStart = url.indexOf("?");
|
|
2376
2377
|
const path7 = searchStart === -1 ? url : url.slice(0, searchStart);
|
|
2377
2378
|
const search = searchStart === -1 ? "" : url.slice(searchStart);
|
|
2378
2379
|
return `${path7.replaceAll(/\/[\\/]+/g, "/")}${search}`;
|
|
2379
2380
|
}
|
|
2380
|
-
function rewriteRedirectLocation(locationHeader,
|
|
2381
|
-
const redirectUrl = new import_node_url.URL(locationHeader,
|
|
2382
|
-
const
|
|
2383
|
-
redirectUrl.protocol =
|
|
2384
|
-
redirectUrl.host =
|
|
2381
|
+
function rewriteRedirectLocation(locationHeader, origin) {
|
|
2382
|
+
const redirectUrl = new import_node_url.URL(locationHeader, origin);
|
|
2383
|
+
const originUrl = new import_node_url.URL(origin);
|
|
2384
|
+
redirectUrl.protocol = originUrl.protocol;
|
|
2385
|
+
redirectUrl.host = originUrl.host;
|
|
2385
2386
|
return redirectUrl.toString();
|
|
2386
2387
|
}
|
|
2388
|
+
function parseConfiguredOrigin(origin) {
|
|
2389
|
+
if (!origin) {
|
|
2390
|
+
return void 0;
|
|
2391
|
+
}
|
|
2392
|
+
const parsed = parseBrowserOrigin(origin);
|
|
2393
|
+
if (!parsed) {
|
|
2394
|
+
throw new Error(
|
|
2395
|
+
`Invalid origin "${origin}". Expected an absolute http(s) origin with no path, search, hash, username, or password.`
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
return parsed.origin;
|
|
2399
|
+
}
|
|
2400
|
+
function parseBrowserOrigin(origin) {
|
|
2401
|
+
try {
|
|
2402
|
+
const url = new import_node_url.URL(origin);
|
|
2403
|
+
const hasOnlyOrigin = url.protocol === "http:" || url.protocol === "https:" ? url.username === "" && url.password === "" && url.pathname === "/" && url.search === "" && url.hash === "" : false;
|
|
2404
|
+
return hasOnlyOrigin ? url : void 0;
|
|
2405
|
+
} catch {
|
|
2406
|
+
return void 0;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
function forwardedHeadersForOrigin(origin) {
|
|
2410
|
+
const url = new import_node_url.URL(origin);
|
|
2411
|
+
return {
|
|
2412
|
+
"x-forwarded-proto": url.protocol.slice(0, -1),
|
|
2413
|
+
"x-forwarded-host": url.host,
|
|
2414
|
+
"x-forwarded-port": url.port || (url.protocol === "https:" ? "443" : "80")
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
function stripLocalProxyHeader(headers) {
|
|
2418
|
+
return Object.fromEntries(
|
|
2419
|
+
Object.entries(headers).filter(
|
|
2420
|
+
([key]) => key.toLowerCase() !== MFE_LOCAL_PROXY_HEADER
|
|
2421
|
+
)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2387
2424
|
var ProxyRequestRouter = class {
|
|
2388
2425
|
constructor(config, {
|
|
2389
2426
|
localApps
|
|
@@ -2750,11 +2787,13 @@ var LocalProxy = class {
|
|
|
2750
2787
|
constructor(config, {
|
|
2751
2788
|
localApps,
|
|
2752
2789
|
proxyPort,
|
|
2753
|
-
configFilePath
|
|
2790
|
+
configFilePath,
|
|
2791
|
+
origin
|
|
2754
2792
|
}) {
|
|
2755
2793
|
this.router = new ProxyRequestRouter(config, { localApps });
|
|
2756
2794
|
this.proxyPort = proxyPort ?? this.router.config.getLocalProxyPort();
|
|
2757
2795
|
this.configFilePath = configFilePath;
|
|
2796
|
+
this.configuredOrigin = parseConfiguredOrigin(origin) ?? parseConfiguredOrigin(process.env[ORIGIN_ENV]);
|
|
2758
2797
|
this.proxy = import_http_proxy.default.createProxyServer({ secure: true });
|
|
2759
2798
|
this.proxy.on("error", (err, req, res) => {
|
|
2760
2799
|
if (res instanceof http.ServerResponse) {
|
|
@@ -2780,7 +2819,8 @@ var LocalProxy = class {
|
|
|
2780
2819
|
}
|
|
2781
2820
|
static fromFile(filePath, {
|
|
2782
2821
|
localApps,
|
|
2783
|
-
proxyPort
|
|
2822
|
+
proxyPort,
|
|
2823
|
+
origin
|
|
2784
2824
|
}) {
|
|
2785
2825
|
let microfrontends;
|
|
2786
2826
|
if (filePath) {
|
|
@@ -2794,6 +2834,7 @@ var LocalProxy = class {
|
|
|
2794
2834
|
return new LocalProxy(microfrontends.config, {
|
|
2795
2835
|
localApps,
|
|
2796
2836
|
proxyPort,
|
|
2837
|
+
origin,
|
|
2797
2838
|
configFilePath: filePath
|
|
2798
2839
|
});
|
|
2799
2840
|
}
|
|
@@ -2826,7 +2867,7 @@ var LocalProxy = class {
|
|
|
2826
2867
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
2827
2868
|
const target = this.router.getTarget(req);
|
|
2828
2869
|
try {
|
|
2829
|
-
const headers = {};
|
|
2870
|
+
const headers = this.configuredOrigin ? forwardedHeadersForOrigin(this.getOrigin()) : {};
|
|
2830
2871
|
headers[MFE_LOCAL_PROXY_HEADER] = "1";
|
|
2831
2872
|
this.proxy.ws(req, socket, head, {
|
|
2832
2873
|
target: target.url,
|
|
@@ -2870,6 +2911,8 @@ var LocalProxy = class {
|
|
|
2870
2911
|
}
|
|
2871
2912
|
const target = this.router.getTarget(req);
|
|
2872
2913
|
const { req: strippedReq, mfeFlagValue } = removeMfeFlagQuery(req);
|
|
2914
|
+
const origin = this.getOrigin();
|
|
2915
|
+
const forwardedHeaders = this.configuredOrigin ? forwardedHeadersForOrigin(this.configuredOrigin) : {};
|
|
2873
2916
|
if (target.protocol === "https") {
|
|
2874
2917
|
const { hostname, port, path: path7 } = target;
|
|
2875
2918
|
const app = this.router.config.getApplication(target.application);
|
|
@@ -2880,13 +2923,15 @@ var LocalProxy = class {
|
|
|
2880
2923
|
const overrideCookieName = getAppEnvOverrideCookieName(
|
|
2881
2924
|
target.application
|
|
2882
2925
|
);
|
|
2926
|
+
const fallbackHeaders = stripLocalProxyHeader(req.headers);
|
|
2883
2927
|
const requestOptions = {
|
|
2884
2928
|
hostname,
|
|
2885
2929
|
path: path7,
|
|
2886
2930
|
method: req.method,
|
|
2887
2931
|
headers: {
|
|
2888
|
-
...
|
|
2932
|
+
...fallbackHeaders,
|
|
2889
2933
|
host: hostname,
|
|
2934
|
+
...forwardedHeaders,
|
|
2890
2935
|
cookie: Object.entries(cookies).reduce((acc, [name, value]) => {
|
|
2891
2936
|
if (value && // strip the override cookie if present, as this causes an auth redirect. The
|
|
2892
2937
|
// override is handled by the local proxy.
|
|
@@ -2900,7 +2945,6 @@ var LocalProxy = class {
|
|
|
2900
2945
|
},
|
|
2901
2946
|
port
|
|
2902
2947
|
};
|
|
2903
|
-
const localhost = `http://localhost:${this.proxyPort}`;
|
|
2904
2948
|
const proxyReq = https.request(requestOptions, (realRes) => {
|
|
2905
2949
|
if (realRes.statusCode === 401 && realRes.headers["set-cookie"]?.find(
|
|
2906
2950
|
(cookie) => cookie.startsWith("_vercel_sso_nonce=")
|
|
@@ -2922,7 +2966,7 @@ var LocalProxy = class {
|
|
|
2922
2966
|
if (locationHeader) {
|
|
2923
2967
|
realRes.headers.location = rewriteRedirectLocation(
|
|
2924
2968
|
locationHeader,
|
|
2925
|
-
|
|
2969
|
+
origin
|
|
2926
2970
|
);
|
|
2927
2971
|
}
|
|
2928
2972
|
}
|
|
@@ -2957,7 +3001,7 @@ var LocalProxy = class {
|
|
|
2957
3001
|
proxyReq.destroy();
|
|
2958
3002
|
});
|
|
2959
3003
|
} else {
|
|
2960
|
-
const headers = {};
|
|
3004
|
+
const headers = { ...forwardedHeaders };
|
|
2961
3005
|
headers[MFE_LOCAL_PROXY_HEADER] = "1";
|
|
2962
3006
|
if (mfeFlagValue !== void 0) {
|
|
2963
3007
|
headers[MFE_FLAG_VALUE_HEADER] = mfeFlagValue.toString();
|
|
@@ -2968,6 +3012,9 @@ var LocalProxy = class {
|
|
|
2968
3012
|
});
|
|
2969
3013
|
}
|
|
2970
3014
|
}
|
|
3015
|
+
getOrigin() {
|
|
3016
|
+
return this.configuredOrigin ?? `http://localhost:${this.proxyPort}`;
|
|
3017
|
+
}
|
|
2971
3018
|
// Handles requests that return data from the local proxy itself.
|
|
2972
3019
|
// Returns true if the request was handled, false otherwise.
|
|
2973
3020
|
handleProxyInfoRequest(path7, res) {
|
|
@@ -3026,6 +3073,9 @@ var LocalProxy = class {
|
|
|
3026
3073
|
logger.info(`
|
|
3027
3074
|
\u25B2 Microfrontends Proxy (${package_default.version}) Started`);
|
|
3028
3075
|
logger.info(` - Proxy URL: http://localhost:${this.proxyPort}`);
|
|
3076
|
+
if (this.configuredOrigin) {
|
|
3077
|
+
logger.info(` - Browser origin: ${this.configuredOrigin}`);
|
|
3078
|
+
}
|
|
3029
3079
|
if (this.configFilePath) {
|
|
3030
3080
|
logger.info(` - Config: ${this.configFilePath}`);
|
|
3031
3081
|
}
|
|
@@ -3188,7 +3238,7 @@ function main() {
|
|
|
3188
3238
|
);
|
|
3189
3239
|
}
|
|
3190
3240
|
return parsedValue;
|
|
3191
|
-
}).action((filePath, options) => {
|
|
3241
|
+
}).option("--origin <origin>", "Origin browsers use to reach the proxy").action((filePath, options) => {
|
|
3192
3242
|
if (options.names && options.localApps.length) {
|
|
3193
3243
|
throw new Error(
|
|
3194
3244
|
"Both --names and --local-apps are set. --names is deprecated and has been replaced with --local-apps, which functions exactly the same. Please only set --local-apps."
|
|
@@ -3196,7 +3246,8 @@ function main() {
|
|
|
3196
3246
|
}
|
|
3197
3247
|
const localProxy = LocalProxy.fromFile(filePath, {
|
|
3198
3248
|
localApps: options.names ?? options.localApps,
|
|
3199
|
-
proxyPort: options.port
|
|
3249
|
+
proxyPort: options.port,
|
|
3250
|
+
origin: options.origin
|
|
3200
3251
|
});
|
|
3201
3252
|
localProxy.startServer();
|
|
3202
3253
|
});
|