@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
package/dist/bin/cli.cjs CHANGED
@@ -29,7 +29,7 @@ var import_commander = require("commander");
29
29
  // package.json
30
30
  var package_default = {
31
31
  name: "@vercel/microfrontends",
32
- version: "0.18.0",
32
+ version: "0.19.1",
33
33
  private: false,
34
34
  description: "Defines configuration and utilities for microfrontends development",
35
35
  keywords: [
@@ -337,7 +337,8 @@ var validateConfigPaths = (applicationConfigsById) => {
337
337
  if (isDefaultApp(app)) {
338
338
  continue;
339
339
  }
340
- for (const pathMatch of app.routing) {
340
+ const childApp = app;
341
+ for (const pathMatch of childApp.routing) {
341
342
  for (const path6 of pathMatch.paths) {
342
343
  const maybeError = validatePathExpression(path6);
343
344
  if (maybeError) {
@@ -357,33 +358,31 @@ var validateConfigPaths = (applicationConfigsById) => {
357
358
  }
358
359
  }
359
360
  const entries = Array.from(pathsByApplicationId.entries());
360
- entries.forEach(([path6, { applications: ids, matcher, applicationId }]) => {
361
+ for (const [path6, { applications: ids, matcher, applicationId }] of entries) {
361
362
  if (ids.length > 1) {
362
363
  errors.push(
363
364
  `Duplicate path "${path6}" for applications "${ids.join(", ")}"`
364
365
  );
365
366
  }
366
- entries.forEach(
367
- ([
368
- matchPath,
369
- { applications: matchIds, applicationId: matchApplicationId }
370
- ]) => {
371
- if (path6 === matchPath) {
372
- return;
373
- }
374
- if (applicationId === matchApplicationId) {
375
- return;
376
- }
377
- if (matcher.test(matchPath)) {
378
- const source = `"${path6}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
379
- const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
380
- errors.push(
381
- `Overlapping path detected between ${source} and ${destination}`
382
- );
383
- }
367
+ for (const [
368
+ matchPath,
369
+ { applications: matchIds, applicationId: matchApplicationId }
370
+ ] of entries) {
371
+ if (path6 === matchPath) {
372
+ continue;
384
373
  }
385
- );
386
- });
374
+ if (applicationId === matchApplicationId) {
375
+ continue;
376
+ }
377
+ if (matcher.test(matchPath)) {
378
+ const source = `"${path6}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
379
+ const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
380
+ errors.push(
381
+ `Overlapping path detected between ${source} and ${destination}`
382
+ );
383
+ }
384
+ }
385
+ }
387
386
  if (errors.length) {
388
387
  throw new MicrofrontendError(`Invalid paths: ${errors.join(", ")}`, {
389
388
  type: "config",
@@ -452,7 +451,7 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
452
451
  const numApplicationsWithoutRouting = numApplications - numApplicationsWithRouting;
453
452
  if (numApplicationsWithoutRouting === 0) {
454
453
  throw new MicrofrontendError(
455
- `No default application found. At least one application needs to be the default by omitting routing.`,
454
+ "No default application found. At least one application needs to be the default by omitting routing.",
456
455
  { type: "config", subtype: "no_default_application" }
457
456
  );
458
457
  }
@@ -498,47 +497,80 @@ function generatePortFromName({
498
497
  // src/config/microfrontends-config/isomorphic/host.ts
499
498
  var Host = class {
500
499
  constructor(hostConfig, options) {
501
- const { protocol = "https", host, port } = hostConfig;
502
- this.protocol = protocol;
503
- this.host = host;
504
- this.port = Host.getPort({ port, protocol: this.protocol });
500
+ if (typeof hostConfig === "string") {
501
+ ({
502
+ protocol: this.protocol,
503
+ host: this.host,
504
+ port: this.port
505
+ } = Host.parseUrl(hostConfig));
506
+ } else {
507
+ const { protocol = "https", host, port } = hostConfig;
508
+ this.protocol = protocol;
509
+ this.host = host;
510
+ this.port = port;
511
+ }
505
512
  this.local = options?.isLocal;
506
513
  }
507
- isLocal() {
508
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
509
- }
510
- static getPort({
511
- protocol,
512
- port
513
- }) {
514
- if (!port) {
515
- if (protocol === "http") {
516
- return 80;
517
- }
518
- return 443;
514
+ static parseUrl(url) {
515
+ let hostToParse = url;
516
+ if (!/^https?:\/\//.exec(hostToParse)) {
517
+ hostToParse = `https://${hostToParse}`;
518
+ }
519
+ const parsed = new URL(hostToParse);
520
+ if (!parsed.hostname) {
521
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
522
+ }
523
+ if (parsed.hash) {
524
+ throw new Error(
525
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
526
+ );
519
527
  }
520
- return port;
528
+ if (parsed.username || parsed.password) {
529
+ throw new Error(
530
+ Host.getMicrofrontendsError(
531
+ url,
532
+ "cannot have authentication credentials (username and/or password)"
533
+ )
534
+ );
535
+ }
536
+ if (parsed.pathname !== "/") {
537
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
538
+ }
539
+ if (parsed.search) {
540
+ throw new Error(
541
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
542
+ );
543
+ }
544
+ const protocol = parsed.protocol.slice(0, -1);
545
+ return {
546
+ protocol,
547
+ host: parsed.hostname,
548
+ port: parsed.port ? Number.parseInt(parsed.port) : void 0
549
+ };
521
550
  }
522
- isDefaultPort() {
523
- return this.port === Host.getPort({ protocol: this.protocol });
551
+ static getMicrofrontendsError(url, message) {
552
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
553
+ }
554
+ isLocal() {
555
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
524
556
  }
525
- toString(opts = {}) {
526
- const url = this.toUrl(opts);
557
+ toString() {
558
+ const url = this.toUrl();
527
559
  return url.toString().replace(/\/$/, "");
528
560
  }
529
- toUrl(opts = {}) {
530
- const { includeDefaultPort } = opts;
531
- const url = `${this.protocol}://${this.host}${this.isDefaultPort() && !includeDefaultPort ? "" : `:${this.port}`}`;
561
+ toUrl() {
562
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
532
563
  return new URL(url);
533
564
  }
534
565
  };
535
566
  var LocalHost = class extends Host {
536
567
  constructor({
537
568
  appName,
569
+ localPort,
538
570
  ...hostConfig
539
571
  }) {
540
572
  const host = hostConfig.host ?? "localhost";
541
- const port = hostConfig.port ?? generatePortFromName({ name: appName });
573
+ const port = localPort ?? hostConfig.port ?? generatePortFromName({ name: appName });
542
574
  const protocol = hostConfig.protocol ?? "http";
543
575
  super({ protocol, host, port });
544
576
  }
@@ -555,12 +587,17 @@ var Application = class {
555
587
  this.development = {
556
588
  local: new LocalHost({
557
589
  appName: name,
590
+ localPort: app.development?.localPort,
558
591
  ...app.development?.local
559
592
  }),
560
593
  fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0
561
594
  };
562
- this.production = app.production ? new Host(app.production) : void 0;
563
- this.vercel = app.vercel;
595
+ if (app.development?.fallback) {
596
+ this.fallback = new Host(app.development.fallback);
597
+ } else if (app.production) {
598
+ this.fallback = new Host(app.production);
599
+ }
600
+ this.projectId = app.projectId ?? app.vercel?.projectId;
564
601
  this.overrides = overrides?.environment ? {
565
602
  environment: new Host(overrides.environment)
566
603
  } : void 0;
@@ -588,7 +625,16 @@ var DefaultApplication = class extends Application {
588
625
  isDefault: true
589
626
  });
590
627
  this.default = true;
591
- this.production = new Host(app.production);
628
+ const fallbackHost = app.development?.fallback ?? app.production;
629
+ if (fallbackHost === void 0) {
630
+ throw new Error(
631
+ "`app.production` or `app.development.fallback` must be set in the default application in microfrontends.json."
632
+ );
633
+ }
634
+ this.fallback = new Host(fallbackHost);
635
+ if (app.production) {
636
+ this.production = new Host(app.production);
637
+ }
592
638
  }
593
639
  getAssetPrefix() {
594
640
  return "";
@@ -777,7 +823,7 @@ var MicrofrontendConfigIsomorphic = class {
777
823
  }
778
824
  if (isMainConfig(config) && !this.defaultApplication) {
779
825
  throw new MicrofrontendError(
780
- `Could not find default application in microfrontends configuration`,
826
+ "Could not find default application in microfrontends configuration",
781
827
  {
782
828
  type: "application",
783
829
  subtype: "not_found"
@@ -853,11 +899,11 @@ var MicrofrontendConfigIsomorphic = class {
853
899
  return app;
854
900
  }
855
901
  getApplicationByProjectId(projectId) {
856
- if (this.defaultApplication?.vercel?.projectId === projectId) {
902
+ if (this.defaultApplication?.projectId === projectId) {
857
903
  return this.defaultApplication;
858
904
  }
859
905
  return Object.values(this.childApplications).find(
860
- (app) => app.vercel?.projectId === projectId
906
+ (app) => app.projectId === projectId
861
907
  );
862
908
  }
863
909
  /**
@@ -867,7 +913,7 @@ var MicrofrontendConfigIsomorphic = class {
867
913
  getDefaultApplication() {
868
914
  if (!this.defaultApplication) {
869
915
  throw new MicrofrontendError(
870
- `Could not find default application in microfrontends configuration`,
916
+ "Could not find default application in microfrontends configuration",
871
917
  {
872
918
  type: "application",
873
919
  subtype: "not_found"
@@ -880,7 +926,7 @@ var MicrofrontendConfigIsomorphic = class {
880
926
  * Returns the configured port for the local proxy
881
927
  */
882
928
  getLocalProxyPort() {
883
- return this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
929
+ return this.config.options?.localProxyPort ?? this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
884
930
  }
885
931
  /**
886
932
  * Serializes the class back to the Schema type.
@@ -941,7 +987,7 @@ var MicrofrontendMainConfig = class extends MicrofrontendConfigIsomorphic {
941
987
  }
942
988
  if (!defaultApplication) {
943
989
  throw new MicrofrontendError(
944
- `Could not find default application in microfrontends configuration`,
990
+ "Could not find default application in microfrontends configuration",
945
991
  {
946
992
  type: "application",
947
993
  subtype: "not_found"
@@ -1263,11 +1309,21 @@ var schema_default = {
1263
1309
  properties: {
1264
1310
  vercel: {
1265
1311
  $ref: "#/definitions/VercelOptions",
1266
- description: "Micro-Frontends wide options for Vercel."
1312
+ description: "Microfrontends wide options for Vercel.",
1313
+ deprecated: "This is being replaced by the `disableOverrides` field below."
1314
+ },
1315
+ disableOverrides: {
1316
+ type: "boolean",
1317
+ 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."
1267
1318
  },
1268
1319
  localProxy: {
1269
1320
  $ref: "#/definitions/LocalProxyOptions",
1270
- description: "Options for local proxy."
1321
+ description: "Options for local proxy.",
1322
+ deprecated: "This is being replaced by the `localProxyPort` field below."
1323
+ },
1324
+ localProxyPort: {
1325
+ type: "number",
1326
+ description: "The port number used by the local proxy server.\n\nThe default is `3024`."
1271
1327
  }
1272
1328
  },
1273
1329
  additionalProperties: false
@@ -1315,13 +1371,19 @@ var schema_default = {
1315
1371
  type: "object",
1316
1372
  properties: {
1317
1373
  vercel: {
1318
- $ref: "#/definitions/Vercel"
1374
+ $ref: "#/definitions/Vercel",
1375
+ deprecated: "This is being replaced by the `projectId` field below."
1376
+ },
1377
+ projectId: {
1378
+ type: "string",
1379
+ description: "Vercel project ID"
1319
1380
  },
1320
1381
  development: {
1321
1382
  $ref: "#/definitions/Development"
1322
1383
  },
1323
1384
  production: {
1324
- $ref: "#/definitions/HostConfig"
1385
+ $ref: "#/definitions/HostConfig",
1386
+ deprecated: "This is a duplicate of the `development.fallback` field and this will be removed soon."
1325
1387
  }
1326
1388
  },
1327
1389
  required: ["production"],
@@ -1342,11 +1404,23 @@ var schema_default = {
1342
1404
  type: "object",
1343
1405
  properties: {
1344
1406
  local: {
1345
- $ref: "#/definitions/LocalHostConfig"
1407
+ $ref: "#/definitions/LocalHostConfig",
1408
+ deprecated: "This is being replaced by the `localPort` field below."
1409
+ },
1410
+ localPort: {
1411
+ type: "number",
1412
+ 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."
1346
1413
  },
1347
1414
  fallback: {
1348
- $ref: "#/definitions/HostConfig",
1349
- 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."
1415
+ anyOf: [
1416
+ {
1417
+ $ref: "#/definitions/HostConfig"
1418
+ },
1419
+ {
1420
+ type: "string"
1421
+ }
1422
+ ],
1423
+ 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."
1350
1424
  },
1351
1425
  task: {
1352
1426
  type: "string",
@@ -1398,7 +1472,12 @@ var schema_default = {
1398
1472
  type: "object",
1399
1473
  properties: {
1400
1474
  vercel: {
1401
- $ref: "#/definitions/Vercel"
1475
+ $ref: "#/definitions/Vercel",
1476
+ deprecated: "This is being replaced by the `projectId` field below."
1477
+ },
1478
+ projectId: {
1479
+ type: "string",
1480
+ description: "Vercel project ID"
1402
1481
  },
1403
1482
  development: {
1404
1483
  $ref: "#/definitions/Development"
@@ -1408,7 +1487,8 @@ var schema_default = {
1408
1487
  description: "Groups of path expressions that are routed to this application."
1409
1488
  },
1410
1489
  production: {
1411
- $ref: "#/definitions/HostConfig"
1490
+ $ref: "#/definitions/HostConfig",
1491
+ deprecated: "This is a duplicate of the `development.fallback` field and this will be removed soon."
1412
1492
  }
1413
1493
  },
1414
1494
  required: ["routing"],
@@ -1704,7 +1784,7 @@ var MicrofrontendsServer = class extends Microfrontends {
1704
1784
  const [defaultApplication] = Object.entries(validatedConfig.applications).filter(([, app]) => isDefaultApp(app)).map(([name]) => name);
1705
1785
  if (!defaultApplication) {
1706
1786
  throw new MicrofrontendError(
1707
- `No default application found. At least one application needs to be the default by omitting routing.`,
1787
+ "No default application found. At least one application needs to be the default by omitting routing.",
1708
1788
  { type: "config", subtype: "no_default_application" }
1709
1789
  );
1710
1790
  }
@@ -1742,13 +1822,13 @@ var ProxyRequestRouter = class {
1742
1822
  getApplicationTarget(application) {
1743
1823
  const useDev = this.localApps.includes(application.name);
1744
1824
  let applicationName = application.name;
1745
- let host = useDev ? application.development.local : application.production;
1825
+ let host = useDev ? application.development.local : application.fallback;
1746
1826
  if (application.overrides?.environment?.host) {
1747
1827
  host = application.overrides.environment;
1748
1828
  }
1749
1829
  if (!host) {
1750
1830
  const defaultApp = this.config.getDefaultApplication();
1751
- host = defaultApp.production;
1831
+ host = defaultApp.fallback;
1752
1832
  applicationName = defaultApp.name;
1753
1833
  }
1754
1834
  const protocol = host.protocol;
@@ -2089,7 +2169,7 @@ function loadConfig({
2089
2169
  return void 0;
2090
2170
  }
2091
2171
  const app = config.config.getApplication(appName);
2092
- const port = app.development.local.port;
2172
+ const port = app.development.local.port ?? (app.development.local.protocol === "https" ? 443 : 80);
2093
2173
  return { port };
2094
2174
  }
2095
2175
 
package/dist/config.cjs CHANGED
@@ -250,7 +250,8 @@ var validateConfigPaths = (applicationConfigsById) => {
250
250
  if (isDefaultApp(app)) {
251
251
  continue;
252
252
  }
253
- for (const pathMatch of app.routing) {
253
+ const childApp = app;
254
+ for (const pathMatch of childApp.routing) {
254
255
  for (const path of pathMatch.paths) {
255
256
  const maybeError = validatePathExpression(path);
256
257
  if (maybeError) {
@@ -270,33 +271,31 @@ var validateConfigPaths = (applicationConfigsById) => {
270
271
  }
271
272
  }
272
273
  const entries = Array.from(pathsByApplicationId.entries());
273
- entries.forEach(([path, { applications: ids, matcher, applicationId }]) => {
274
+ for (const [path, { applications: ids, matcher, applicationId }] of entries) {
274
275
  if (ids.length > 1) {
275
276
  errors.push(
276
277
  `Duplicate path "${path}" for applications "${ids.join(", ")}"`
277
278
  );
278
279
  }
279
- entries.forEach(
280
- ([
281
- matchPath,
282
- { applications: matchIds, applicationId: matchApplicationId }
283
- ]) => {
284
- if (path === matchPath) {
285
- return;
286
- }
287
- if (applicationId === matchApplicationId) {
288
- return;
289
- }
290
- if (matcher.test(matchPath)) {
291
- const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
292
- const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
293
- errors.push(
294
- `Overlapping path detected between ${source} and ${destination}`
295
- );
296
- }
280
+ for (const [
281
+ matchPath,
282
+ { applications: matchIds, applicationId: matchApplicationId }
283
+ ] of entries) {
284
+ if (path === matchPath) {
285
+ continue;
297
286
  }
298
- );
299
- });
287
+ if (applicationId === matchApplicationId) {
288
+ continue;
289
+ }
290
+ if (matcher.test(matchPath)) {
291
+ const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
292
+ const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
293
+ errors.push(
294
+ `Overlapping path detected between ${source} and ${destination}`
295
+ );
296
+ }
297
+ }
298
+ }
300
299
  if (errors.length) {
301
300
  throw new MicrofrontendError(`Invalid paths: ${errors.join(", ")}`, {
302
301
  type: "config",
@@ -365,7 +364,7 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
365
364
  const numApplicationsWithoutRouting = numApplications - numApplicationsWithRouting;
366
365
  if (numApplicationsWithoutRouting === 0) {
367
366
  throw new MicrofrontendError(
368
- `No default application found. At least one application needs to be the default by omitting routing.`,
367
+ "No default application found. At least one application needs to be the default by omitting routing.",
369
368
  { type: "config", subtype: "no_default_application" }
370
369
  );
371
370
  }
@@ -411,47 +410,80 @@ function generatePortFromName({
411
410
  // src/config/microfrontends-config/isomorphic/host.ts
412
411
  var Host = class {
413
412
  constructor(hostConfig, options) {
414
- const { protocol = "https", host, port } = hostConfig;
415
- this.protocol = protocol;
416
- this.host = host;
417
- this.port = Host.getPort({ port, protocol: this.protocol });
413
+ if (typeof hostConfig === "string") {
414
+ ({
415
+ protocol: this.protocol,
416
+ host: this.host,
417
+ port: this.port
418
+ } = Host.parseUrl(hostConfig));
419
+ } else {
420
+ const { protocol = "https", host, port } = hostConfig;
421
+ this.protocol = protocol;
422
+ this.host = host;
423
+ this.port = port;
424
+ }
418
425
  this.local = options?.isLocal;
419
426
  }
420
- isLocal() {
421
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
422
- }
423
- static getPort({
424
- protocol,
425
- port
426
- }) {
427
- if (!port) {
428
- if (protocol === "http") {
429
- return 80;
430
- }
431
- return 443;
427
+ static parseUrl(url) {
428
+ let hostToParse = url;
429
+ if (!/^https?:\/\//.exec(hostToParse)) {
430
+ hostToParse = `https://${hostToParse}`;
431
+ }
432
+ const parsed = new URL(hostToParse);
433
+ if (!parsed.hostname) {
434
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
435
+ }
436
+ if (parsed.hash) {
437
+ throw new Error(
438
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
439
+ );
440
+ }
441
+ if (parsed.username || parsed.password) {
442
+ throw new Error(
443
+ Host.getMicrofrontendsError(
444
+ url,
445
+ "cannot have authentication credentials (username and/or password)"
446
+ )
447
+ );
448
+ }
449
+ if (parsed.pathname !== "/") {
450
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
451
+ }
452
+ if (parsed.search) {
453
+ throw new Error(
454
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
455
+ );
432
456
  }
433
- return port;
457
+ const protocol = parsed.protocol.slice(0, -1);
458
+ return {
459
+ protocol,
460
+ host: parsed.hostname,
461
+ port: parsed.port ? Number.parseInt(parsed.port) : void 0
462
+ };
463
+ }
464
+ static getMicrofrontendsError(url, message) {
465
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
434
466
  }
435
- isDefaultPort() {
436
- return this.port === Host.getPort({ protocol: this.protocol });
467
+ isLocal() {
468
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
437
469
  }
438
- toString(opts = {}) {
439
- const url = this.toUrl(opts);
470
+ toString() {
471
+ const url = this.toUrl();
440
472
  return url.toString().replace(/\/$/, "");
441
473
  }
442
- toUrl(opts = {}) {
443
- const { includeDefaultPort } = opts;
444
- const url = `${this.protocol}://${this.host}${this.isDefaultPort() && !includeDefaultPort ? "" : `:${this.port}`}`;
474
+ toUrl() {
475
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
445
476
  return new URL(url);
446
477
  }
447
478
  };
448
479
  var LocalHost = class extends Host {
449
480
  constructor({
450
481
  appName,
482
+ localPort,
451
483
  ...hostConfig
452
484
  }) {
453
485
  const host = hostConfig.host ?? "localhost";
454
- const port = hostConfig.port ?? generatePortFromName({ name: appName });
486
+ const port = localPort ?? hostConfig.port ?? generatePortFromName({ name: appName });
455
487
  const protocol = hostConfig.protocol ?? "http";
456
488
  super({ protocol, host, port });
457
489
  }
@@ -468,12 +500,17 @@ var Application = class {
468
500
  this.development = {
469
501
  local: new LocalHost({
470
502
  appName: name,
503
+ localPort: app.development?.localPort,
471
504
  ...app.development?.local
472
505
  }),
473
506
  fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0
474
507
  };
475
- this.production = app.production ? new Host(app.production) : void 0;
476
- this.vercel = app.vercel;
508
+ if (app.development?.fallback) {
509
+ this.fallback = new Host(app.development.fallback);
510
+ } else if (app.production) {
511
+ this.fallback = new Host(app.production);
512
+ }
513
+ this.projectId = app.projectId ?? app.vercel?.projectId;
477
514
  this.overrides = overrides?.environment ? {
478
515
  environment: new Host(overrides.environment)
479
516
  } : void 0;
@@ -501,7 +538,16 @@ var DefaultApplication = class extends Application {
501
538
  isDefault: true
502
539
  });
503
540
  this.default = true;
504
- this.production = new Host(app.production);
541
+ const fallbackHost = app.development?.fallback ?? app.production;
542
+ if (fallbackHost === void 0) {
543
+ throw new Error(
544
+ "`app.production` or `app.development.fallback` must be set in the default application in microfrontends.json."
545
+ );
546
+ }
547
+ this.fallback = new Host(fallbackHost);
548
+ if (app.production) {
549
+ this.production = new Host(app.production);
550
+ }
505
551
  }
506
552
  getAssetPrefix() {
507
553
  return "";
@@ -571,7 +617,7 @@ var MicrofrontendConfigIsomorphic = class {
571
617
  }
572
618
  if (isMainConfig(config) && !this.defaultApplication) {
573
619
  throw new MicrofrontendError(
574
- `Could not find default application in microfrontends configuration`,
620
+ "Could not find default application in microfrontends configuration",
575
621
  {
576
622
  type: "application",
577
623
  subtype: "not_found"
@@ -647,11 +693,11 @@ var MicrofrontendConfigIsomorphic = class {
647
693
  return app;
648
694
  }
649
695
  getApplicationByProjectId(projectId) {
650
- if (this.defaultApplication?.vercel?.projectId === projectId) {
696
+ if (this.defaultApplication?.projectId === projectId) {
651
697
  return this.defaultApplication;
652
698
  }
653
699
  return Object.values(this.childApplications).find(
654
- (app) => app.vercel?.projectId === projectId
700
+ (app) => app.projectId === projectId
655
701
  );
656
702
  }
657
703
  /**
@@ -661,7 +707,7 @@ var MicrofrontendConfigIsomorphic = class {
661
707
  getDefaultApplication() {
662
708
  if (!this.defaultApplication) {
663
709
  throw new MicrofrontendError(
664
- `Could not find default application in microfrontends configuration`,
710
+ "Could not find default application in microfrontends configuration",
665
711
  {
666
712
  type: "application",
667
713
  subtype: "not_found"
@@ -674,7 +720,7 @@ var MicrofrontendConfigIsomorphic = class {
674
720
  * Returns the configured port for the local proxy
675
721
  */
676
722
  getLocalProxyPort() {
677
- return this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
723
+ return this.config.options?.localProxyPort ?? this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
678
724
  }
679
725
  /**
680
726
  * Serializes the class back to the Schema type.