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