@vercel/microfrontends 0.18.0 → 0.19.1

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.
Files changed (47) hide show
  1. package/dist/bin/cli.cjs +151 -71
  2. package/dist/config.cjs +102 -56
  3. package/dist/config.cjs.map +1 -1
  4. package/dist/config.d.ts +4 -4
  5. package/dist/config.js +102 -56
  6. package/dist/config.js.map +1 -1
  7. package/dist/{index-24024799.d.ts → index-09b1ddf9.d.ts} +16 -19
  8. package/dist/{index-ef8657e6.d.ts → index-2f78c0ca.d.ts} +44 -7
  9. package/dist/microfrontends/server.cjs +147 -67
  10. package/dist/microfrontends/server.cjs.map +1 -1
  11. package/dist/microfrontends/server.d.ts +4 -4
  12. package/dist/microfrontends/server.js +147 -67
  13. package/dist/microfrontends/server.js.map +1 -1
  14. package/dist/microfrontends.cjs +103 -57
  15. package/dist/microfrontends.cjs.map +1 -1
  16. package/dist/microfrontends.d.ts +4 -4
  17. package/dist/microfrontends.js +103 -57
  18. package/dist/microfrontends.js.map +1 -1
  19. package/dist/next/config.cjs +166 -79
  20. package/dist/next/config.cjs.map +1 -1
  21. package/dist/next/config.js +166 -79
  22. package/dist/next/config.js.map +1 -1
  23. package/dist/next/endpoints.d.ts +2 -2
  24. package/dist/next/middleware.cjs +120 -75
  25. package/dist/next/middleware.cjs.map +1 -1
  26. package/dist/next/middleware.js +120 -75
  27. package/dist/next/middleware.js.map +1 -1
  28. package/dist/next/testing.cjs +109 -62
  29. package/dist/next/testing.cjs.map +1 -1
  30. package/dist/next/testing.d.ts +4 -4
  31. package/dist/next/testing.js +109 -62
  32. package/dist/next/testing.js.map +1 -1
  33. package/dist/overrides.d.ts +3 -3
  34. package/dist/schema.d.ts +1 -1
  35. package/dist/{types-089498fd.d.ts → types-4ef2bddb.d.ts} +1 -1
  36. package/dist/{types-9f161cec.d.ts → types-b6d38aea.d.ts} +1 -1
  37. package/dist/utils/mfe-port.cjs +148 -68
  38. package/dist/utils/mfe-port.cjs.map +1 -1
  39. package/dist/utils/mfe-port.js +148 -68
  40. package/dist/utils/mfe-port.js.map +1 -1
  41. package/dist/validation.cjs +43 -9
  42. package/dist/validation.cjs.map +1 -1
  43. package/dist/validation.d.ts +1 -1
  44. package/dist/validation.js +43 -9
  45. package/dist/validation.js.map +1 -1
  46. package/package.json +1 -1
  47. package/schema/schema.json +71 -40
@@ -233,7 +233,8 @@ var validateConfigPaths = (applicationConfigsById) => {
233
233
  if (isDefaultApp(app)) {
234
234
  continue;
235
235
  }
236
- for (const pathMatch of app.routing) {
236
+ const childApp = app;
237
+ for (const pathMatch of childApp.routing) {
237
238
  for (const path5 of pathMatch.paths) {
238
239
  const maybeError = validatePathExpression(path5);
239
240
  if (maybeError) {
@@ -253,33 +254,31 @@ var validateConfigPaths = (applicationConfigsById) => {
253
254
  }
254
255
  }
255
256
  const entries = Array.from(pathsByApplicationId.entries());
256
- entries.forEach(([path5, { applications: ids, matcher, applicationId }]) => {
257
+ for (const [path5, { applications: ids, matcher, applicationId }] of entries) {
257
258
  if (ids.length > 1) {
258
259
  errors.push(
259
260
  `Duplicate path "${path5}" for applications "${ids.join(", ")}"`
260
261
  );
261
262
  }
262
- entries.forEach(
263
- ([
264
- matchPath,
265
- { applications: matchIds, applicationId: matchApplicationId }
266
- ]) => {
267
- if (path5 === matchPath) {
268
- return;
269
- }
270
- if (applicationId === matchApplicationId) {
271
- return;
272
- }
273
- if (matcher.test(matchPath)) {
274
- const source = `"${path5}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
275
- const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
276
- errors.push(
277
- `Overlapping path detected between ${source} and ${destination}`
278
- );
279
- }
263
+ for (const [
264
+ matchPath,
265
+ { applications: matchIds, applicationId: matchApplicationId }
266
+ ] of entries) {
267
+ if (path5 === matchPath) {
268
+ continue;
280
269
  }
281
- );
282
- });
270
+ if (applicationId === matchApplicationId) {
271
+ continue;
272
+ }
273
+ if (matcher.test(matchPath)) {
274
+ const source = `"${path5}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
275
+ const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
276
+ errors.push(
277
+ `Overlapping path detected between ${source} and ${destination}`
278
+ );
279
+ }
280
+ }
281
+ }
283
282
  if (errors.length) {
284
283
  throw new MicrofrontendError(`Invalid paths: ${errors.join(", ")}`, {
285
284
  type: "config",
@@ -348,7 +347,7 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
348
347
  const numApplicationsWithoutRouting = numApplications - numApplicationsWithRouting;
349
348
  if (numApplicationsWithoutRouting === 0) {
350
349
  throw new MicrofrontendError(
351
- `No default application found. At least one application needs to be the default by omitting routing.`,
350
+ "No default application found. At least one application needs to be the default by omitting routing.",
352
351
  { type: "config", subtype: "no_default_application" }
353
352
  );
354
353
  }
@@ -394,47 +393,80 @@ function generatePortFromName({
394
393
  // src/config/microfrontends-config/isomorphic/host.ts
395
394
  var Host = class {
396
395
  constructor(hostConfig, options) {
397
- const { protocol = "https", host, port } = hostConfig;
398
- this.protocol = protocol;
399
- this.host = host;
400
- this.port = Host.getPort({ port, protocol: this.protocol });
396
+ if (typeof hostConfig === "string") {
397
+ ({
398
+ protocol: this.protocol,
399
+ host: this.host,
400
+ port: this.port
401
+ } = Host.parseUrl(hostConfig));
402
+ } else {
403
+ const { protocol = "https", host, port } = hostConfig;
404
+ this.protocol = protocol;
405
+ this.host = host;
406
+ this.port = port;
407
+ }
401
408
  this.local = options?.isLocal;
402
409
  }
403
- isLocal() {
404
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
405
- }
406
- static getPort({
407
- protocol,
408
- port
409
- }) {
410
- if (!port) {
411
- if (protocol === "http") {
412
- return 80;
413
- }
414
- return 443;
410
+ static parseUrl(url) {
411
+ let hostToParse = url;
412
+ if (!/^https?:\/\//.exec(hostToParse)) {
413
+ hostToParse = `https://${hostToParse}`;
414
+ }
415
+ const parsed = new URL(hostToParse);
416
+ if (!parsed.hostname) {
417
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
415
418
  }
416
- return port;
419
+ if (parsed.hash) {
420
+ throw new Error(
421
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
422
+ );
423
+ }
424
+ if (parsed.username || parsed.password) {
425
+ throw new Error(
426
+ Host.getMicrofrontendsError(
427
+ url,
428
+ "cannot have authentication credentials (username and/or password)"
429
+ )
430
+ );
431
+ }
432
+ if (parsed.pathname !== "/") {
433
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
434
+ }
435
+ if (parsed.search) {
436
+ throw new Error(
437
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
438
+ );
439
+ }
440
+ const protocol = parsed.protocol.slice(0, -1);
441
+ return {
442
+ protocol,
443
+ host: parsed.hostname,
444
+ port: parsed.port ? Number.parseInt(parsed.port) : void 0
445
+ };
446
+ }
447
+ static getMicrofrontendsError(url, message) {
448
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
417
449
  }
418
- isDefaultPort() {
419
- return this.port === Host.getPort({ protocol: this.protocol });
450
+ isLocal() {
451
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
420
452
  }
421
- toString(opts = {}) {
422
- const url = this.toUrl(opts);
453
+ toString() {
454
+ const url = this.toUrl();
423
455
  return url.toString().replace(/\/$/, "");
424
456
  }
425
- toUrl(opts = {}) {
426
- const { includeDefaultPort } = opts;
427
- const url = `${this.protocol}://${this.host}${this.isDefaultPort() && !includeDefaultPort ? "" : `:${this.port}`}`;
457
+ toUrl() {
458
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
428
459
  return new URL(url);
429
460
  }
430
461
  };
431
462
  var LocalHost = class extends Host {
432
463
  constructor({
433
464
  appName,
465
+ localPort,
434
466
  ...hostConfig
435
467
  }) {
436
468
  const host = hostConfig.host ?? "localhost";
437
- const port = hostConfig.port ?? generatePortFromName({ name: appName });
469
+ const port = localPort ?? hostConfig.port ?? generatePortFromName({ name: appName });
438
470
  const protocol = hostConfig.protocol ?? "http";
439
471
  super({ protocol, host, port });
440
472
  }
@@ -451,12 +483,17 @@ var Application = class {
451
483
  this.development = {
452
484
  local: new LocalHost({
453
485
  appName: name,
486
+ localPort: app.development?.localPort,
454
487
  ...app.development?.local
455
488
  }),
456
489
  fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0
457
490
  };
458
- this.production = app.production ? new Host(app.production) : void 0;
459
- this.vercel = app.vercel;
491
+ if (app.development?.fallback) {
492
+ this.fallback = new Host(app.development.fallback);
493
+ } else if (app.production) {
494
+ this.fallback = new Host(app.production);
495
+ }
496
+ this.projectId = app.projectId ?? app.vercel?.projectId;
460
497
  this.overrides = overrides?.environment ? {
461
498
  environment: new Host(overrides.environment)
462
499
  } : void 0;
@@ -484,7 +521,16 @@ var DefaultApplication = class extends Application {
484
521
  isDefault: true
485
522
  });
486
523
  this.default = true;
487
- this.production = new Host(app.production);
524
+ const fallbackHost = app.development?.fallback ?? app.production;
525
+ if (fallbackHost === void 0) {
526
+ throw new Error(
527
+ "`app.production` or `app.development.fallback` must be set in the default application in microfrontends.json."
528
+ );
529
+ }
530
+ this.fallback = new Host(fallbackHost);
531
+ if (app.production) {
532
+ this.production = new Host(app.production);
533
+ }
488
534
  }
489
535
  getAssetPrefix() {
490
536
  return "";
@@ -554,7 +600,7 @@ var MicrofrontendConfigIsomorphic = class {
554
600
  }
555
601
  if (isMainConfig(config) && !this.defaultApplication) {
556
602
  throw new MicrofrontendError(
557
- `Could not find default application in microfrontends configuration`,
603
+ "Could not find default application in microfrontends configuration",
558
604
  {
559
605
  type: "application",
560
606
  subtype: "not_found"
@@ -630,11 +676,11 @@ var MicrofrontendConfigIsomorphic = class {
630
676
  return app;
631
677
  }
632
678
  getApplicationByProjectId(projectId) {
633
- if (this.defaultApplication?.vercel?.projectId === projectId) {
679
+ if (this.defaultApplication?.projectId === projectId) {
634
680
  return this.defaultApplication;
635
681
  }
636
682
  return Object.values(this.childApplications).find(
637
- (app) => app.vercel?.projectId === projectId
683
+ (app) => app.projectId === projectId
638
684
  );
639
685
  }
640
686
  /**
@@ -644,7 +690,7 @@ var MicrofrontendConfigIsomorphic = class {
644
690
  getDefaultApplication() {
645
691
  if (!this.defaultApplication) {
646
692
  throw new MicrofrontendError(
647
- `Could not find default application in microfrontends configuration`,
693
+ "Could not find default application in microfrontends configuration",
648
694
  {
649
695
  type: "application",
650
696
  subtype: "not_found"
@@ -657,7 +703,7 @@ var MicrofrontendConfigIsomorphic = class {
657
703
  * Returns the configured port for the local proxy
658
704
  */
659
705
  getLocalProxyPort() {
660
- return this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
706
+ return this.config.options?.localProxyPort ?? this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
661
707
  }
662
708
  /**
663
709
  * Serializes the class back to the Schema type.
@@ -731,7 +777,7 @@ var MicrofrontendMainConfig = class extends MicrofrontendConfigIsomorphic {
731
777
  }
732
778
  if (!defaultApplication) {
733
779
  throw new MicrofrontendError(
734
- `Could not find default application in microfrontends configuration`,
780
+ "Could not find default application in microfrontends configuration",
735
781
  {
736
782
  type: "application",
737
783
  subtype: "not_found"
@@ -1031,11 +1077,21 @@ var schema_default = {
1031
1077
  properties: {
1032
1078
  vercel: {
1033
1079
  $ref: "#/definitions/VercelOptions",
1034
- description: "Micro-Frontends wide options for Vercel."
1080
+ description: "Microfrontends wide options for Vercel.",
1081
+ deprecated: "This is being replaced by the `disableOverrides` field below."
1082
+ },
1083
+ disableOverrides: {
1084
+ type: "boolean",
1085
+ description: "If you want to disable the overrides for the site. For example, if you are managing rewrites between applications externally, you may wish to disable the overrides on the toolbar as they will have no effect."
1035
1086
  },
1036
1087
  localProxy: {
1037
1088
  $ref: "#/definitions/LocalProxyOptions",
1038
- description: "Options for local proxy."
1089
+ description: "Options for local proxy.",
1090
+ deprecated: "This is being replaced by the `localProxyPort` field below."
1091
+ },
1092
+ localProxyPort: {
1093
+ type: "number",
1094
+ description: "The port number used by the local proxy server.\n\nThe default is `3024`."
1039
1095
  }
1040
1096
  },
1041
1097
  additionalProperties: false
@@ -1083,13 +1139,19 @@ var schema_default = {
1083
1139
  type: "object",
1084
1140
  properties: {
1085
1141
  vercel: {
1086
- $ref: "#/definitions/Vercel"
1142
+ $ref: "#/definitions/Vercel",
1143
+ deprecated: "This is being replaced by the `projectId` field below."
1144
+ },
1145
+ projectId: {
1146
+ type: "string",
1147
+ description: "Vercel project ID"
1087
1148
  },
1088
1149
  development: {
1089
1150
  $ref: "#/definitions/Development"
1090
1151
  },
1091
1152
  production: {
1092
- $ref: "#/definitions/HostConfig"
1153
+ $ref: "#/definitions/HostConfig",
1154
+ deprecated: "This is a duplicate of the `development.fallback` field and this will be removed soon."
1093
1155
  }
1094
1156
  },
1095
1157
  required: ["production"],
@@ -1110,11 +1172,23 @@ var schema_default = {
1110
1172
  type: "object",
1111
1173
  properties: {
1112
1174
  local: {
1113
- $ref: "#/definitions/LocalHostConfig"
1175
+ $ref: "#/definitions/LocalHostConfig",
1176
+ deprecated: "This is being replaced by the `localPort` field below."
1177
+ },
1178
+ localPort: {
1179
+ type: "number",
1180
+ description: "The local port number that this application runs on when it is running locally. Common values include `80` for HTTP and `443` for HTTPS."
1114
1181
  },
1115
1182
  fallback: {
1116
- $ref: "#/definitions/HostConfig",
1117
- description: "Fallback for local development, could be a host config that points to any environment. If this is not provided, or the application is not running - requests to the application in local development will error."
1183
+ anyOf: [
1184
+ {
1185
+ $ref: "#/definitions/HostConfig"
1186
+ },
1187
+ {
1188
+ type: "string"
1189
+ }
1190
+ ],
1191
+ description: "Fallback for local development, could be a host config that points to any environment. If this is not provided, or the application is not running - requests to the application in local development will error.\n\nIf passing a string, include the protocol (optional), host (required) and port (optional). For example: `https://this.ismyhost:8080`. If omitted, the protocol defaults to HTTPS. If omitted, the port defaults to `80` for HTTP and `443` for HTTPS."
1118
1192
  },
1119
1193
  task: {
1120
1194
  type: "string",
@@ -1166,7 +1240,12 @@ var schema_default = {
1166
1240
  type: "object",
1167
1241
  properties: {
1168
1242
  vercel: {
1169
- $ref: "#/definitions/Vercel"
1243
+ $ref: "#/definitions/Vercel",
1244
+ deprecated: "This is being replaced by the `projectId` field below."
1245
+ },
1246
+ projectId: {
1247
+ type: "string",
1248
+ description: "Vercel project ID"
1170
1249
  },
1171
1250
  development: {
1172
1251
  $ref: "#/definitions/Development"
@@ -1176,7 +1255,8 @@ var schema_default = {
1176
1255
  description: "Groups of path expressions that are routed to this application."
1177
1256
  },
1178
1257
  production: {
1179
- $ref: "#/definitions/HostConfig"
1258
+ $ref: "#/definitions/HostConfig",
1259
+ deprecated: "This is a duplicate of the `development.fallback` field and this will be removed soon."
1180
1260
  }
1181
1261
  },
1182
1262
  required: ["routing"],
@@ -1472,7 +1552,7 @@ var MicrofrontendsServer = class extends Microfrontends {
1472
1552
  const [defaultApplication] = Object.entries(validatedConfig.applications).filter(([, app]) => isDefaultApp(app)).map(([name]) => name);
1473
1553
  if (!defaultApplication) {
1474
1554
  throw new MicrofrontendError(
1475
- `No default application found. At least one application needs to be the default by omitting routing.`,
1555
+ "No default application found. At least one application needs to be the default by omitting routing.",
1476
1556
  { type: "config", subtype: "no_default_application" }
1477
1557
  );
1478
1558
  }
@@ -1601,12 +1681,10 @@ function getDomainFromEnvironment({
1601
1681
  if (mfeProjects.length === 0) {
1602
1682
  throw new Error("Missing related microfrontends project information");
1603
1683
  }
1604
- if (!app.vercel?.projectId) {
1684
+ if (!app.projectId) {
1605
1685
  throw new Error(`Missing applications[${app.name}].vercel.projectId`);
1606
1686
  }
1607
- const vercelProject = mfeProjects.find(
1608
- (p) => p.project.id === app.vercel?.projectId
1609
- );
1687
+ const vercelProject = mfeProjects.find((p) => p.project.id === app.projectId);
1610
1688
  if (!vercelProject) {
1611
1689
  throw new Error(
1612
1690
  `Missing related microfrontends project information for application "${app.name}"`
@@ -1640,9 +1718,11 @@ function getCurrentEnvironment() {
1640
1718
  const isProduction2 = process.env.VERCEL_ENV === "production";
1641
1719
  if (isDevelopment) {
1642
1720
  return { group: "development" };
1643
- } else if (isProduction2) {
1721
+ }
1722
+ if (isProduction2) {
1644
1723
  return { group: "production" };
1645
- } else if (isPreview) {
1724
+ }
1725
+ if (isPreview) {
1646
1726
  return { group: "preview" };
1647
1727
  }
1648
1728
  return { group: "custom", name: process.env.VERCEL_ENV };
@@ -1653,10 +1733,10 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
1653
1733
  return app.overrides.environment.toString();
1654
1734
  }
1655
1735
  const { group } = getCurrentEnvironment();
1656
- const productionHost = config.getDefaultApplication().production.toString();
1736
+ const fallbackHost = config.getDefaultApplication().fallback.toString();
1657
1737
  switch (group) {
1658
1738
  case "development": {
1659
- const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : productionHost;
1739
+ const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : fallbackHost;
1660
1740
  debugDomains(appName, "development", domain);
1661
1741
  return domain;
1662
1742
  }
@@ -1667,10 +1747,9 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
1667
1747
  return getDomainFromEnvironment({ app, target: "production" });
1668
1748
  }
1669
1749
  case "custom":
1670
- console.warn(
1671
- `Custom environments are not supported in getDomainForCurrentEnvironment`
1750
+ throw new Error(
1751
+ "Custom environments are not supported in getDomainForCurrentEnvironment"
1672
1752
  );
1673
- return productionHost;
1674
1753
  }
1675
1754
  }
1676
1755
 
@@ -1857,7 +1936,15 @@ function transform5(args) {
1857
1936
  // this deployments host
1858
1937
  ...process.env.VERCEL_URL ? [formatDomainForServerAction(process.env.VERCEL_URL)] : [],
1859
1938
  // default application host
1860
- formatDomainForServerAction(defaultApplication.production.toString()),
1939
+ ...process.env.VERCEL_MICROFRONTENDS_PROJECTS ? [
1940
+ formatDomainForServerAction(
1941
+ getDomainFromEnvironment({
1942
+ app: defaultApplication,
1943
+ target: "production"
1944
+ })
1945
+ )
1946
+ ] : [],
1947
+ formatDomainForServerAction(defaultApplication.fallback.toString()),
1861
1948
  // environment specific microfrontend hosts
1862
1949
  ...appsToAllow.flatMap((a) => [
1863
1950
  formatDomainForServerAction(