@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
@@ -206,8 +206,9 @@ var CONFIGURATION_FILENAMES = [
206
206
  var configCache = {};
207
207
  function findPackageWithMicrofrontendsConfig({
208
208
  repositoryRoot,
209
- applicationName
209
+ applicationContext
210
210
  }) {
211
+ const applicationName = applicationContext.name;
211
212
  try {
212
213
  const microfrontendsJsonPaths = fg.globSync(
213
214
  `**/{${CONFIGURATION_FILENAMES.join(",")}}`,
@@ -249,26 +250,45 @@ ${matchingPaths.join("\n \u2022 ")}`,
249
250
  );
250
251
  }
251
252
  if (matchingPaths.length === 0) {
253
+ let additionalErrorMessage = "";
254
+ if (microfrontendsJsonPaths.length > 0) {
255
+ if (!applicationContext.projectName) {
256
+ additionalErrorMessage = `
257
+
258
+ 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.`;
259
+ } else {
260
+ additionalErrorMessage = `
261
+
262
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
263
+ }
264
+ }
252
265
  throw new MicrofrontendError(
253
- `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.`,
266
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
267
+
268
+ 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.
269
+
270
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
254
271
  { type: "config", subtype: "inference_failed" }
255
272
  );
256
273
  }
257
274
  const [packageJsonPath] = matchingPaths;
258
275
  return dirname(packageJsonPath);
259
276
  } catch (error) {
277
+ if (error instanceof MicrofrontendError) {
278
+ throw error;
279
+ }
260
280
  return null;
261
281
  }
262
282
  }
263
283
  function inferMicrofrontendsLocation(opts) {
264
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationName}`;
284
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
265
285
  if (configCache[cacheKey]) {
266
286
  return configCache[cacheKey];
267
287
  }
268
288
  const result = findPackageWithMicrofrontendsConfig(opts);
269
289
  if (!result) {
270
290
  throw new MicrofrontendError(
271
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationName}" starting in directory "${opts.repositoryRoot}".`,
291
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
272
292
  { type: "config", subtype: "inference_failed" }
273
293
  );
274
294
  }
@@ -340,90 +360,13 @@ function findConfig({ dir }) {
340
360
  // src/config/microfrontends-config/isomorphic/index.ts
341
361
  import { parse as parse2 } from "jsonc-parser";
342
362
 
343
- // src/config/microfrontends-config/client/index.ts
344
- import { pathToRegexp } from "path-to-regexp";
345
- var regexpCache = /* @__PURE__ */ new Map();
346
- var getRegexp = (path6) => {
347
- const existing = regexpCache.get(path6);
348
- if (existing) {
349
- return existing;
350
- }
351
- const regexp = pathToRegexp(path6);
352
- regexpCache.set(path6, regexp);
353
- return regexp;
354
- };
355
- var MicrofrontendConfigClient = class {
356
- constructor(config, opts) {
357
- this.pathCache = {};
358
- this.serialized = config;
359
- if (opts?.removeFlaggedPaths) {
360
- for (const app of Object.values(config.applications)) {
361
- if (app.routing) {
362
- app.routing = app.routing.filter((match) => !match.flag);
363
- }
364
- }
365
- }
366
- this.applications = config.applications;
367
- }
368
- /**
369
- * Create a new `MicrofrontendConfigClient` from a JSON string.
370
- * Config must be passed in to remain framework agnostic
371
- */
372
- static fromEnv(config, opts) {
373
- if (!config) {
374
- throw new Error(
375
- "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
376
- );
377
- }
378
- return new MicrofrontendConfigClient(
379
- JSON.parse(config),
380
- opts
381
- );
382
- }
383
- isEqual(other) {
384
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
385
- }
386
- getApplicationNameForPath(path6) {
387
- if (!path6.startsWith("/")) {
388
- throw new Error(`Path must start with a /`);
389
- }
390
- if (this.pathCache[path6]) {
391
- return this.pathCache[path6];
392
- }
393
- const pathname = new URL(path6, "https://example.com").pathname;
394
- for (const [name, application] of Object.entries(this.applications)) {
395
- if (application.routing) {
396
- for (const group of application.routing) {
397
- for (const childPath of group.paths) {
398
- const regexp = getRegexp(childPath);
399
- if (regexp.test(pathname)) {
400
- this.pathCache[path6] = name;
401
- return name;
402
- }
403
- }
404
- }
405
- }
406
- }
407
- const defaultApplication = Object.entries(this.applications).find(
408
- ([, application]) => application.default
409
- );
410
- if (!defaultApplication) {
411
- return null;
412
- }
413
- this.pathCache[path6] = defaultApplication[0];
414
- return defaultApplication[0];
415
- }
416
- serialize() {
417
- return this.serialized;
418
- }
419
- };
420
-
421
363
  // src/config/microfrontends-config/isomorphic/validation.ts
422
- import { pathToRegexp as pathToRegexp2, parse as parsePathRegexp } from "path-to-regexp";
364
+ import { pathToRegexp, parse as parsePathRegexp } from "path-to-regexp";
423
365
  var LIST_FORMATTER = new Intl.ListFormat("en", {
424
366
  style: "long",
425
367
  type: "conjunction"
426
368
  });
369
+ var VALID_ASSET_PREFIX_REGEXP = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
427
370
  var validateConfigPaths = (applicationConfigsById) => {
428
371
  if (!applicationConfigsById) {
429
372
  return;
@@ -446,7 +389,7 @@ var validateConfigPaths = (applicationConfigsById) => {
446
389
  } else {
447
390
  pathsByApplicationId.set(path6, {
448
391
  applications: [id],
449
- matcher: pathToRegexp2(path6),
392
+ matcher: pathToRegexp(path6),
450
393
  applicationId: id
451
394
  });
452
395
  }
@@ -550,6 +493,22 @@ var validateAppPaths = (name, app) => {
550
493
  }
551
494
  }
552
495
  }
496
+ if (app.assetPrefix) {
497
+ if (!VALID_ASSET_PREFIX_REGEXP.test(app.assetPrefix)) {
498
+ throw new MicrofrontendError(
499
+ `Invalid asset prefix for application "${name}". ${app.assetPrefix} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
500
+ { type: "application", subtype: "invalid_asset_prefix" }
501
+ );
502
+ }
503
+ if (app.assetPrefix !== `vc-ap-${name}` && !app.routing.some(
504
+ (group) => group.paths.includes(`/${app.assetPrefix}/:path*`) && !group.flag
505
+ )) {
506
+ throw new MicrofrontendError(
507
+ `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.`,
508
+ { type: "application", subtype: "invalid_asset_prefix" }
509
+ );
510
+ }
511
+ }
553
512
  };
554
513
  var validateConfigDefaultApplication = (applicationConfigsById) => {
555
514
  if (!applicationConfigsById) {
@@ -581,6 +540,15 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
581
540
  }
582
541
  };
583
542
 
543
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
544
+ import md5 from "md5";
545
+ function hashApplicationName(name) {
546
+ if (!name) {
547
+ throw new Error("Application name is required to generate hash");
548
+ }
549
+ return md5(name).substring(0, 6).padStart(6, "0");
550
+ }
551
+
584
552
  // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
585
553
  var PREFIX = "vc-ap";
586
554
  function generateAssetPrefixFromName({
@@ -589,7 +557,7 @@ function generateAssetPrefixFromName({
589
557
  if (!name) {
590
558
  throw new Error("Name is required to generate an asset prefix");
591
559
  }
592
- return `${PREFIX}-${name}`;
560
+ return `${PREFIX}-${hashApplicationName(name)}`;
593
561
  }
594
562
 
595
563
  // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
@@ -749,7 +717,13 @@ var Application = class {
749
717
  return this.default;
750
718
  }
751
719
  getAssetPrefix() {
752
- return generateAssetPrefixFromName({ name: this.name });
720
+ const generatedAssetPrefix = generateAssetPrefixFromName({
721
+ name: this.name
722
+ });
723
+ if ("assetPrefix" in this.serialized) {
724
+ return this.serialized.assetPrefix ?? generatedAssetPrefix;
725
+ }
726
+ return generatedAssetPrefix;
753
727
  }
754
728
  getAutomationBypassEnvVarName() {
755
729
  return generateAutomationBypassEnvVarName({ name: this.name });
@@ -883,7 +857,7 @@ var MicrofrontendConfigIsomorphic = class {
883
857
  );
884
858
  if (!app) {
885
859
  throw new MicrofrontendError(
886
- `Could not find microfrontends configuration for application "${name}"`,
860
+ `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.`,
887
861
  {
888
862
  type: "application",
889
863
  subtype: "not_found"
@@ -928,23 +902,6 @@ var MicrofrontendConfigIsomorphic = class {
928
902
  toSchemaJson() {
929
903
  return this.serialized.config;
930
904
  }
931
- toClientConfig() {
932
- const applications = Object.fromEntries(
933
- Object.entries(this.childApplications).map(([name, application]) => [
934
- name,
935
- {
936
- default: false,
937
- routing: application.routing
938
- }
939
- ])
940
- );
941
- applications[this.defaultApplication.name] = {
942
- default: true
943
- };
944
- return new MicrofrontendConfigClient({
945
- applications
946
- });
947
- }
948
905
  serialize() {
949
906
  return this.serialized;
950
907
  }
@@ -957,8 +914,31 @@ function getApplicationContext(opts) {
957
914
  if (opts?.appName) {
958
915
  return { name: opts.appName };
959
916
  }
917
+ if (process.env.VERCEL_PROJECT_NAME) {
918
+ return {
919
+ name: process.env.VERCEL_PROJECT_NAME,
920
+ projectName: process.env.VERCEL_PROJECT_NAME
921
+ };
922
+ }
960
923
  if (process.env.NX_TASK_TARGET_PROJECT) {
961
- return { name: process.env.NX_TASK_TARGET_PROJECT };
924
+ return {
925
+ name: process.env.NX_TASK_TARGET_PROJECT,
926
+ packageJsonName: process.env.NX_TASK_TARGET_PROJECT
927
+ };
928
+ }
929
+ try {
930
+ const vercelProjectJsonPath = fs5.readFileSync(
931
+ path4.join(opts?.packageRoot || ".", ".vercel", "project.json"),
932
+ "utf-8"
933
+ );
934
+ const projectJson = JSON.parse(vercelProjectJsonPath);
935
+ if (projectJson.projectName) {
936
+ return {
937
+ name: projectJson.projectName,
938
+ projectName: projectJson.projectName
939
+ };
940
+ }
941
+ } catch (_) {
962
942
  }
963
943
  try {
964
944
  const packageJsonString = fs5.readFileSync(
@@ -976,7 +956,7 @@ function getApplicationContext(opts) {
976
956
  }
977
957
  );
978
958
  }
979
- return { name: packageJson.name };
959
+ return { name: packageJson.name, packageJsonName: packageJson.name };
980
960
  } catch (err) {
981
961
  throw MicrofrontendError.handle(err, {
982
962
  fileName: "package.json"
@@ -1116,6 +1096,10 @@ var schema_default = {
1116
1096
  routing: {
1117
1097
  $ref: "#/definitions/Routing",
1118
1098
  description: "Groups of path expressions that are routed to this application."
1099
+ },
1100
+ assetPrefix: {
1101
+ type: "string",
1102
+ 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."
1119
1103
  }
1120
1104
  },
1121
1105
  required: [
@@ -1341,7 +1325,7 @@ var MicrofrontendsServer = class {
1341
1325
  }
1342
1326
  try {
1343
1327
  const packageRoot = findPackageRoot(directory);
1344
- const { name: appName } = getApplicationContext({ packageRoot });
1328
+ const applicationContext = getApplicationContext({ packageRoot });
1345
1329
  const maybeConfig = findConfig({ dir: packageRoot });
1346
1330
  if (maybeConfig) {
1347
1331
  return MicrofrontendsServer.fromFile({
@@ -1375,7 +1359,7 @@ var MicrofrontendsServer = class {
1375
1359
  if (isMonorepo2) {
1376
1360
  const defaultPackage = inferMicrofrontendsLocation({
1377
1361
  repositoryRoot,
1378
- applicationName: appName
1362
+ applicationContext
1379
1363
  });
1380
1364
  const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
1381
1365
  if (maybeConfigFromDefault) {