@vercel/microfrontends 0.18.0 → 0.19.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.
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 +164 -79
  20. package/dist/next/config.cjs.map +1 -1
  21. package/dist/next/config.js +164 -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
@@ -1,6 +1,6 @@
1
1
  import { NextResponse } from 'next/server';
2
- import { C as ClientConfig } from '../types-089498fd.js';
3
- import '../index-ef8657e6.js';
2
+ import { C as ClientConfig } from '../types-4ef2bddb.js';
3
+ import '../index-2f78c0ca.js';
4
4
 
5
5
  /**
6
6
  * Data that is returned from the `.well-known/vercel/microfrontends/client-config`
@@ -257,7 +257,8 @@ var validateConfigPaths = (applicationConfigsById) => {
257
257
  if (isDefaultApp(app)) {
258
258
  continue;
259
259
  }
260
- for (const pathMatch of app.routing) {
260
+ const childApp = app;
261
+ for (const pathMatch of childApp.routing) {
261
262
  for (const path of pathMatch.paths) {
262
263
  const maybeError = validatePathExpression(path);
263
264
  if (maybeError) {
@@ -277,33 +278,31 @@ var validateConfigPaths = (applicationConfigsById) => {
277
278
  }
278
279
  }
279
280
  const entries = Array.from(pathsByApplicationId.entries());
280
- entries.forEach(([path, { applications: ids, matcher, applicationId }]) => {
281
+ for (const [path, { applications: ids, matcher, applicationId }] of entries) {
281
282
  if (ids.length > 1) {
282
283
  errors.push(
283
284
  `Duplicate path "${path}" for applications "${ids.join(", ")}"`
284
285
  );
285
286
  }
286
- entries.forEach(
287
- ([
288
- matchPath,
289
- { applications: matchIds, applicationId: matchApplicationId }
290
- ]) => {
291
- if (path === matchPath) {
292
- return;
293
- }
294
- if (applicationId === matchApplicationId) {
295
- return;
296
- }
297
- if (matcher.test(matchPath)) {
298
- const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
299
- const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
300
- errors.push(
301
- `Overlapping path detected between ${source} and ${destination}`
302
- );
303
- }
287
+ for (const [
288
+ matchPath,
289
+ { applications: matchIds, applicationId: matchApplicationId }
290
+ ] of entries) {
291
+ if (path === matchPath) {
292
+ continue;
304
293
  }
305
- );
306
- });
294
+ if (applicationId === matchApplicationId) {
295
+ continue;
296
+ }
297
+ if (matcher.test(matchPath)) {
298
+ const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
299
+ const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
300
+ errors.push(
301
+ `Overlapping path detected between ${source} and ${destination}`
302
+ );
303
+ }
304
+ }
305
+ }
307
306
  if (errors.length) {
308
307
  throw new MicrofrontendError(`Invalid paths: ${errors.join(", ")}`, {
309
308
  type: "config",
@@ -372,7 +371,7 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
372
371
  const numApplicationsWithoutRouting = numApplications - numApplicationsWithRouting;
373
372
  if (numApplicationsWithoutRouting === 0) {
374
373
  throw new MicrofrontendError(
375
- `No default application found. At least one application needs to be the default by omitting routing.`,
374
+ "No default application found. At least one application needs to be the default by omitting routing.",
376
375
  { type: "config", subtype: "no_default_application" }
377
376
  );
378
377
  }
@@ -418,47 +417,80 @@ function generatePortFromName({
418
417
  // src/config/microfrontends-config/isomorphic/host.ts
419
418
  var Host = class {
420
419
  constructor(hostConfig, options) {
421
- const { protocol = "https", host, port } = hostConfig;
422
- this.protocol = protocol;
423
- this.host = host;
424
- this.port = Host.getPort({ port, protocol: this.protocol });
420
+ if (typeof hostConfig === "string") {
421
+ ({
422
+ protocol: this.protocol,
423
+ host: this.host,
424
+ port: this.port
425
+ } = Host.parseUrl(hostConfig));
426
+ } else {
427
+ const { protocol = "https", host, port } = hostConfig;
428
+ this.protocol = protocol;
429
+ this.host = host;
430
+ this.port = port;
431
+ }
425
432
  this.local = options?.isLocal;
426
433
  }
427
- isLocal() {
428
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
429
- }
430
- static getPort({
431
- protocol,
432
- port
433
- }) {
434
- if (!port) {
435
- if (protocol === "http") {
436
- return 80;
437
- }
438
- return 443;
434
+ static parseUrl(url) {
435
+ let hostToParse = url;
436
+ if (!/^https?:\/\//.exec(hostToParse)) {
437
+ hostToParse = `https://${hostToParse}`;
439
438
  }
440
- return port;
439
+ const parsed = new URL(hostToParse);
440
+ if (!parsed.hostname) {
441
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
442
+ }
443
+ if (parsed.hash) {
444
+ throw new Error(
445
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
446
+ );
447
+ }
448
+ if (parsed.username || parsed.password) {
449
+ throw new Error(
450
+ Host.getMicrofrontendsError(
451
+ url,
452
+ "cannot have authentication credentials (username and/or password)"
453
+ )
454
+ );
455
+ }
456
+ if (parsed.pathname !== "/") {
457
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
458
+ }
459
+ if (parsed.search) {
460
+ throw new Error(
461
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
462
+ );
463
+ }
464
+ const protocol = parsed.protocol.slice(0, -1);
465
+ return {
466
+ protocol,
467
+ host: parsed.hostname,
468
+ port: parsed.port ? Number.parseInt(parsed.port) : void 0
469
+ };
441
470
  }
442
- isDefaultPort() {
443
- return this.port === Host.getPort({ protocol: this.protocol });
471
+ static getMicrofrontendsError(url, message) {
472
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
444
473
  }
445
- toString(opts = {}) {
446
- const url = this.toUrl(opts);
474
+ isLocal() {
475
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
476
+ }
477
+ toString() {
478
+ const url = this.toUrl();
447
479
  return url.toString().replace(/\/$/, "");
448
480
  }
449
- toUrl(opts = {}) {
450
- const { includeDefaultPort } = opts;
451
- const url = `${this.protocol}://${this.host}${this.isDefaultPort() && !includeDefaultPort ? "" : `:${this.port}`}`;
481
+ toUrl() {
482
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
452
483
  return new URL(url);
453
484
  }
454
485
  };
455
486
  var LocalHost = class extends Host {
456
487
  constructor({
457
488
  appName,
489
+ localPort,
458
490
  ...hostConfig
459
491
  }) {
460
492
  const host = hostConfig.host ?? "localhost";
461
- const port = hostConfig.port ?? generatePortFromName({ name: appName });
493
+ const port = localPort ?? hostConfig.port ?? generatePortFromName({ name: appName });
462
494
  const protocol = hostConfig.protocol ?? "http";
463
495
  super({ protocol, host, port });
464
496
  }
@@ -475,12 +507,17 @@ var Application = class {
475
507
  this.development = {
476
508
  local: new LocalHost({
477
509
  appName: name,
510
+ localPort: app.development?.localPort,
478
511
  ...app.development?.local
479
512
  }),
480
513
  fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0
481
514
  };
482
- this.production = app.production ? new Host(app.production) : void 0;
483
- this.vercel = app.vercel;
515
+ if (app.development?.fallback) {
516
+ this.fallback = new Host(app.development.fallback);
517
+ } else if (app.production) {
518
+ this.fallback = new Host(app.production);
519
+ }
520
+ this.projectId = app.projectId ?? app.vercel?.projectId;
484
521
  this.overrides = overrides?.environment ? {
485
522
  environment: new Host(overrides.environment)
486
523
  } : void 0;
@@ -508,7 +545,16 @@ var DefaultApplication = class extends Application {
508
545
  isDefault: true
509
546
  });
510
547
  this.default = true;
511
- this.production = new Host(app.production);
548
+ const fallbackHost = app.development?.fallback ?? app.production;
549
+ if (fallbackHost === void 0) {
550
+ throw new Error(
551
+ "`app.production` or `app.development.fallback` must be set in the default application in microfrontends.json."
552
+ );
553
+ }
554
+ this.fallback = new Host(fallbackHost);
555
+ if (app.production) {
556
+ this.production = new Host(app.production);
557
+ }
512
558
  }
513
559
  getAssetPrefix() {
514
560
  return "";
@@ -578,7 +624,7 @@ var MicrofrontendConfigIsomorphic = class {
578
624
  }
579
625
  if (isMainConfig(config) && !this.defaultApplication) {
580
626
  throw new MicrofrontendError(
581
- `Could not find default application in microfrontends configuration`,
627
+ "Could not find default application in microfrontends configuration",
582
628
  {
583
629
  type: "application",
584
630
  subtype: "not_found"
@@ -654,11 +700,11 @@ var MicrofrontendConfigIsomorphic = class {
654
700
  return app;
655
701
  }
656
702
  getApplicationByProjectId(projectId) {
657
- if (this.defaultApplication?.vercel?.projectId === projectId) {
703
+ if (this.defaultApplication?.projectId === projectId) {
658
704
  return this.defaultApplication;
659
705
  }
660
706
  return Object.values(this.childApplications).find(
661
- (app) => app.vercel?.projectId === projectId
707
+ (app) => app.projectId === projectId
662
708
  );
663
709
  }
664
710
  /**
@@ -668,7 +714,7 @@ var MicrofrontendConfigIsomorphic = class {
668
714
  getDefaultApplication() {
669
715
  if (!this.defaultApplication) {
670
716
  throw new MicrofrontendError(
671
- `Could not find default application in microfrontends configuration`,
717
+ "Could not find default application in microfrontends configuration",
672
718
  {
673
719
  type: "application",
674
720
  subtype: "not_found"
@@ -681,7 +727,7 @@ var MicrofrontendConfigIsomorphic = class {
681
727
  * Returns the configured port for the local proxy
682
728
  */
683
729
  getLocalProxyPort() {
684
- return this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
730
+ return this.config.options?.localProxyPort ?? this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
685
731
  }
686
732
  /**
687
733
  * Serializes the class back to the Schema type.
@@ -755,7 +801,7 @@ var MicrofrontendMainConfig = class extends MicrofrontendConfigIsomorphic {
755
801
  }
756
802
  if (!defaultApplication) {
757
803
  throw new MicrofrontendError(
758
- `Could not find default application in microfrontends configuration`,
804
+ "Could not find default application in microfrontends configuration",
759
805
  {
760
806
  type: "application",
761
807
  subtype: "not_found"
@@ -805,12 +851,10 @@ function getDomainFromEnvironment({
805
851
  if (mfeProjects.length === 0) {
806
852
  throw new Error("Missing related microfrontends project information");
807
853
  }
808
- if (!app.vercel?.projectId) {
854
+ if (!app.projectId) {
809
855
  throw new Error(`Missing applications[${app.name}].vercel.projectId`);
810
856
  }
811
- const vercelProject = mfeProjects.find(
812
- (p) => p.project.id === app.vercel?.projectId
813
- );
857
+ const vercelProject = mfeProjects.find((p) => p.project.id === app.projectId);
814
858
  if (!vercelProject) {
815
859
  throw new Error(
816
860
  `Missing related microfrontends project information for application "${app.name}"`
@@ -844,9 +888,11 @@ function getCurrentEnvironment() {
844
888
  const isProduction = process.env.VERCEL_ENV === "production";
845
889
  if (isDevelopment) {
846
890
  return { group: "development" };
847
- } else if (isProduction) {
891
+ }
892
+ if (isProduction) {
848
893
  return { group: "production" };
849
- } else if (isPreview) {
894
+ }
895
+ if (isPreview) {
850
896
  return { group: "preview" };
851
897
  }
852
898
  return { group: "custom", name: process.env.VERCEL_ENV };
@@ -857,10 +903,10 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
857
903
  return app.overrides.environment.toString();
858
904
  }
859
905
  const { group } = getCurrentEnvironment();
860
- const productionHost = config.getDefaultApplication().production.toString();
906
+ const fallbackHost = config.getDefaultApplication().fallback.toString();
861
907
  switch (group) {
862
908
  case "development": {
863
- const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : productionHost;
909
+ const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : fallbackHost;
864
910
  debugDomains(appName, "development", domain);
865
911
  return domain;
866
912
  }
@@ -871,10 +917,9 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
871
917
  return getDomainFromEnvironment({ app, target: "production" });
872
918
  }
873
919
  case "custom":
874
- console.warn(
875
- `Custom environments are not supported in getDomainForCurrentEnvironment`
920
+ throw new Error(
921
+ "Custom environments are not supported in getDomainForCurrentEnvironment"
876
922
  );
877
- return productionHost;
878
923
  }
879
924
  }
880
925
 
@@ -902,7 +947,7 @@ function getHandler({
902
947
  application,
903
948
  flagFn,
904
949
  pattern,
905
- production
950
+ fallback
906
951
  }) {
907
952
  return async (req) => {
908
953
  try {
@@ -922,7 +967,7 @@ function getHandler({
922
967
  const zoneFallbackCookieName = `__zone_${application.name}_production_fallback`;
923
968
  const assetPrefix = application.getAssetPrefix();
924
969
  if (assetPrefix && pathname.startsWith(`/${assetPrefix}`) && req.cookies.get(zoneFallbackCookieName)?.value === "1") {
925
- rewriteDomain = production.toString();
970
+ rewriteDomain = fallback.toString();
926
971
  } else {
927
972
  try {
928
973
  let deploymentFound;
@@ -932,7 +977,7 @@ function getHandler({
932
977
  deploymentFound = await verifyPreviewDomain(req, rewriteDomain);
933
978
  }
934
979
  if (!deploymentFound) {
935
- rewriteDomain = production.toString();
980
+ rewriteDomain = fallback.toString();
936
981
  responseCallbacks.push((response) => {
937
982
  response.cookies.set(zoneFallbackCookieName, "1", {
938
983
  httpOnly: true,
@@ -947,7 +992,7 @@ function getHandler({
947
992
  if (!existingCookie?.includes("__vercel_toolbar")) {
948
993
  patchedHeaders.set(
949
994
  "cookie",
950
- [`__vercel_toolbar=1`, existingCookie].join("; ")
995
+ ["__vercel_toolbar=1", existingCookie].join("; ")
951
996
  );
952
997
  responseCallbacks.push((response) => {
953
998
  response.cookies.set("__vercel_toolbar", "1", {
@@ -1017,7 +1062,7 @@ function getMicrofrontendsMiddleware({
1017
1062
  return middlewares;
1018
1063
  }
1019
1064
  const config = microfrontends.config;
1020
- const production = config.defaultApplication.production;
1065
+ const fallback = config.defaultApplication.fallback;
1021
1066
  for (const application of config.getChildApplications()) {
1022
1067
  if (application.name === process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION) {
1023
1068
  continue;
@@ -1030,7 +1075,7 @@ function getMicrofrontendsMiddleware({
1030
1075
  config,
1031
1076
  application,
1032
1077
  pattern,
1033
- production
1078
+ fallback
1034
1079
  })
1035
1080
  });
1036
1081
  }
@@ -1055,7 +1100,7 @@ function getMicrofrontendsMiddleware({
1055
1100
  application,
1056
1101
  flagFn,
1057
1102
  pattern,
1058
- production
1103
+ fallback
1059
1104
  })
1060
1105
  });
1061
1106
  }