@vercel/microfrontends 1.5.0 → 2.0.0-canary.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 (58) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/bin/cli.cjs +103 -117
  3. package/dist/config.cjs +48 -101
  4. package/dist/config.cjs.map +1 -1
  5. package/dist/config.d.ts +2 -29
  6. package/dist/config.js +37 -100
  7. package/dist/config.js.map +1 -1
  8. package/dist/experimental/sveltekit.cjs +93 -109
  9. package/dist/experimental/sveltekit.cjs.map +1 -1
  10. package/dist/experimental/sveltekit.js +92 -108
  11. package/dist/experimental/sveltekit.js.map +1 -1
  12. package/dist/experimental/vite.cjs +93 -109
  13. package/dist/experimental/vite.cjs.map +1 -1
  14. package/dist/experimental/vite.js +92 -108
  15. package/dist/experimental/vite.js.map +1 -1
  16. package/dist/get-application-context-e8a5a0e2.d.ts +14 -0
  17. package/dist/microfrontends/server.cjs +93 -109
  18. package/dist/microfrontends/server.cjs.map +1 -1
  19. package/dist/microfrontends/server.d.ts +4 -13
  20. package/dist/microfrontends/server.js +92 -108
  21. package/dist/microfrontends/server.js.map +1 -1
  22. package/dist/microfrontends/utils.cjs +24 -4
  23. package/dist/microfrontends/utils.cjs.map +1 -1
  24. package/dist/microfrontends/utils.d.ts +3 -1
  25. package/dist/microfrontends/utils.js +24 -4
  26. package/dist/microfrontends/utils.js.map +1 -1
  27. package/dist/next/client.cjs +1 -1
  28. package/dist/next/client.cjs.map +1 -1
  29. package/dist/next/client.js +1 -1
  30. package/dist/next/client.js.map +1 -1
  31. package/dist/next/config.cjs +223 -111
  32. package/dist/next/config.cjs.map +1 -1
  33. package/dist/next/config.js +222 -110
  34. package/dist/next/config.js.map +1 -1
  35. package/dist/next/middleware.cjs +88 -44
  36. package/dist/next/middleware.cjs.map +1 -1
  37. package/dist/next/middleware.js +78 -44
  38. package/dist/next/middleware.js.map +1 -1
  39. package/dist/next/testing.cjs +51 -104
  40. package/dist/next/testing.cjs.map +1 -1
  41. package/dist/next/testing.d.ts +2 -2
  42. package/dist/next/testing.js +39 -102
  43. package/dist/next/testing.js.map +1 -1
  44. package/dist/overrides.d.ts +3 -3
  45. package/dist/schema.d.ts +2 -2
  46. package/dist/{types-1cec43e6.d.ts → types-0deb756b.d.ts} +16 -1
  47. package/dist/{types-1fb60496.d.ts → types-4299bff1.d.ts} +1 -1
  48. package/dist/utils/mfe-port.cjs +93 -109
  49. package/dist/utils/mfe-port.cjs.map +1 -1
  50. package/dist/utils/mfe-port.js +92 -108
  51. package/dist/utils/mfe-port.js.map +1 -1
  52. package/dist/validation.cjs +4 -0
  53. package/dist/validation.cjs.map +1 -1
  54. package/dist/validation.d.ts +1 -1
  55. package/dist/validation.js +4 -0
  56. package/dist/validation.js.map +1 -1
  57. package/package.json +3 -1
  58. package/schema/schema.json +4 -0
@@ -233,8 +233,9 @@ var CONFIGURATION_FILENAMES = [
233
233
  var configCache = {};
234
234
  function findPackageWithMicrofrontendsConfig({
235
235
  repositoryRoot,
236
- applicationName
236
+ applicationContext
237
237
  }) {
238
+ const applicationName = applicationContext.name;
238
239
  try {
239
240
  const microfrontendsJsonPaths = import_fast_glob.default.globSync(
240
241
  `**/{${CONFIGURATION_FILENAMES.join(",")}}`,
@@ -276,26 +277,45 @@ ${matchingPaths.join("\n \u2022 ")}`,
276
277
  );
277
278
  }
278
279
  if (matchingPaths.length === 0) {
280
+ let additionalErrorMessage = "";
281
+ if (microfrontendsJsonPaths.length > 0) {
282
+ if (!applicationContext.projectName) {
283
+ additionalErrorMessage = `
284
+
285
+ If the name in package.json (${applicationContext.packageJsonName}) differs from your Vercel Project name, set the \`packageName\` field for the application in \`microfrontends.json\` to ensure that the configuration can be found locally.`;
286
+ } else {
287
+ additionalErrorMessage = `
288
+
289
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
290
+ }
291
+ }
279
292
  throw new MicrofrontendError(
280
- `Could not find a \`microfrontends.json\` file in the repository that contains "applications.${applicationName}". If your Vercel Microfrontends configuration is not in this repository, you can use the Vercel CLI to pull the Vercel Microfrontends configuration using the "vercel microfrontends pull" command, or you can specify the path manually using the VC_MICROFRONTENDS_CONFIG environment variable. If you suspect this is thrown in error, please reach out to the Vercel team.`,
293
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
294
+
295
+ If your Vercel Microfrontends configuration is not in this repository, you can use the Vercel CLI to pull the Vercel Microfrontends configuration using the "vercel microfrontends pull" command, or you can specify the path manually using the VC_MICROFRONTENDS_CONFIG environment variable.
296
+
297
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
281
298
  { type: "config", subtype: "inference_failed" }
282
299
  );
283
300
  }
284
301
  const [packageJsonPath] = matchingPaths;
285
302
  return (0, import_node_path2.dirname)(packageJsonPath);
286
303
  } catch (error) {
304
+ if (error instanceof MicrofrontendError) {
305
+ throw error;
306
+ }
287
307
  return null;
288
308
  }
289
309
  }
290
310
  function inferMicrofrontendsLocation(opts) {
291
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationName}`;
311
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
292
312
  if (configCache[cacheKey]) {
293
313
  return configCache[cacheKey];
294
314
  }
295
315
  const result = findPackageWithMicrofrontendsConfig(opts);
296
316
  if (!result) {
297
317
  throw new MicrofrontendError(
298
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationName}" starting in directory "${opts.repositoryRoot}".`,
318
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
299
319
  { type: "config", subtype: "inference_failed" }
300
320
  );
301
321
  }
@@ -367,90 +387,13 @@ function findConfig({ dir }) {
367
387
  // src/config/microfrontends-config/isomorphic/index.ts
368
388
  var import_jsonc_parser2 = require("jsonc-parser");
369
389
 
370
- // src/config/microfrontends-config/client/index.ts
371
- var import_path_to_regexp = require("path-to-regexp");
372
- var regexpCache = /* @__PURE__ */ new Map();
373
- var getRegexp = (path6) => {
374
- const existing = regexpCache.get(path6);
375
- if (existing) {
376
- return existing;
377
- }
378
- const regexp = (0, import_path_to_regexp.pathToRegexp)(path6);
379
- regexpCache.set(path6, regexp);
380
- return regexp;
381
- };
382
- var MicrofrontendConfigClient = class {
383
- constructor(config, opts) {
384
- this.pathCache = {};
385
- this.serialized = config;
386
- if (opts?.removeFlaggedPaths) {
387
- for (const app of Object.values(config.applications)) {
388
- if (app.routing) {
389
- app.routing = app.routing.filter((match) => !match.flag);
390
- }
391
- }
392
- }
393
- this.applications = config.applications;
394
- }
395
- /**
396
- * Create a new `MicrofrontendConfigClient` from a JSON string.
397
- * Config must be passed in to remain framework agnostic
398
- */
399
- static fromEnv(config, opts) {
400
- if (!config) {
401
- throw new Error(
402
- "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
403
- );
404
- }
405
- return new MicrofrontendConfigClient(
406
- JSON.parse(config),
407
- opts
408
- );
409
- }
410
- isEqual(other) {
411
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
412
- }
413
- getApplicationNameForPath(path6) {
414
- if (!path6.startsWith("/")) {
415
- throw new Error(`Path must start with a /`);
416
- }
417
- if (this.pathCache[path6]) {
418
- return this.pathCache[path6];
419
- }
420
- const pathname = new URL(path6, "https://example.com").pathname;
421
- for (const [name, application] of Object.entries(this.applications)) {
422
- if (application.routing) {
423
- for (const group of application.routing) {
424
- for (const childPath of group.paths) {
425
- const regexp = getRegexp(childPath);
426
- if (regexp.test(pathname)) {
427
- this.pathCache[path6] = name;
428
- return name;
429
- }
430
- }
431
- }
432
- }
433
- }
434
- const defaultApplication = Object.entries(this.applications).find(
435
- ([, application]) => application.default
436
- );
437
- if (!defaultApplication) {
438
- return null;
439
- }
440
- this.pathCache[path6] = defaultApplication[0];
441
- return defaultApplication[0];
442
- }
443
- serialize() {
444
- return this.serialized;
445
- }
446
- };
447
-
448
390
  // src/config/microfrontends-config/isomorphic/validation.ts
449
- var import_path_to_regexp2 = require("path-to-regexp");
391
+ var import_path_to_regexp = require("path-to-regexp");
450
392
  var LIST_FORMATTER = new Intl.ListFormat("en", {
451
393
  style: "long",
452
394
  type: "conjunction"
453
395
  });
396
+ var VALID_ASSET_PREFIX_REGEXP = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
454
397
  var validateConfigPaths = (applicationConfigsById) => {
455
398
  if (!applicationConfigsById) {
456
399
  return;
@@ -473,7 +416,7 @@ var validateConfigPaths = (applicationConfigsById) => {
473
416
  } else {
474
417
  pathsByApplicationId.set(path6, {
475
418
  applications: [id],
476
- matcher: (0, import_path_to_regexp2.pathToRegexp)(path6),
419
+ matcher: (0, import_path_to_regexp.pathToRegexp)(path6),
477
420
  applicationId: id
478
421
  });
479
422
  }
@@ -520,7 +463,7 @@ var validateConfigPaths = (applicationConfigsById) => {
520
463
  var PATH_DEFAULT_PATTERN = "[^\\/#\\?]+?";
521
464
  function validatePathExpression(path6) {
522
465
  try {
523
- const tokens = (0, import_path_to_regexp2.parse)(path6);
466
+ const tokens = (0, import_path_to_regexp.parse)(path6);
524
467
  if (/(?<!\\)\{/.test(path6)) {
525
468
  return `Optional paths are not supported: ${path6}`;
526
469
  }
@@ -577,6 +520,22 @@ var validateAppPaths = (name, app) => {
577
520
  }
578
521
  }
579
522
  }
523
+ if (app.assetPrefix) {
524
+ if (!VALID_ASSET_PREFIX_REGEXP.test(app.assetPrefix)) {
525
+ throw new MicrofrontendError(
526
+ `Invalid asset prefix for application "${name}". ${app.assetPrefix} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
527
+ { type: "application", subtype: "invalid_asset_prefix" }
528
+ );
529
+ }
530
+ if (app.assetPrefix !== `vc-ap-${name}` && !app.routing.some(
531
+ (group) => group.paths.includes(`/${app.assetPrefix}/:path*`) && !group.flag
532
+ )) {
533
+ throw new MicrofrontendError(
534
+ `When \`assetPrefix\` is specified, \`/${app.assetPrefix}/:path*\` must be added the routing paths for the application. Changing the asset prefix is not a forwards and backwards compatible change, and the custom asset prefix should be added to \`paths\` and deployed before setting the \`assetPrefix\` field.`,
535
+ { type: "application", subtype: "invalid_asset_prefix" }
536
+ );
537
+ }
538
+ }
580
539
  };
581
540
  var validateConfigDefaultApplication = (applicationConfigsById) => {
582
541
  if (!applicationConfigsById) {
@@ -608,6 +567,15 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
608
567
  }
609
568
  };
610
569
 
570
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
571
+ var import_md5 = __toESM(require("md5"), 1);
572
+ function hashApplicationName(name) {
573
+ if (!name) {
574
+ throw new Error("Application name is required to generate hash");
575
+ }
576
+ return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0");
577
+ }
578
+
611
579
  // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
612
580
  var PREFIX = "vc-ap";
613
581
  function generateAssetPrefixFromName({
@@ -616,7 +584,7 @@ function generateAssetPrefixFromName({
616
584
  if (!name) {
617
585
  throw new Error("Name is required to generate an asset prefix");
618
586
  }
619
- return `${PREFIX}-${name}`;
587
+ return `${PREFIX}-${hashApplicationName(name)}`;
620
588
  }
621
589
 
622
590
  // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
@@ -776,7 +744,13 @@ var Application = class {
776
744
  return this.default;
777
745
  }
778
746
  getAssetPrefix() {
779
- return generateAssetPrefixFromName({ name: this.name });
747
+ const generatedAssetPrefix = generateAssetPrefixFromName({
748
+ name: this.name
749
+ });
750
+ if ("assetPrefix" in this.serialized) {
751
+ return this.serialized.assetPrefix ?? generatedAssetPrefix;
752
+ }
753
+ return generatedAssetPrefix;
780
754
  }
781
755
  getAutomationBypassEnvVarName() {
782
756
  return generateAutomationBypassEnvVarName({ name: this.name });
@@ -910,7 +884,7 @@ var MicrofrontendConfigIsomorphic = class {
910
884
  );
911
885
  if (!app) {
912
886
  throw new MicrofrontendError(
913
- `Could not find microfrontends configuration for application "${name}"`,
887
+ `Could not find microfrontends configuration for application "${name}". If the name in package.json differs from your Vercel Project name, set the \`packageName\` field for the application in \`microfrontends.json\` to ensure that the configuration can be found locally.`,
914
888
  {
915
889
  type: "application",
916
890
  subtype: "not_found"
@@ -955,23 +929,6 @@ var MicrofrontendConfigIsomorphic = class {
955
929
  toSchemaJson() {
956
930
  return this.serialized.config;
957
931
  }
958
- toClientConfig() {
959
- const applications = Object.fromEntries(
960
- Object.entries(this.childApplications).map(([name, application]) => [
961
- name,
962
- {
963
- default: false,
964
- routing: application.routing
965
- }
966
- ])
967
- );
968
- applications[this.defaultApplication.name] = {
969
- default: true
970
- };
971
- return new MicrofrontendConfigClient({
972
- applications
973
- });
974
- }
975
932
  serialize() {
976
933
  return this.serialized;
977
934
  }
@@ -984,8 +941,31 @@ function getApplicationContext(opts) {
984
941
  if (opts?.appName) {
985
942
  return { name: opts.appName };
986
943
  }
944
+ if (process.env.VERCEL_PROJECT_NAME) {
945
+ return {
946
+ name: process.env.VERCEL_PROJECT_NAME,
947
+ projectName: process.env.VERCEL_PROJECT_NAME
948
+ };
949
+ }
987
950
  if (process.env.NX_TASK_TARGET_PROJECT) {
988
- return { name: process.env.NX_TASK_TARGET_PROJECT };
951
+ return {
952
+ name: process.env.NX_TASK_TARGET_PROJECT,
953
+ packageJsonName: process.env.NX_TASK_TARGET_PROJECT
954
+ };
955
+ }
956
+ try {
957
+ const vercelProjectJsonPath = import_node_fs6.default.readFileSync(
958
+ import_node_path6.default.join(opts?.packageRoot || ".", ".vercel", "project.json"),
959
+ "utf-8"
960
+ );
961
+ const projectJson = JSON.parse(vercelProjectJsonPath);
962
+ if (projectJson.projectName) {
963
+ return {
964
+ name: projectJson.projectName,
965
+ projectName: projectJson.projectName
966
+ };
967
+ }
968
+ } catch (_) {
989
969
  }
990
970
  try {
991
971
  const packageJsonString = import_node_fs6.default.readFileSync(
@@ -1003,7 +983,7 @@ function getApplicationContext(opts) {
1003
983
  }
1004
984
  );
1005
985
  }
1006
- return { name: packageJson.name };
986
+ return { name: packageJson.name, packageJsonName: packageJson.name };
1007
987
  } catch (err) {
1008
988
  throw MicrofrontendError.handle(err, {
1009
989
  fileName: "package.json"
@@ -1143,6 +1123,10 @@ var schema_default = {
1143
1123
  routing: {
1144
1124
  $ref: "#/definitions/Routing",
1145
1125
  description: "Groups of path expressions that are routed to this application."
1126
+ },
1127
+ assetPrefix: {
1128
+ type: "string",
1129
+ description: "The name of the asset prefix to use instead of the auto-generated name.\n\nThe asset prefix is used to prefix all paths to static assets, such as JS, CSS, or images that are served by a specific application. It is necessary to ensure there are no conflicts with other applications on the same domain.\n\nAn auto-generated asset prefix of the form `vc-ap-<hash>` is used when this field is not provided.\n\nWhen this field is provided, `/${assetPrefix}/:path*` must also be added to the list of paths in the `routing` field. Changing the asset prefix after a microfrontend application has already been deployed is not a forwards and backwards compatible change, and the asset prefix should be added to the `routing` field and deployed before setting the `assetPrefix` field."
1146
1130
  }
1147
1131
  },
1148
1132
  required: [
@@ -1368,7 +1352,7 @@ var MicrofrontendsServer = class {
1368
1352
  }
1369
1353
  try {
1370
1354
  const packageRoot = findPackageRoot(directory);
1371
- const { name: appName } = getApplicationContext({ packageRoot });
1355
+ const applicationContext = getApplicationContext({ packageRoot });
1372
1356
  const maybeConfig = findConfig({ dir: packageRoot });
1373
1357
  if (maybeConfig) {
1374
1358
  return MicrofrontendsServer.fromFile({
@@ -1402,7 +1386,7 @@ var MicrofrontendsServer = class {
1402
1386
  if (isMonorepo2) {
1403
1387
  const defaultPackage = inferMicrofrontendsLocation({
1404
1388
  repositoryRoot,
1405
- applicationName: appName
1389
+ applicationContext
1406
1390
  });
1407
1391
  const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
1408
1392
  if (maybeConfigFromDefault) {