@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
@@ -201,8 +201,9 @@ var CONFIGURATION_FILENAMES = [
201
201
  var configCache = {};
202
202
  function findPackageWithMicrofrontendsConfig({
203
203
  repositoryRoot,
204
- applicationName
204
+ applicationContext
205
205
  }) {
206
+ const applicationName = applicationContext.name;
206
207
  try {
207
208
  const microfrontendsJsonPaths = fg.globSync(
208
209
  `**/{${CONFIGURATION_FILENAMES.join(",")}}`,
@@ -244,26 +245,45 @@ ${matchingPaths.join("\n \u2022 ")}`,
244
245
  );
245
246
  }
246
247
  if (matchingPaths.length === 0) {
248
+ let additionalErrorMessage = "";
249
+ if (microfrontendsJsonPaths.length > 0) {
250
+ if (!applicationContext.projectName) {
251
+ additionalErrorMessage = `
252
+
253
+ 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.`;
254
+ } else {
255
+ additionalErrorMessage = `
256
+
257
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
258
+ }
259
+ }
247
260
  throw new MicrofrontendError(
248
- `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.`,
261
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
262
+
263
+ 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.
264
+
265
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
249
266
  { type: "config", subtype: "inference_failed" }
250
267
  );
251
268
  }
252
269
  const [packageJsonPath] = matchingPaths;
253
270
  return dirname(packageJsonPath);
254
271
  } catch (error) {
272
+ if (error instanceof MicrofrontendError) {
273
+ throw error;
274
+ }
255
275
  return null;
256
276
  }
257
277
  }
258
278
  function inferMicrofrontendsLocation(opts) {
259
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationName}`;
279
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
260
280
  if (configCache[cacheKey]) {
261
281
  return configCache[cacheKey];
262
282
  }
263
283
  const result = findPackageWithMicrofrontendsConfig(opts);
264
284
  if (!result) {
265
285
  throw new MicrofrontendError(
266
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationName}" starting in directory "${opts.repositoryRoot}".`,
286
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
267
287
  { type: "config", subtype: "inference_failed" }
268
288
  );
269
289
  }
@@ -335,90 +355,13 @@ function findConfig({ dir }) {
335
355
  // src/config/microfrontends-config/isomorphic/index.ts
336
356
  import { parse as parse2 } from "jsonc-parser";
337
357
 
338
- // src/config/microfrontends-config/client/index.ts
339
- import { pathToRegexp } from "path-to-regexp";
340
- var regexpCache = /* @__PURE__ */ new Map();
341
- var getRegexp = (path7) => {
342
- const existing = regexpCache.get(path7);
343
- if (existing) {
344
- return existing;
345
- }
346
- const regexp = pathToRegexp(path7);
347
- regexpCache.set(path7, regexp);
348
- return regexp;
349
- };
350
- var MicrofrontendConfigClient = class {
351
- constructor(config, opts) {
352
- this.pathCache = {};
353
- this.serialized = config;
354
- if (opts?.removeFlaggedPaths) {
355
- for (const app of Object.values(config.applications)) {
356
- if (app.routing) {
357
- app.routing = app.routing.filter((match) => !match.flag);
358
- }
359
- }
360
- }
361
- this.applications = config.applications;
362
- }
363
- /**
364
- * Create a new `MicrofrontendConfigClient` from a JSON string.
365
- * Config must be passed in to remain framework agnostic
366
- */
367
- static fromEnv(config, opts) {
368
- if (!config) {
369
- throw new Error(
370
- "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
371
- );
372
- }
373
- return new MicrofrontendConfigClient(
374
- JSON.parse(config),
375
- opts
376
- );
377
- }
378
- isEqual(other) {
379
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
380
- }
381
- getApplicationNameForPath(path7) {
382
- if (!path7.startsWith("/")) {
383
- throw new Error(`Path must start with a /`);
384
- }
385
- if (this.pathCache[path7]) {
386
- return this.pathCache[path7];
387
- }
388
- const pathname = new URL(path7, "https://example.com").pathname;
389
- for (const [name, application] of Object.entries(this.applications)) {
390
- if (application.routing) {
391
- for (const group of application.routing) {
392
- for (const childPath of group.paths) {
393
- const regexp = getRegexp(childPath);
394
- if (regexp.test(pathname)) {
395
- this.pathCache[path7] = name;
396
- return name;
397
- }
398
- }
399
- }
400
- }
401
- }
402
- const defaultApplication = Object.entries(this.applications).find(
403
- ([, application]) => application.default
404
- );
405
- if (!defaultApplication) {
406
- return null;
407
- }
408
- this.pathCache[path7] = defaultApplication[0];
409
- return defaultApplication[0];
410
- }
411
- serialize() {
412
- return this.serialized;
413
- }
414
- };
415
-
416
358
  // src/config/microfrontends-config/isomorphic/validation.ts
417
- import { pathToRegexp as pathToRegexp2, parse as parsePathRegexp } from "path-to-regexp";
359
+ import { pathToRegexp, parse as parsePathRegexp } from "path-to-regexp";
418
360
  var LIST_FORMATTER = new Intl.ListFormat("en", {
419
361
  style: "long",
420
362
  type: "conjunction"
421
363
  });
364
+ var VALID_ASSET_PREFIX_REGEXP = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
422
365
  var validateConfigPaths = (applicationConfigsById) => {
423
366
  if (!applicationConfigsById) {
424
367
  return;
@@ -441,7 +384,7 @@ var validateConfigPaths = (applicationConfigsById) => {
441
384
  } else {
442
385
  pathsByApplicationId.set(path7, {
443
386
  applications: [id],
444
- matcher: pathToRegexp2(path7),
387
+ matcher: pathToRegexp(path7),
445
388
  applicationId: id
446
389
  });
447
390
  }
@@ -545,6 +488,22 @@ var validateAppPaths = (name, app) => {
545
488
  }
546
489
  }
547
490
  }
491
+ if (app.assetPrefix) {
492
+ if (!VALID_ASSET_PREFIX_REGEXP.test(app.assetPrefix)) {
493
+ throw new MicrofrontendError(
494
+ `Invalid asset prefix for application "${name}". ${app.assetPrefix} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
495
+ { type: "application", subtype: "invalid_asset_prefix" }
496
+ );
497
+ }
498
+ if (app.assetPrefix !== `vc-ap-${name}` && !app.routing.some(
499
+ (group) => group.paths.includes(`/${app.assetPrefix}/:path*`) && !group.flag
500
+ )) {
501
+ throw new MicrofrontendError(
502
+ `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.`,
503
+ { type: "application", subtype: "invalid_asset_prefix" }
504
+ );
505
+ }
506
+ }
548
507
  };
549
508
  var validateConfigDefaultApplication = (applicationConfigsById) => {
550
509
  if (!applicationConfigsById) {
@@ -576,6 +535,15 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
576
535
  }
577
536
  };
578
537
 
538
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
539
+ import md5 from "md5";
540
+ function hashApplicationName(name) {
541
+ if (!name) {
542
+ throw new Error("Application name is required to generate hash");
543
+ }
544
+ return md5(name).substring(0, 6).padStart(6, "0");
545
+ }
546
+
579
547
  // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
580
548
  var PREFIX = "vc-ap";
581
549
  function generateAssetPrefixFromName({
@@ -584,7 +552,7 @@ function generateAssetPrefixFromName({
584
552
  if (!name) {
585
553
  throw new Error("Name is required to generate an asset prefix");
586
554
  }
587
- return `${PREFIX}-${name}`;
555
+ return `${PREFIX}-${hashApplicationName(name)}`;
588
556
  }
589
557
 
590
558
  // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
@@ -744,7 +712,13 @@ var Application = class {
744
712
  return this.default;
745
713
  }
746
714
  getAssetPrefix() {
747
- return generateAssetPrefixFromName({ name: this.name });
715
+ const generatedAssetPrefix = generateAssetPrefixFromName({
716
+ name: this.name
717
+ });
718
+ if ("assetPrefix" in this.serialized) {
719
+ return this.serialized.assetPrefix ?? generatedAssetPrefix;
720
+ }
721
+ return generatedAssetPrefix;
748
722
  }
749
723
  getAutomationBypassEnvVarName() {
750
724
  return generateAutomationBypassEnvVarName({ name: this.name });
@@ -878,7 +852,7 @@ var MicrofrontendConfigIsomorphic = class {
878
852
  );
879
853
  if (!app) {
880
854
  throw new MicrofrontendError(
881
- `Could not find microfrontends configuration for application "${name}"`,
855
+ `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.`,
882
856
  {
883
857
  type: "application",
884
858
  subtype: "not_found"
@@ -923,23 +897,6 @@ var MicrofrontendConfigIsomorphic = class {
923
897
  toSchemaJson() {
924
898
  return this.serialized.config;
925
899
  }
926
- toClientConfig() {
927
- const applications = Object.fromEntries(
928
- Object.entries(this.childApplications).map(([name, application]) => [
929
- name,
930
- {
931
- default: false,
932
- routing: application.routing
933
- }
934
- ])
935
- );
936
- applications[this.defaultApplication.name] = {
937
- default: true
938
- };
939
- return new MicrofrontendConfigClient({
940
- applications
941
- });
942
- }
943
900
  serialize() {
944
901
  return this.serialized;
945
902
  }
@@ -952,8 +909,31 @@ function getApplicationContext(opts) {
952
909
  if (opts?.appName) {
953
910
  return { name: opts.appName };
954
911
  }
912
+ if (process.env.VERCEL_PROJECT_NAME) {
913
+ return {
914
+ name: process.env.VERCEL_PROJECT_NAME,
915
+ projectName: process.env.VERCEL_PROJECT_NAME
916
+ };
917
+ }
955
918
  if (process.env.NX_TASK_TARGET_PROJECT) {
956
- return { name: process.env.NX_TASK_TARGET_PROJECT };
919
+ return {
920
+ name: process.env.NX_TASK_TARGET_PROJECT,
921
+ packageJsonName: process.env.NX_TASK_TARGET_PROJECT
922
+ };
923
+ }
924
+ try {
925
+ const vercelProjectJsonPath = fs5.readFileSync(
926
+ path4.join(opts?.packageRoot || ".", ".vercel", "project.json"),
927
+ "utf-8"
928
+ );
929
+ const projectJson = JSON.parse(vercelProjectJsonPath);
930
+ if (projectJson.projectName) {
931
+ return {
932
+ name: projectJson.projectName,
933
+ projectName: projectJson.projectName
934
+ };
935
+ }
936
+ } catch (_) {
957
937
  }
958
938
  try {
959
939
  const packageJsonString = fs5.readFileSync(
@@ -971,7 +951,7 @@ function getApplicationContext(opts) {
971
951
  }
972
952
  );
973
953
  }
974
- return { name: packageJson.name };
954
+ return { name: packageJson.name, packageJsonName: packageJson.name };
975
955
  } catch (err) {
976
956
  throw MicrofrontendError.handle(err, {
977
957
  fileName: "package.json"
@@ -1111,6 +1091,10 @@ var schema_default = {
1111
1091
  routing: {
1112
1092
  $ref: "#/definitions/Routing",
1113
1093
  description: "Groups of path expressions that are routed to this application."
1094
+ },
1095
+ assetPrefix: {
1096
+ type: "string",
1097
+ 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."
1114
1098
  }
1115
1099
  },
1116
1100
  required: [
@@ -1336,7 +1320,7 @@ var MicrofrontendsServer = class {
1336
1320
  }
1337
1321
  try {
1338
1322
  const packageRoot = findPackageRoot(directory);
1339
- const { name: appName } = getApplicationContext({ packageRoot });
1323
+ const applicationContext = getApplicationContext({ packageRoot });
1340
1324
  const maybeConfig = findConfig({ dir: packageRoot });
1341
1325
  if (maybeConfig) {
1342
1326
  return MicrofrontendsServer.fromFile({
@@ -1370,7 +1354,7 @@ var MicrofrontendsServer = class {
1370
1354
  if (isMonorepo2) {
1371
1355
  const defaultPackage = inferMicrofrontendsLocation({
1372
1356
  repositoryRoot,
1373
- applicationName: appName
1357
+ applicationContext
1374
1358
  });
1375
1359
  const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
1376
1360
  if (maybeConfigFromDefault) {