@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
@@ -197,8 +197,9 @@ var CONFIGURATION_FILENAMES = [
197
197
  var configCache = {};
198
198
  function findPackageWithMicrofrontendsConfig({
199
199
  repositoryRoot,
200
- applicationName
200
+ applicationContext
201
201
  }) {
202
+ const applicationName = applicationContext.name;
202
203
  try {
203
204
  const microfrontendsJsonPaths = fg.globSync(
204
205
  `**/{${CONFIGURATION_FILENAMES.join(",")}}`,
@@ -240,26 +241,45 @@ ${matchingPaths.join("\n \u2022 ")}`,
240
241
  );
241
242
  }
242
243
  if (matchingPaths.length === 0) {
244
+ let additionalErrorMessage = "";
245
+ if (microfrontendsJsonPaths.length > 0) {
246
+ if (!applicationContext.projectName) {
247
+ additionalErrorMessage = `
248
+
249
+ 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.`;
250
+ } else {
251
+ additionalErrorMessage = `
252
+
253
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
254
+ }
255
+ }
243
256
  throw new MicrofrontendError(
244
- `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.`,
257
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
258
+
259
+ 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.
260
+
261
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
245
262
  { type: "config", subtype: "inference_failed" }
246
263
  );
247
264
  }
248
265
  const [packageJsonPath] = matchingPaths;
249
266
  return dirname(packageJsonPath);
250
267
  } catch (error) {
268
+ if (error instanceof MicrofrontendError) {
269
+ throw error;
270
+ }
251
271
  return null;
252
272
  }
253
273
  }
254
274
  function inferMicrofrontendsLocation(opts) {
255
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationName}`;
275
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}`;
256
276
  if (configCache[cacheKey]) {
257
277
  return configCache[cacheKey];
258
278
  }
259
279
  const result = findPackageWithMicrofrontendsConfig(opts);
260
280
  if (!result) {
261
281
  throw new MicrofrontendError(
262
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationName}" starting in directory "${opts.repositoryRoot}".`,
282
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
263
283
  { type: "config", subtype: "inference_failed" }
264
284
  );
265
285
  }
@@ -331,90 +351,13 @@ function findConfig({ dir }) {
331
351
  // src/config/microfrontends-config/isomorphic/index.ts
332
352
  import { parse as parse2 } from "jsonc-parser";
333
353
 
334
- // src/config/microfrontends-config/client/index.ts
335
- import { pathToRegexp } from "path-to-regexp";
336
- var regexpCache = /* @__PURE__ */ new Map();
337
- var getRegexp = (path6) => {
338
- const existing = regexpCache.get(path6);
339
- if (existing) {
340
- return existing;
341
- }
342
- const regexp = pathToRegexp(path6);
343
- regexpCache.set(path6, regexp);
344
- return regexp;
345
- };
346
- var MicrofrontendConfigClient = class {
347
- constructor(config, opts) {
348
- this.pathCache = {};
349
- this.serialized = config;
350
- if (opts?.removeFlaggedPaths) {
351
- for (const app of Object.values(config.applications)) {
352
- if (app.routing) {
353
- app.routing = app.routing.filter((match) => !match.flag);
354
- }
355
- }
356
- }
357
- this.applications = config.applications;
358
- }
359
- /**
360
- * Create a new `MicrofrontendConfigClient` from a JSON string.
361
- * Config must be passed in to remain framework agnostic
362
- */
363
- static fromEnv(config, opts) {
364
- if (!config) {
365
- throw new Error(
366
- "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
367
- );
368
- }
369
- return new MicrofrontendConfigClient(
370
- JSON.parse(config),
371
- opts
372
- );
373
- }
374
- isEqual(other) {
375
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
376
- }
377
- getApplicationNameForPath(path6) {
378
- if (!path6.startsWith("/")) {
379
- throw new Error(`Path must start with a /`);
380
- }
381
- if (this.pathCache[path6]) {
382
- return this.pathCache[path6];
383
- }
384
- const pathname = new URL(path6, "https://example.com").pathname;
385
- for (const [name, application] of Object.entries(this.applications)) {
386
- if (application.routing) {
387
- for (const group of application.routing) {
388
- for (const childPath of group.paths) {
389
- const regexp = getRegexp(childPath);
390
- if (regexp.test(pathname)) {
391
- this.pathCache[path6] = name;
392
- return name;
393
- }
394
- }
395
- }
396
- }
397
- }
398
- const defaultApplication = Object.entries(this.applications).find(
399
- ([, application]) => application.default
400
- );
401
- if (!defaultApplication) {
402
- return null;
403
- }
404
- this.pathCache[path6] = defaultApplication[0];
405
- return defaultApplication[0];
406
- }
407
- serialize() {
408
- return this.serialized;
409
- }
410
- };
411
-
412
354
  // src/config/microfrontends-config/isomorphic/validation.ts
413
- import { pathToRegexp as pathToRegexp2, parse as parsePathRegexp } from "path-to-regexp";
355
+ import { pathToRegexp, parse as parsePathRegexp } from "path-to-regexp";
414
356
  var LIST_FORMATTER = new Intl.ListFormat("en", {
415
357
  style: "long",
416
358
  type: "conjunction"
417
359
  });
360
+ var VALID_ASSET_PREFIX_REGEXP = /^[a-z](?:[a-z0-9-]*[a-z0-9])?$/;
418
361
  var validateConfigPaths = (applicationConfigsById) => {
419
362
  if (!applicationConfigsById) {
420
363
  return;
@@ -437,7 +380,7 @@ var validateConfigPaths = (applicationConfigsById) => {
437
380
  } else {
438
381
  pathsByApplicationId.set(path6, {
439
382
  applications: [id],
440
- matcher: pathToRegexp2(path6),
383
+ matcher: pathToRegexp(path6),
441
384
  applicationId: id
442
385
  });
443
386
  }
@@ -541,6 +484,22 @@ var validateAppPaths = (name, app) => {
541
484
  }
542
485
  }
543
486
  }
487
+ if (app.assetPrefix) {
488
+ if (!VALID_ASSET_PREFIX_REGEXP.test(app.assetPrefix)) {
489
+ throw new MicrofrontendError(
490
+ `Invalid asset prefix for application "${name}". ${app.assetPrefix} must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`,
491
+ { type: "application", subtype: "invalid_asset_prefix" }
492
+ );
493
+ }
494
+ if (app.assetPrefix !== `vc-ap-${name}` && !app.routing.some(
495
+ (group) => group.paths.includes(`/${app.assetPrefix}/:path*`) && !group.flag
496
+ )) {
497
+ throw new MicrofrontendError(
498
+ `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.`,
499
+ { type: "application", subtype: "invalid_asset_prefix" }
500
+ );
501
+ }
502
+ }
544
503
  };
545
504
  var validateConfigDefaultApplication = (applicationConfigsById) => {
546
505
  if (!applicationConfigsById) {
@@ -572,6 +531,15 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
572
531
  }
573
532
  };
574
533
 
534
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
535
+ import md5 from "md5";
536
+ function hashApplicationName(name) {
537
+ if (!name) {
538
+ throw new Error("Application name is required to generate hash");
539
+ }
540
+ return md5(name).substring(0, 6).padStart(6, "0");
541
+ }
542
+
575
543
  // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
576
544
  var PREFIX = "vc-ap";
577
545
  function generateAssetPrefixFromName({
@@ -580,7 +548,7 @@ function generateAssetPrefixFromName({
580
548
  if (!name) {
581
549
  throw new Error("Name is required to generate an asset prefix");
582
550
  }
583
- return `${PREFIX}-${name}`;
551
+ return `${PREFIX}-${hashApplicationName(name)}`;
584
552
  }
585
553
 
586
554
  // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
@@ -740,7 +708,13 @@ var Application = class {
740
708
  return this.default;
741
709
  }
742
710
  getAssetPrefix() {
743
- return generateAssetPrefixFromName({ name: this.name });
711
+ const generatedAssetPrefix = generateAssetPrefixFromName({
712
+ name: this.name
713
+ });
714
+ if ("assetPrefix" in this.serialized) {
715
+ return this.serialized.assetPrefix ?? generatedAssetPrefix;
716
+ }
717
+ return generatedAssetPrefix;
744
718
  }
745
719
  getAutomationBypassEnvVarName() {
746
720
  return generateAutomationBypassEnvVarName({ name: this.name });
@@ -874,7 +848,7 @@ var MicrofrontendConfigIsomorphic = class {
874
848
  );
875
849
  if (!app) {
876
850
  throw new MicrofrontendError(
877
- `Could not find microfrontends configuration for application "${name}"`,
851
+ `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.`,
878
852
  {
879
853
  type: "application",
880
854
  subtype: "not_found"
@@ -919,23 +893,6 @@ var MicrofrontendConfigIsomorphic = class {
919
893
  toSchemaJson() {
920
894
  return this.serialized.config;
921
895
  }
922
- toClientConfig() {
923
- const applications = Object.fromEntries(
924
- Object.entries(this.childApplications).map(([name, application]) => [
925
- name,
926
- {
927
- default: false,
928
- routing: application.routing
929
- }
930
- ])
931
- );
932
- applications[this.defaultApplication.name] = {
933
- default: true
934
- };
935
- return new MicrofrontendConfigClient({
936
- applications
937
- });
938
- }
939
896
  serialize() {
940
897
  return this.serialized;
941
898
  }
@@ -948,8 +905,31 @@ function getApplicationContext(opts) {
948
905
  if (opts?.appName) {
949
906
  return { name: opts.appName };
950
907
  }
908
+ if (process.env.VERCEL_PROJECT_NAME) {
909
+ return {
910
+ name: process.env.VERCEL_PROJECT_NAME,
911
+ projectName: process.env.VERCEL_PROJECT_NAME
912
+ };
913
+ }
951
914
  if (process.env.NX_TASK_TARGET_PROJECT) {
952
- return { name: process.env.NX_TASK_TARGET_PROJECT };
915
+ return {
916
+ name: process.env.NX_TASK_TARGET_PROJECT,
917
+ packageJsonName: process.env.NX_TASK_TARGET_PROJECT
918
+ };
919
+ }
920
+ try {
921
+ const vercelProjectJsonPath = fs5.readFileSync(
922
+ path4.join(opts?.packageRoot || ".", ".vercel", "project.json"),
923
+ "utf-8"
924
+ );
925
+ const projectJson = JSON.parse(vercelProjectJsonPath);
926
+ if (projectJson.projectName) {
927
+ return {
928
+ name: projectJson.projectName,
929
+ projectName: projectJson.projectName
930
+ };
931
+ }
932
+ } catch (_) {
953
933
  }
954
934
  try {
955
935
  const packageJsonString = fs5.readFileSync(
@@ -967,7 +947,7 @@ function getApplicationContext(opts) {
967
947
  }
968
948
  );
969
949
  }
970
- return { name: packageJson.name };
950
+ return { name: packageJson.name, packageJsonName: packageJson.name };
971
951
  } catch (err) {
972
952
  throw MicrofrontendError.handle(err, {
973
953
  fileName: "package.json"
@@ -1107,6 +1087,10 @@ var schema_default = {
1107
1087
  routing: {
1108
1088
  $ref: "#/definitions/Routing",
1109
1089
  description: "Groups of path expressions that are routed to this application."
1090
+ },
1091
+ assetPrefix: {
1092
+ type: "string",
1093
+ 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."
1110
1094
  }
1111
1095
  },
1112
1096
  required: [
@@ -1332,7 +1316,7 @@ var MicrofrontendsServer = class {
1332
1316
  }
1333
1317
  try {
1334
1318
  const packageRoot = findPackageRoot(directory);
1335
- const { name: appName } = getApplicationContext({ packageRoot });
1319
+ const applicationContext = getApplicationContext({ packageRoot });
1336
1320
  const maybeConfig = findConfig({ dir: packageRoot });
1337
1321
  if (maybeConfig) {
1338
1322
  return MicrofrontendsServer.fromFile({
@@ -1366,7 +1350,7 @@ var MicrofrontendsServer = class {
1366
1350
  if (isMonorepo2) {
1367
1351
  const defaultPackage = inferMicrofrontendsLocation({
1368
1352
  repositoryRoot,
1369
- applicationName: appName
1353
+ applicationContext
1370
1354
  });
1371
1355
  const maybeConfigFromDefault = findConfig({ dir: defaultPackage });
1372
1356
  if (maybeConfigFromDefault) {