@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
@@ -267,7 +267,8 @@ var validateConfigPaths = (applicationConfigsById) => {
267
267
  if (isDefaultApp(app)) {
268
268
  continue;
269
269
  }
270
- for (const pathMatch of app.routing) {
270
+ const childApp = app;
271
+ for (const pathMatch of childApp.routing) {
271
272
  for (const path5 of pathMatch.paths) {
272
273
  const maybeError = validatePathExpression(path5);
273
274
  if (maybeError) {
@@ -287,33 +288,31 @@ var validateConfigPaths = (applicationConfigsById) => {
287
288
  }
288
289
  }
289
290
  const entries = Array.from(pathsByApplicationId.entries());
290
- entries.forEach(([path5, { applications: ids, matcher, applicationId }]) => {
291
+ for (const [path5, { applications: ids, matcher, applicationId }] of entries) {
291
292
  if (ids.length > 1) {
292
293
  errors.push(
293
294
  `Duplicate path "${path5}" for applications "${ids.join(", ")}"`
294
295
  );
295
296
  }
296
- entries.forEach(
297
- ([
298
- matchPath,
299
- { applications: matchIds, applicationId: matchApplicationId }
300
- ]) => {
301
- if (path5 === matchPath) {
302
- return;
303
- }
304
- if (applicationId === matchApplicationId) {
305
- return;
306
- }
307
- if (matcher.test(matchPath)) {
308
- const source = `"${path5}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
309
- const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
310
- errors.push(
311
- `Overlapping path detected between ${source} and ${destination}`
312
- );
313
- }
297
+ for (const [
298
+ matchPath,
299
+ { applications: matchIds, applicationId: matchApplicationId }
300
+ ] of entries) {
301
+ if (path5 === matchPath) {
302
+ continue;
314
303
  }
315
- );
316
- });
304
+ if (applicationId === matchApplicationId) {
305
+ continue;
306
+ }
307
+ if (matcher.test(matchPath)) {
308
+ const source = `"${path5}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
309
+ const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
310
+ errors.push(
311
+ `Overlapping path detected between ${source} and ${destination}`
312
+ );
313
+ }
314
+ }
315
+ }
317
316
  if (errors.length) {
318
317
  throw new MicrofrontendError(`Invalid paths: ${errors.join(", ")}`, {
319
318
  type: "config",
@@ -382,7 +381,7 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
382
381
  const numApplicationsWithoutRouting = numApplications - numApplicationsWithRouting;
383
382
  if (numApplicationsWithoutRouting === 0) {
384
383
  throw new MicrofrontendError(
385
- `No default application found. At least one application needs to be the default by omitting routing.`,
384
+ "No default application found. At least one application needs to be the default by omitting routing.",
386
385
  { type: "config", subtype: "no_default_application" }
387
386
  );
388
387
  }
@@ -428,47 +427,80 @@ function generatePortFromName({
428
427
  // src/config/microfrontends-config/isomorphic/host.ts
429
428
  var Host = class {
430
429
  constructor(hostConfig, options) {
431
- const { protocol = "https", host, port } = hostConfig;
432
- this.protocol = protocol;
433
- this.host = host;
434
- this.port = Host.getPort({ port, protocol: this.protocol });
430
+ if (typeof hostConfig === "string") {
431
+ ({
432
+ protocol: this.protocol,
433
+ host: this.host,
434
+ port: this.port
435
+ } = Host.parseUrl(hostConfig));
436
+ } else {
437
+ const { protocol = "https", host, port } = hostConfig;
438
+ this.protocol = protocol;
439
+ this.host = host;
440
+ this.port = port;
441
+ }
435
442
  this.local = options?.isLocal;
436
443
  }
437
- isLocal() {
438
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
439
- }
440
- static getPort({
441
- protocol,
442
- port
443
- }) {
444
- if (!port) {
445
- if (protocol === "http") {
446
- return 80;
447
- }
448
- return 443;
444
+ static parseUrl(url) {
445
+ let hostToParse = url;
446
+ if (!/^https?:\/\//.exec(hostToParse)) {
447
+ hostToParse = `https://${hostToParse}`;
448
+ }
449
+ const parsed = new URL(hostToParse);
450
+ if (!parsed.hostname) {
451
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
449
452
  }
450
- return port;
453
+ if (parsed.hash) {
454
+ throw new Error(
455
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
456
+ );
457
+ }
458
+ if (parsed.username || parsed.password) {
459
+ throw new Error(
460
+ Host.getMicrofrontendsError(
461
+ url,
462
+ "cannot have authentication credentials (username and/or password)"
463
+ )
464
+ );
465
+ }
466
+ if (parsed.pathname !== "/") {
467
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
468
+ }
469
+ if (parsed.search) {
470
+ throw new Error(
471
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
472
+ );
473
+ }
474
+ const protocol = parsed.protocol.slice(0, -1);
475
+ return {
476
+ protocol,
477
+ host: parsed.hostname,
478
+ port: parsed.port ? Number.parseInt(parsed.port) : void 0
479
+ };
480
+ }
481
+ static getMicrofrontendsError(url, message) {
482
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
451
483
  }
452
- isDefaultPort() {
453
- return this.port === Host.getPort({ protocol: this.protocol });
484
+ isLocal() {
485
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
454
486
  }
455
- toString(opts = {}) {
456
- const url = this.toUrl(opts);
487
+ toString() {
488
+ const url = this.toUrl();
457
489
  return url.toString().replace(/\/$/, "");
458
490
  }
459
- toUrl(opts = {}) {
460
- const { includeDefaultPort } = opts;
461
- const url = `${this.protocol}://${this.host}${this.isDefaultPort() && !includeDefaultPort ? "" : `:${this.port}`}`;
491
+ toUrl() {
492
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
462
493
  return new URL(url);
463
494
  }
464
495
  };
465
496
  var LocalHost = class extends Host {
466
497
  constructor({
467
498
  appName,
499
+ localPort,
468
500
  ...hostConfig
469
501
  }) {
470
502
  const host = hostConfig.host ?? "localhost";
471
- const port = hostConfig.port ?? generatePortFromName({ name: appName });
503
+ const port = localPort ?? hostConfig.port ?? generatePortFromName({ name: appName });
472
504
  const protocol = hostConfig.protocol ?? "http";
473
505
  super({ protocol, host, port });
474
506
  }
@@ -485,12 +517,17 @@ var Application = class {
485
517
  this.development = {
486
518
  local: new LocalHost({
487
519
  appName: name,
520
+ localPort: app.development?.localPort,
488
521
  ...app.development?.local
489
522
  }),
490
523
  fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0
491
524
  };
492
- this.production = app.production ? new Host(app.production) : void 0;
493
- this.vercel = app.vercel;
525
+ if (app.development?.fallback) {
526
+ this.fallback = new Host(app.development.fallback);
527
+ } else if (app.production) {
528
+ this.fallback = new Host(app.production);
529
+ }
530
+ this.projectId = app.projectId ?? app.vercel?.projectId;
494
531
  this.overrides = overrides?.environment ? {
495
532
  environment: new Host(overrides.environment)
496
533
  } : void 0;
@@ -518,7 +555,16 @@ var DefaultApplication = class extends Application {
518
555
  isDefault: true
519
556
  });
520
557
  this.default = true;
521
- this.production = new Host(app.production);
558
+ const fallbackHost = app.development?.fallback ?? app.production;
559
+ if (fallbackHost === void 0) {
560
+ throw new Error(
561
+ "`app.production` or `app.development.fallback` must be set in the default application in microfrontends.json."
562
+ );
563
+ }
564
+ this.fallback = new Host(fallbackHost);
565
+ if (app.production) {
566
+ this.production = new Host(app.production);
567
+ }
522
568
  }
523
569
  getAssetPrefix() {
524
570
  return "";
@@ -588,7 +634,7 @@ var MicrofrontendConfigIsomorphic = class {
588
634
  }
589
635
  if (isMainConfig(config) && !this.defaultApplication) {
590
636
  throw new MicrofrontendError(
591
- `Could not find default application in microfrontends configuration`,
637
+ "Could not find default application in microfrontends configuration",
592
638
  {
593
639
  type: "application",
594
640
  subtype: "not_found"
@@ -664,11 +710,11 @@ var MicrofrontendConfigIsomorphic = class {
664
710
  return app;
665
711
  }
666
712
  getApplicationByProjectId(projectId) {
667
- if (this.defaultApplication?.vercel?.projectId === projectId) {
713
+ if (this.defaultApplication?.projectId === projectId) {
668
714
  return this.defaultApplication;
669
715
  }
670
716
  return Object.values(this.childApplications).find(
671
- (app) => app.vercel?.projectId === projectId
717
+ (app) => app.projectId === projectId
672
718
  );
673
719
  }
674
720
  /**
@@ -678,7 +724,7 @@ var MicrofrontendConfigIsomorphic = class {
678
724
  getDefaultApplication() {
679
725
  if (!this.defaultApplication) {
680
726
  throw new MicrofrontendError(
681
- `Could not find default application in microfrontends configuration`,
727
+ "Could not find default application in microfrontends configuration",
682
728
  {
683
729
  type: "application",
684
730
  subtype: "not_found"
@@ -691,7 +737,7 @@ var MicrofrontendConfigIsomorphic = class {
691
737
  * Returns the configured port for the local proxy
692
738
  */
693
739
  getLocalProxyPort() {
694
- return this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
740
+ return this.config.options?.localProxyPort ?? this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
695
741
  }
696
742
  /**
697
743
  * Serializes the class back to the Schema type.
@@ -765,7 +811,7 @@ var MicrofrontendMainConfig = class extends MicrofrontendConfigIsomorphic {
765
811
  }
766
812
  if (!defaultApplication) {
767
813
  throw new MicrofrontendError(
768
- `Could not find default application in microfrontends configuration`,
814
+ "Could not find default application in microfrontends configuration",
769
815
  {
770
816
  type: "application",
771
817
  subtype: "not_found"
@@ -1065,11 +1111,21 @@ var schema_default = {
1065
1111
  properties: {
1066
1112
  vercel: {
1067
1113
  $ref: "#/definitions/VercelOptions",
1068
- description: "Micro-Frontends wide options for Vercel."
1114
+ description: "Microfrontends wide options for Vercel.",
1115
+ deprecated: "This is being replaced by the `disableOverrides` field below."
1116
+ },
1117
+ disableOverrides: {
1118
+ type: "boolean",
1119
+ 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."
1069
1120
  },
1070
1121
  localProxy: {
1071
1122
  $ref: "#/definitions/LocalProxyOptions",
1072
- description: "Options for local proxy."
1123
+ description: "Options for local proxy.",
1124
+ deprecated: "This is being replaced by the `localProxyPort` field below."
1125
+ },
1126
+ localProxyPort: {
1127
+ type: "number",
1128
+ description: "The port number used by the local proxy server.\n\nThe default is `3024`."
1073
1129
  }
1074
1130
  },
1075
1131
  additionalProperties: false
@@ -1117,13 +1173,19 @@ var schema_default = {
1117
1173
  type: "object",
1118
1174
  properties: {
1119
1175
  vercel: {
1120
- $ref: "#/definitions/Vercel"
1176
+ $ref: "#/definitions/Vercel",
1177
+ deprecated: "This is being replaced by the `projectId` field below."
1178
+ },
1179
+ projectId: {
1180
+ type: "string",
1181
+ description: "Vercel project ID"
1121
1182
  },
1122
1183
  development: {
1123
1184
  $ref: "#/definitions/Development"
1124
1185
  },
1125
1186
  production: {
1126
- $ref: "#/definitions/HostConfig"
1187
+ $ref: "#/definitions/HostConfig",
1188
+ deprecated: "This is a duplicate of the `development.fallback` field and this will be removed soon."
1127
1189
  }
1128
1190
  },
1129
1191
  required: ["production"],
@@ -1144,11 +1206,23 @@ var schema_default = {
1144
1206
  type: "object",
1145
1207
  properties: {
1146
1208
  local: {
1147
- $ref: "#/definitions/LocalHostConfig"
1209
+ $ref: "#/definitions/LocalHostConfig",
1210
+ deprecated: "This is being replaced by the `localPort` field below."
1211
+ },
1212
+ localPort: {
1213
+ type: "number",
1214
+ 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."
1148
1215
  },
1149
1216
  fallback: {
1150
- $ref: "#/definitions/HostConfig",
1151
- 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."
1217
+ anyOf: [
1218
+ {
1219
+ $ref: "#/definitions/HostConfig"
1220
+ },
1221
+ {
1222
+ type: "string"
1223
+ }
1224
+ ],
1225
+ 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."
1152
1226
  },
1153
1227
  task: {
1154
1228
  type: "string",
@@ -1200,7 +1274,12 @@ var schema_default = {
1200
1274
  type: "object",
1201
1275
  properties: {
1202
1276
  vercel: {
1203
- $ref: "#/definitions/Vercel"
1277
+ $ref: "#/definitions/Vercel",
1278
+ deprecated: "This is being replaced by the `projectId` field below."
1279
+ },
1280
+ projectId: {
1281
+ type: "string",
1282
+ description: "Vercel project ID"
1204
1283
  },
1205
1284
  development: {
1206
1285
  $ref: "#/definitions/Development"
@@ -1210,7 +1289,8 @@ var schema_default = {
1210
1289
  description: "Groups of path expressions that are routed to this application."
1211
1290
  },
1212
1291
  production: {
1213
- $ref: "#/definitions/HostConfig"
1292
+ $ref: "#/definitions/HostConfig",
1293
+ deprecated: "This is a duplicate of the `development.fallback` field and this will be removed soon."
1214
1294
  }
1215
1295
  },
1216
1296
  required: ["routing"],
@@ -1506,7 +1586,7 @@ var MicrofrontendsServer = class extends Microfrontends {
1506
1586
  const [defaultApplication] = Object.entries(validatedConfig.applications).filter(([, app]) => isDefaultApp(app)).map(([name]) => name);
1507
1587
  if (!defaultApplication) {
1508
1588
  throw new MicrofrontendError(
1509
- `No default application found. At least one application needs to be the default by omitting routing.`,
1589
+ "No default application found. At least one application needs to be the default by omitting routing.",
1510
1590
  { type: "config", subtype: "no_default_application" }
1511
1591
  );
1512
1592
  }
@@ -1635,12 +1715,10 @@ function getDomainFromEnvironment({
1635
1715
  if (mfeProjects.length === 0) {
1636
1716
  throw new Error("Missing related microfrontends project information");
1637
1717
  }
1638
- if (!app.vercel?.projectId) {
1718
+ if (!app.projectId) {
1639
1719
  throw new Error(`Missing applications[${app.name}].vercel.projectId`);
1640
1720
  }
1641
- const vercelProject = mfeProjects.find(
1642
- (p) => p.project.id === app.vercel?.projectId
1643
- );
1721
+ const vercelProject = mfeProjects.find((p) => p.project.id === app.projectId);
1644
1722
  if (!vercelProject) {
1645
1723
  throw new Error(
1646
1724
  `Missing related microfrontends project information for application "${app.name}"`
@@ -1674,9 +1752,11 @@ function getCurrentEnvironment() {
1674
1752
  const isProduction2 = process.env.VERCEL_ENV === "production";
1675
1753
  if (isDevelopment) {
1676
1754
  return { group: "development" };
1677
- } else if (isProduction2) {
1755
+ }
1756
+ if (isProduction2) {
1678
1757
  return { group: "production" };
1679
- } else if (isPreview) {
1758
+ }
1759
+ if (isPreview) {
1680
1760
  return { group: "preview" };
1681
1761
  }
1682
1762
  return { group: "custom", name: process.env.VERCEL_ENV };
@@ -1687,10 +1767,10 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
1687
1767
  return app.overrides.environment.toString();
1688
1768
  }
1689
1769
  const { group } = getCurrentEnvironment();
1690
- const productionHost = config.getDefaultApplication().production.toString();
1770
+ const fallbackHost = config.getDefaultApplication().fallback.toString();
1691
1771
  switch (group) {
1692
1772
  case "development": {
1693
- const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : productionHost;
1773
+ const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : fallbackHost;
1694
1774
  debugDomains(appName, "development", domain);
1695
1775
  return domain;
1696
1776
  }
@@ -1701,10 +1781,9 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
1701
1781
  return getDomainFromEnvironment({ app, target: "production" });
1702
1782
  }
1703
1783
  case "custom":
1704
- console.warn(
1705
- `Custom environments are not supported in getDomainForCurrentEnvironment`
1784
+ throw new Error(
1785
+ "Custom environments are not supported in getDomainForCurrentEnvironment"
1706
1786
  );
1707
- return productionHost;
1708
1787
  }
1709
1788
  }
1710
1789
 
@@ -1891,7 +1970,15 @@ function transform5(args) {
1891
1970
  // this deployments host
1892
1971
  ...process.env.VERCEL_URL ? [formatDomainForServerAction(process.env.VERCEL_URL)] : [],
1893
1972
  // default application host
1894
- formatDomainForServerAction(defaultApplication.production.toString()),
1973
+ ...process.env.VERCEL_MICROFRONTENDS_PROJECTS ? [
1974
+ formatDomainForServerAction(
1975
+ getDomainFromEnvironment({
1976
+ app: defaultApplication,
1977
+ target: "production"
1978
+ })
1979
+ )
1980
+ ] : [],
1981
+ formatDomainForServerAction(defaultApplication.fallback.toString()),
1895
1982
  // environment specific microfrontend hosts
1896
1983
  ...appsToAllow.flatMap((a) => [
1897
1984
  formatDomainForServerAction(