@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
@@ -230,7 +230,8 @@ var validateConfigPaths = (applicationConfigsById) => {
230
230
  if (isDefaultApp(app)) {
231
231
  continue;
232
232
  }
233
- for (const pathMatch of app.routing) {
233
+ const childApp = app;
234
+ for (const pathMatch of childApp.routing) {
234
235
  for (const path of pathMatch.paths) {
235
236
  const maybeError = validatePathExpression(path);
236
237
  if (maybeError) {
@@ -250,33 +251,31 @@ var validateConfigPaths = (applicationConfigsById) => {
250
251
  }
251
252
  }
252
253
  const entries = Array.from(pathsByApplicationId.entries());
253
- entries.forEach(([path, { applications: ids, matcher, applicationId }]) => {
254
+ for (const [path, { applications: ids, matcher, applicationId }] of entries) {
254
255
  if (ids.length > 1) {
255
256
  errors.push(
256
257
  `Duplicate path "${path}" for applications "${ids.join(", ")}"`
257
258
  );
258
259
  }
259
- entries.forEach(
260
- ([
261
- matchPath,
262
- { applications: matchIds, applicationId: matchApplicationId }
263
- ]) => {
264
- if (path === matchPath) {
265
- return;
266
- }
267
- if (applicationId === matchApplicationId) {
268
- return;
269
- }
270
- if (matcher.test(matchPath)) {
271
- const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
272
- const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
273
- errors.push(
274
- `Overlapping path detected between ${source} and ${destination}`
275
- );
276
- }
260
+ for (const [
261
+ matchPath,
262
+ { applications: matchIds, applicationId: matchApplicationId }
263
+ ] of entries) {
264
+ if (path === matchPath) {
265
+ continue;
277
266
  }
278
- );
279
- });
267
+ if (applicationId === matchApplicationId) {
268
+ continue;
269
+ }
270
+ if (matcher.test(matchPath)) {
271
+ const source = `"${path}" of application${ids.length > 0 ? "s" : ""} ${ids.join(", ")}`;
272
+ const destination = `"${matchPath}" of application${matchIds.length > 0 ? "s" : ""} ${matchIds.join(", ")}`;
273
+ errors.push(
274
+ `Overlapping path detected between ${source} and ${destination}`
275
+ );
276
+ }
277
+ }
278
+ }
280
279
  if (errors.length) {
281
280
  throw new MicrofrontendError(`Invalid paths: ${errors.join(", ")}`, {
282
281
  type: "config",
@@ -345,7 +344,7 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
345
344
  const numApplicationsWithoutRouting = numApplications - numApplicationsWithRouting;
346
345
  if (numApplicationsWithoutRouting === 0) {
347
346
  throw new MicrofrontendError(
348
- `No default application found. At least one application needs to be the default by omitting routing.`,
347
+ "No default application found. At least one application needs to be the default by omitting routing.",
349
348
  { type: "config", subtype: "no_default_application" }
350
349
  );
351
350
  }
@@ -391,47 +390,80 @@ function generatePortFromName({
391
390
  // src/config/microfrontends-config/isomorphic/host.ts
392
391
  var Host = class {
393
392
  constructor(hostConfig, options) {
394
- const { protocol = "https", host, port } = hostConfig;
395
- this.protocol = protocol;
396
- this.host = host;
397
- this.port = Host.getPort({ port, protocol: this.protocol });
393
+ if (typeof hostConfig === "string") {
394
+ ({
395
+ protocol: this.protocol,
396
+ host: this.host,
397
+ port: this.port
398
+ } = Host.parseUrl(hostConfig));
399
+ } else {
400
+ const { protocol = "https", host, port } = hostConfig;
401
+ this.protocol = protocol;
402
+ this.host = host;
403
+ this.port = port;
404
+ }
398
405
  this.local = options?.isLocal;
399
406
  }
400
- isLocal() {
401
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
402
- }
403
- static getPort({
404
- protocol,
405
- port
406
- }) {
407
- if (!port) {
408
- if (protocol === "http") {
409
- return 80;
410
- }
411
- return 443;
407
+ static parseUrl(url) {
408
+ let hostToParse = url;
409
+ if (!/^https?:\/\//.exec(hostToParse)) {
410
+ hostToParse = `https://${hostToParse}`;
412
411
  }
413
- return port;
412
+ const parsed = new URL(hostToParse);
413
+ if (!parsed.hostname) {
414
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
415
+ }
416
+ if (parsed.hash) {
417
+ throw new Error(
418
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
419
+ );
420
+ }
421
+ if (parsed.username || parsed.password) {
422
+ throw new Error(
423
+ Host.getMicrofrontendsError(
424
+ url,
425
+ "cannot have authentication credentials (username and/or password)"
426
+ )
427
+ );
428
+ }
429
+ if (parsed.pathname !== "/") {
430
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
431
+ }
432
+ if (parsed.search) {
433
+ throw new Error(
434
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
435
+ );
436
+ }
437
+ const protocol = parsed.protocol.slice(0, -1);
438
+ return {
439
+ protocol,
440
+ host: parsed.hostname,
441
+ port: parsed.port ? Number.parseInt(parsed.port) : void 0
442
+ };
414
443
  }
415
- isDefaultPort() {
416
- return this.port === Host.getPort({ protocol: this.protocol });
444
+ static getMicrofrontendsError(url, message) {
445
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
417
446
  }
418
- toString(opts = {}) {
419
- const url = this.toUrl(opts);
447
+ isLocal() {
448
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
449
+ }
450
+ toString() {
451
+ const url = this.toUrl();
420
452
  return url.toString().replace(/\/$/, "");
421
453
  }
422
- toUrl(opts = {}) {
423
- const { includeDefaultPort } = opts;
424
- const url = `${this.protocol}://${this.host}${this.isDefaultPort() && !includeDefaultPort ? "" : `:${this.port}`}`;
454
+ toUrl() {
455
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
425
456
  return new URL(url);
426
457
  }
427
458
  };
428
459
  var LocalHost = class extends Host {
429
460
  constructor({
430
461
  appName,
462
+ localPort,
431
463
  ...hostConfig
432
464
  }) {
433
465
  const host = hostConfig.host ?? "localhost";
434
- const port = hostConfig.port ?? generatePortFromName({ name: appName });
466
+ const port = localPort ?? hostConfig.port ?? generatePortFromName({ name: appName });
435
467
  const protocol = hostConfig.protocol ?? "http";
436
468
  super({ protocol, host, port });
437
469
  }
@@ -448,12 +480,17 @@ var Application = class {
448
480
  this.development = {
449
481
  local: new LocalHost({
450
482
  appName: name,
483
+ localPort: app.development?.localPort,
451
484
  ...app.development?.local
452
485
  }),
453
486
  fallback: app.development?.fallback ? new Host(app.development.fallback) : void 0
454
487
  };
455
- this.production = app.production ? new Host(app.production) : void 0;
456
- this.vercel = app.vercel;
488
+ if (app.development?.fallback) {
489
+ this.fallback = new Host(app.development.fallback);
490
+ } else if (app.production) {
491
+ this.fallback = new Host(app.production);
492
+ }
493
+ this.projectId = app.projectId ?? app.vercel?.projectId;
457
494
  this.overrides = overrides?.environment ? {
458
495
  environment: new Host(overrides.environment)
459
496
  } : void 0;
@@ -481,7 +518,16 @@ var DefaultApplication = class extends Application {
481
518
  isDefault: true
482
519
  });
483
520
  this.default = true;
484
- this.production = new Host(app.production);
521
+ const fallbackHost = app.development?.fallback ?? app.production;
522
+ if (fallbackHost === void 0) {
523
+ throw new Error(
524
+ "`app.production` or `app.development.fallback` must be set in the default application in microfrontends.json."
525
+ );
526
+ }
527
+ this.fallback = new Host(fallbackHost);
528
+ if (app.production) {
529
+ this.production = new Host(app.production);
530
+ }
485
531
  }
486
532
  getAssetPrefix() {
487
533
  return "";
@@ -551,7 +597,7 @@ var MicrofrontendConfigIsomorphic = class {
551
597
  }
552
598
  if (isMainConfig(config) && !this.defaultApplication) {
553
599
  throw new MicrofrontendError(
554
- `Could not find default application in microfrontends configuration`,
600
+ "Could not find default application in microfrontends configuration",
555
601
  {
556
602
  type: "application",
557
603
  subtype: "not_found"
@@ -627,11 +673,11 @@ var MicrofrontendConfigIsomorphic = class {
627
673
  return app;
628
674
  }
629
675
  getApplicationByProjectId(projectId) {
630
- if (this.defaultApplication?.vercel?.projectId === projectId) {
676
+ if (this.defaultApplication?.projectId === projectId) {
631
677
  return this.defaultApplication;
632
678
  }
633
679
  return Object.values(this.childApplications).find(
634
- (app) => app.vercel?.projectId === projectId
680
+ (app) => app.projectId === projectId
635
681
  );
636
682
  }
637
683
  /**
@@ -641,7 +687,7 @@ var MicrofrontendConfigIsomorphic = class {
641
687
  getDefaultApplication() {
642
688
  if (!this.defaultApplication) {
643
689
  throw new MicrofrontendError(
644
- `Could not find default application in microfrontends configuration`,
690
+ "Could not find default application in microfrontends configuration",
645
691
  {
646
692
  type: "application",
647
693
  subtype: "not_found"
@@ -654,7 +700,7 @@ var MicrofrontendConfigIsomorphic = class {
654
700
  * Returns the configured port for the local proxy
655
701
  */
656
702
  getLocalProxyPort() {
657
- return this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
703
+ return this.config.options?.localProxyPort ?? this.config.options?.localProxy?.port ?? DEFAULT_LOCAL_PROXY_PORT;
658
704
  }
659
705
  /**
660
706
  * Serializes the class back to the Schema type.
@@ -728,7 +774,7 @@ var MicrofrontendMainConfig = class extends MicrofrontendConfigIsomorphic {
728
774
  }
729
775
  if (!defaultApplication) {
730
776
  throw new MicrofrontendError(
731
- `Could not find default application in microfrontends configuration`,
777
+ "Could not find default application in microfrontends configuration",
732
778
  {
733
779
  type: "application",
734
780
  subtype: "not_found"
@@ -778,12 +824,10 @@ function getDomainFromEnvironment({
778
824
  if (mfeProjects.length === 0) {
779
825
  throw new Error("Missing related microfrontends project information");
780
826
  }
781
- if (!app.vercel?.projectId) {
827
+ if (!app.projectId) {
782
828
  throw new Error(`Missing applications[${app.name}].vercel.projectId`);
783
829
  }
784
- const vercelProject = mfeProjects.find(
785
- (p) => p.project.id === app.vercel?.projectId
786
- );
830
+ const vercelProject = mfeProjects.find((p) => p.project.id === app.projectId);
787
831
  if (!vercelProject) {
788
832
  throw new Error(
789
833
  `Missing related microfrontends project information for application "${app.name}"`
@@ -817,9 +861,11 @@ function getCurrentEnvironment() {
817
861
  const isProduction = process.env.VERCEL_ENV === "production";
818
862
  if (isDevelopment) {
819
863
  return { group: "development" };
820
- } else if (isProduction) {
864
+ }
865
+ if (isProduction) {
821
866
  return { group: "production" };
822
- } else if (isPreview) {
867
+ }
868
+ if (isPreview) {
823
869
  return { group: "preview" };
824
870
  }
825
871
  return { group: "custom", name: process.env.VERCEL_ENV };
@@ -830,10 +876,10 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
830
876
  return app.overrides.environment.toString();
831
877
  }
832
878
  const { group } = getCurrentEnvironment();
833
- const productionHost = config.getDefaultApplication().production.toString();
879
+ const fallbackHost = config.getDefaultApplication().fallback.toString();
834
880
  switch (group) {
835
881
  case "development": {
836
- const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : productionHost;
882
+ const domain = ["test", "development"].includes(process.env.NODE_ENV) ? app.development.local.toString() : fallbackHost;
837
883
  debugDomains(appName, "development", domain);
838
884
  return domain;
839
885
  }
@@ -844,10 +890,9 @@ function getDomainForCurrentEnvironment(config, appName, opts = {}) {
844
890
  return getDomainFromEnvironment({ app, target: "production" });
845
891
  }
846
892
  case "custom":
847
- console.warn(
848
- `Custom environments are not supported in getDomainForCurrentEnvironment`
893
+ throw new Error(
894
+ "Custom environments are not supported in getDomainForCurrentEnvironment"
849
895
  );
850
- return productionHost;
851
896
  }
852
897
  }
853
898
 
@@ -875,7 +920,7 @@ function getHandler({
875
920
  application,
876
921
  flagFn,
877
922
  pattern,
878
- production
923
+ fallback
879
924
  }) {
880
925
  return async (req) => {
881
926
  try {
@@ -895,7 +940,7 @@ function getHandler({
895
940
  const zoneFallbackCookieName = `__zone_${application.name}_production_fallback`;
896
941
  const assetPrefix = application.getAssetPrefix();
897
942
  if (assetPrefix && pathname.startsWith(`/${assetPrefix}`) && req.cookies.get(zoneFallbackCookieName)?.value === "1") {
898
- rewriteDomain = production.toString();
943
+ rewriteDomain = fallback.toString();
899
944
  } else {
900
945
  try {
901
946
  let deploymentFound;
@@ -905,7 +950,7 @@ function getHandler({
905
950
  deploymentFound = await verifyPreviewDomain(req, rewriteDomain);
906
951
  }
907
952
  if (!deploymentFound) {
908
- rewriteDomain = production.toString();
953
+ rewriteDomain = fallback.toString();
909
954
  responseCallbacks.push((response) => {
910
955
  response.cookies.set(zoneFallbackCookieName, "1", {
911
956
  httpOnly: true,
@@ -920,7 +965,7 @@ function getHandler({
920
965
  if (!existingCookie?.includes("__vercel_toolbar")) {
921
966
  patchedHeaders.set(
922
967
  "cookie",
923
- [`__vercel_toolbar=1`, existingCookie].join("; ")
968
+ ["__vercel_toolbar=1", existingCookie].join("; ")
924
969
  );
925
970
  responseCallbacks.push((response) => {
926
971
  response.cookies.set("__vercel_toolbar", "1", {
@@ -990,7 +1035,7 @@ function getMicrofrontendsMiddleware({
990
1035
  return middlewares;
991
1036
  }
992
1037
  const config = microfrontends.config;
993
- const production = config.defaultApplication.production;
1038
+ const fallback = config.defaultApplication.fallback;
994
1039
  for (const application of config.getChildApplications()) {
995
1040
  if (application.name === process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION) {
996
1041
  continue;
@@ -1003,7 +1048,7 @@ function getMicrofrontendsMiddleware({
1003
1048
  config,
1004
1049
  application,
1005
1050
  pattern,
1006
- production
1051
+ fallback
1007
1052
  })
1008
1053
  });
1009
1054
  }
@@ -1028,7 +1073,7 @@ function getMicrofrontendsMiddleware({
1028
1073
  application,
1029
1074
  flagFn,
1030
1075
  pattern,
1031
- production
1076
+ fallback
1032
1077
  })
1033
1078
  });
1034
1079
  }