@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
@@ -235,8 +235,9 @@ var CONFIGURATION_FILENAMES = [
235
235
  var configCache = {};
236
236
  function findPackageWithMicrofrontendsConfig({
237
237
  repositoryRoot,
238
- applicationName
238
+ applicationContext
239
239
  }) {
240
+ const applicationName = applicationContext.name;
240
241
  try {
241
242
  const microfrontendsJsonPaths = import_fast_glob.default.globSync(
242
243
  `**/{${CONFIGURATION_FILENAMES.join(",")}}`,
@@ -278,26 +279,45 @@ ${matchingPaths.join("\n \u2022 ")}`,
278
279
  );
279
280
  }
280
281
  if (matchingPaths.length === 0) {
282
+ let additionalErrorMessage = "";
283
+ if (microfrontendsJsonPaths.length > 0) {
284
+ if (!applicationContext.projectName) {
285
+ additionalErrorMessage = `
286
+
287
+ 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.`;
288
+ } else {
289
+ additionalErrorMessage = `
290
+
291
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
292
+ }
293
+ }
281
294
  throw new MicrofrontendError(
282
- `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.`,
295
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
296
+
297
+ 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.
298
+
299
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
283
300
  { type: "config", subtype: "inference_failed" }
284
301
  );
285
302
  }
286
303
  const [packageJsonPath] = matchingPaths;
287
304
  return (0, import_node_path2.dirname)(packageJsonPath);
288
305
  } catch (error) {
306
+ if (error instanceof MicrofrontendError) {
307
+ throw error;
308
+ }
289
309
  return null;
290
310
  }
291
311
  }
292
312
  function inferMicrofrontendsLocation(opts) {
293
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationName}`;
313
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
294
314
  if (configCache[cacheKey]) {
295
315
  return configCache[cacheKey];
296
316
  }
297
317
  const result = findPackageWithMicrofrontendsConfig(opts);
298
318
  if (!result) {
299
319
  throw new MicrofrontendError(
300
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationName}" starting in directory "${opts.repositoryRoot}".`,
320
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
301
321
  { type: "config", subtype: "inference_failed" }
302
322
  );
303
323
  }
@@ -369,90 +389,13 @@ function findConfig({ dir }) {
369
389
  // src/config/microfrontends-config/isomorphic/index.ts
370
390
  var import_jsonc_parser2 = require("jsonc-parser");
371
391
 
372
- // src/config/microfrontends-config/client/index.ts
373
- var import_path_to_regexp = require("path-to-regexp");
374
- var regexpCache = /* @__PURE__ */ new Map();
375
- var getRegexp = (path7) => {
376
- const existing = regexpCache.get(path7);
377
- if (existing) {
378
- return existing;
379
- }
380
- const regexp = (0, import_path_to_regexp.pathToRegexp)(path7);
381
- regexpCache.set(path7, regexp);
382
- return regexp;
383
- };
384
- var MicrofrontendConfigClient = class {
385
- constructor(config, opts) {
386
- this.pathCache = {};
387
- this.serialized = config;
388
- if (opts?.removeFlaggedPaths) {
389
- for (const app of Object.values(config.applications)) {
390
- if (app.routing) {
391
- app.routing = app.routing.filter((match) => !match.flag);
392
- }
393
- }
394
- }
395
- this.applications = config.applications;
396
- }
397
- /**
398
- * Create a new `MicrofrontendConfigClient` from a JSON string.
399
- * Config must be passed in to remain framework agnostic
400
- */
401
- static fromEnv(config, opts) {
402
- if (!config) {
403
- throw new Error(
404
- "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
405
- );
406
- }
407
- return new MicrofrontendConfigClient(
408
- JSON.parse(config),
409
- opts
410
- );
411
- }
412
- isEqual(other) {
413
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
414
- }
415
- getApplicationNameForPath(path7) {
416
- if (!path7.startsWith("/")) {
417
- throw new Error(`Path must start with a /`);
418
- }
419
- if (this.pathCache[path7]) {
420
- return this.pathCache[path7];
421
- }
422
- const pathname = new URL(path7, "https://example.com").pathname;
423
- for (const [name, application] of Object.entries(this.applications)) {
424
- if (application.routing) {
425
- for (const group of application.routing) {
426
- for (const childPath of group.paths) {
427
- const regexp = getRegexp(childPath);
428
- if (regexp.test(pathname)) {
429
- this.pathCache[path7] = name;
430
- return name;
431
- }
432
- }
433
- }
434
- }
435
- }
436
- const defaultApplication = Object.entries(this.applications).find(
437
- ([, application]) => application.default
438
- );
439
- if (!defaultApplication) {
440
- return null;
441
- }
442
- this.pathCache[path7] = defaultApplication[0];
443
- return defaultApplication[0];
444
- }
445
- serialize() {
446
- return this.serialized;
447
- }
448
- };
449
-
450
392
  // src/config/microfrontends-config/isomorphic/validation.ts
451
- var import_path_to_regexp2 = require("path-to-regexp");
393
+ var import_path_to_regexp = require("path-to-regexp");
452
394
  var LIST_FORMATTER = new Intl.ListFormat("en", {
453
395
  style: "long",
454
396
  type: "conjunction"
455
397
  });
398
+ var VALID_ASSET_PREFIX_REGEXP = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
456
399
  var validateConfigPaths = (applicationConfigsById) => {
457
400
  if (!applicationConfigsById) {
458
401
  return;
@@ -475,7 +418,7 @@ var validateConfigPaths = (applicationConfigsById) => {
475
418
  } else {
476
419
  pathsByApplicationId.set(path7, {
477
420
  applications: [id],
478
- matcher: (0, import_path_to_regexp2.pathToRegexp)(path7),
421
+ matcher: (0, import_path_to_regexp.pathToRegexp)(path7),
479
422
  applicationId: id
480
423
  });
481
424
  }
@@ -522,7 +465,7 @@ var validateConfigPaths = (applicationConfigsById) => {
522
465
  var PATH_DEFAULT_PATTERN = "[^\\/#\\?]+?";
523
466
  function validatePathExpression(path7) {
524
467
  try {
525
- const tokens = (0, import_path_to_regexp2.parse)(path7);
468
+ const tokens = (0, import_path_to_regexp.parse)(path7);
526
469
  if (/(?<!\\)\{/.test(path7)) {
527
470
  return `Optional paths are not supported: ${path7}`;
528
471
  }
@@ -579,6 +522,22 @@ var validateAppPaths = (name, app) => {
579
522
  }
580
523
  }
581
524
  }
525
+ if (app.assetPrefix) {
526
+ if (!VALID_ASSET_PREFIX_REGEXP.test(app.assetPrefix)) {
527
+ throw new MicrofrontendError(
528
+ `Invalid asset prefix for application "${name}". ${app.assetPrefix} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
529
+ { type: "application", subtype: "invalid_asset_prefix" }
530
+ );
531
+ }
532
+ if (app.assetPrefix !== `vc-ap-${name}` && !app.routing.some(
533
+ (group) => group.paths.includes(`/${app.assetPrefix}/:path*`) && !group.flag
534
+ )) {
535
+ throw new MicrofrontendError(
536
+ `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.`,
537
+ { type: "application", subtype: "invalid_asset_prefix" }
538
+ );
539
+ }
540
+ }
582
541
  };
583
542
  var validateConfigDefaultApplication = (applicationConfigsById) => {
584
543
  if (!applicationConfigsById) {
@@ -610,6 +569,15 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
610
569
  }
611
570
  };
612
571
 
572
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
573
+ var import_md5 = __toESM(require("md5"), 1);
574
+ function hashApplicationName(name) {
575
+ if (!name) {
576
+ throw new Error("Application name is required to generate hash");
577
+ }
578
+ return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0");
579
+ }
580
+
613
581
  // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
614
582
  var PREFIX = "vc-ap";
615
583
  function generateAssetPrefixFromName({
@@ -618,7 +586,7 @@ function generateAssetPrefixFromName({
618
586
  if (!name) {
619
587
  throw new Error("Name is required to generate an asset prefix");
620
588
  }
621
- return `${PREFIX}-${name}`;
589
+ return `${PREFIX}-${hashApplicationName(name)}`;
622
590
  }
623
591
 
624
592
  // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
@@ -778,7 +746,13 @@ var Application = class {
778
746
  return this.default;
779
747
  }
780
748
  getAssetPrefix() {
781
- return generateAssetPrefixFromName({ name: this.name });
749
+ const generatedAssetPrefix = generateAssetPrefixFromName({
750
+ name: this.name
751
+ });
752
+ if ("assetPrefix" in this.serialized) {
753
+ return this.serialized.assetPrefix ?? generatedAssetPrefix;
754
+ }
755
+ return generatedAssetPrefix;
782
756
  }
783
757
  getAutomationBypassEnvVarName() {
784
758
  return generateAutomationBypassEnvVarName({ name: this.name });
@@ -912,7 +886,7 @@ var MicrofrontendConfigIsomorphic = class {
912
886
  );
913
887
  if (!app) {
914
888
  throw new MicrofrontendError(
915
- `Could not find microfrontends configuration for application "${name}"`,
889
+ `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.`,
916
890
  {
917
891
  type: "application",
918
892
  subtype: "not_found"
@@ -957,23 +931,6 @@ var MicrofrontendConfigIsomorphic = class {
957
931
  toSchemaJson() {
958
932
  return this.serialized.config;
959
933
  }
960
- toClientConfig() {
961
- const applications = Object.fromEntries(
962
- Object.entries(this.childApplications).map(([name, application]) => [
963
- name,
964
- {
965
- default: false,
966
- routing: application.routing
967
- }
968
- ])
969
- );
970
- applications[this.defaultApplication.name] = {
971
- default: true
972
- };
973
- return new MicrofrontendConfigClient({
974
- applications
975
- });
976
- }
977
934
  serialize() {
978
935
  return this.serialized;
979
936
  }
@@ -986,8 +943,31 @@ function getApplicationContext(opts) {
986
943
  if (opts?.appName) {
987
944
  return { name: opts.appName };
988
945
  }
946
+ if (process.env.VERCEL_PROJECT_NAME) {
947
+ return {
948
+ name: process.env.VERCEL_PROJECT_NAME,
949
+ projectName: process.env.VERCEL_PROJECT_NAME
950
+ };
951
+ }
989
952
  if (process.env.NX_TASK_TARGET_PROJECT) {
990
- return { name: process.env.NX_TASK_TARGET_PROJECT };
953
+ return {
954
+ name: process.env.NX_TASK_TARGET_PROJECT,
955
+ packageJsonName: process.env.NX_TASK_TARGET_PROJECT
956
+ };
957
+ }
958
+ try {
959
+ const vercelProjectJsonPath = import_node_fs6.default.readFileSync(
960
+ import_node_path6.default.join(opts?.packageRoot || ".", ".vercel", "project.json"),
961
+ "utf-8"
962
+ );
963
+ const projectJson = JSON.parse(vercelProjectJsonPath);
964
+ if (projectJson.projectName) {
965
+ return {
966
+ name: projectJson.projectName,
967
+ projectName: projectJson.projectName
968
+ };
969
+ }
970
+ } catch (_) {
991
971
  }
992
972
  try {
993
973
  const packageJsonString = import_node_fs6.default.readFileSync(
@@ -1005,7 +985,7 @@ function getApplicationContext(opts) {
1005
985
  }
1006
986
  );
1007
987
  }
1008
- return { name: packageJson.name };
988
+ return { name: packageJson.name, packageJsonName: packageJson.name };
1009
989
  } catch (err) {
1010
990
  throw MicrofrontendError.handle(err, {
1011
991
  fileName: "package.json"
@@ -1145,6 +1125,10 @@ var schema_default = {
1145
1125
  routing: {
1146
1126
  $ref: "#/definitions/Routing",
1147
1127
  description: "Groups of path expressions that are routed to this application."
1128
+ },
1129
+ assetPrefix: {
1130
+ type: "string",
1131
+ 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."
1148
1132
  }
1149
1133
  },
1150
1134
  required: [
@@ -1370,7 +1354,7 @@ var MicrofrontendsServer = class {
1370
1354
  }
1371
1355
  try {
1372
1356
  const packageRoot = findPackageRoot(directory);
1373
- const { name: appName } = getApplicationContext({ packageRoot });
1357
+ const applicationContext = getApplicationContext({ packageRoot });
1374
1358
  const maybeConfig = findConfig({ dir: packageRoot });
1375
1359
  if (maybeConfig) {
1376
1360
  return MicrofrontendsServer.fromFile({
@@ -1404,7 +1388,7 @@ var MicrofrontendsServer = class {
1404
1388
  if (isMonorepo2) {
1405
1389
  const defaultPackage = inferMicrofrontendsLocation({
1406
1390
  repositoryRoot,
1407
- applicationName: appName
1391
+ applicationContext
1408
1392
  });
1409
1393
  const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
1410
1394
  if (maybeConfigFromDefault) {