@vercel/microfrontends 2.0.0-canary.1 → 2.0.0-canary.2

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 (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +21 -4
  3. package/dist/bin/cli.cjs +142 -13
  4. package/dist/config.cjs +123 -3
  5. package/dist/config.cjs.map +1 -1
  6. package/dist/config.d.ts +33 -2
  7. package/dist/config.js +122 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/experimental/sveltekit.cjs +128 -4
  10. package/dist/experimental/sveltekit.cjs.map +1 -1
  11. package/dist/experimental/sveltekit.js +127 -3
  12. package/dist/experimental/sveltekit.js.map +1 -1
  13. package/dist/experimental/vite.cjs +128 -4
  14. package/dist/experimental/vite.cjs.map +1 -1
  15. package/dist/experimental/vite.js +127 -3
  16. package/dist/experimental/vite.js.map +1 -1
  17. package/dist/microfrontends/server.cjs +128 -4
  18. package/dist/microfrontends/server.cjs.map +1 -1
  19. package/dist/microfrontends/server.d.ts +4 -3
  20. package/dist/microfrontends/server.js +127 -3
  21. package/dist/microfrontends/server.js.map +1 -1
  22. package/dist/next/config.cjs +131 -131
  23. package/dist/next/config.cjs.map +1 -1
  24. package/dist/next/config.d.ts +5 -0
  25. package/dist/next/config.js +130 -130
  26. package/dist/next/config.js.map +1 -1
  27. package/dist/next/middleware.cjs +125 -105
  28. package/dist/next/middleware.cjs.map +1 -1
  29. package/dist/next/middleware.js +125 -105
  30. package/dist/next/middleware.js.map +1 -1
  31. package/dist/next/testing.cjs +126 -6
  32. package/dist/next/testing.cjs.map +1 -1
  33. package/dist/next/testing.d.ts +2 -2
  34. package/dist/next/testing.js +124 -4
  35. package/dist/next/testing.js.map +1 -1
  36. package/dist/overrides.d.ts +3 -3
  37. package/dist/schema.d.ts +2 -2
  38. package/dist/{types-4299bff1.d.ts → types-88602303.d.ts} +1 -1
  39. package/dist/{types-0deb756b.d.ts → types-e7523e61.d.ts} +1 -1
  40. package/dist/utils/mfe-port.cjs +128 -4
  41. package/dist/utils/mfe-port.cjs.map +1 -1
  42. package/dist/utils/mfe-port.js +127 -3
  43. package/dist/utils/mfe-port.js.map +1 -1
  44. package/dist/validation.d.ts +1 -1
  45. package/package.json +7 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @vercel/microfrontends
2
2
 
3
+ ## 2.0.0-canary.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Use user-provided appName when inferring the location of microfrontends.json.
8
+
3
9
  ## 2.0.0-canary.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -1,9 +1,26 @@
1
1
  <picture>
2
- <source srcset="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-banner-dark.png" media="(prefers-color-scheme: dark)">
3
- <source srcset="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-banner-light.png" media="(prefers-color-scheme: light)">
4
- <img src="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-banner-light.png" alt="hero banner">
2
+ <source srcset="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-package-banner-dark.png" media="(prefers-color-scheme: dark)">
3
+ <source srcset="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-package-banner-light.png" media="(prefers-color-scheme: light)">
4
+ <img src="https://assets.vercel.com/image/upload/v1689795055/docs-assets/static/docs/microfrontends/mfe-package-banner-dark.png" alt="hero banner">
5
5
  </picture>
6
6
 
7
7
  # @vercel/microfrontends
8
8
 
9
- 📚 [Documentation](https://vercel.com/docs/microfrontends)
9
+ Split apart large applications and develop faster with microfrontends. This package contains library code to be used with Vercel's [microfrontends support](https://vercel.com/docs/microfrontends).
10
+
11
+ - **Improved developer velocity**: Build and test just the application that you are developing. Independent applications allow each team to choose their own technology stack.
12
+ - **Incremental migrations**: Gradually migrate systems to modern frameworks without rewriting everything at once.
13
+
14
+ See the [full documentation](https://vercel.com/docs/microfrontends) and [examples](https://vercel.com/templates/microfrontends) to learn more.
15
+
16
+ ## Getting Started
17
+
18
+ Follow the [quickstart](https://vercel.com/docs/microfrontends/quickstart) documentation to get started.
19
+
20
+ ## Frameworks support
21
+
22
+ You can use the frameworks and microfrontends frameworks of your choice. `@vercel/microfrontends` supports Next.js, SvelteKit, React Router, Vite, and React.
23
+
24
+ ## Need help?
25
+
26
+ Reach out for help in the [Vercel Community](https://community.vercel.com).
package/dist/bin/cli.cjs CHANGED
@@ -30,12 +30,17 @@ var import_env = require("@next/env");
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "@vercel/microfrontends",
33
- version: "2.0.0-canary.1",
33
+ version: "2.0.0-canary.2",
34
34
  private: false,
35
35
  description: "Defines configuration and utilities for microfrontends development",
36
36
  keywords: [
37
37
  "microfrontends",
38
- "Next.js"
38
+ "micro-frontends",
39
+ "micro frontends",
40
+ "microservices",
41
+ "Vercel",
42
+ "Next.js",
43
+ "React"
39
44
  ],
40
45
  homepage: "https://vercel.com/docs/microfrontends",
41
46
  repository: {
@@ -244,7 +249,7 @@ var http = __toESM(require("http"), 1);
244
249
  var https = __toESM(require("https"), 1);
245
250
  var import_node_url = require("url");
246
251
  var import_cookie = require("cookie");
247
- var import_path_to_regexp2 = require("path-to-regexp");
252
+ var import_path_to_regexp3 = require("path-to-regexp");
248
253
  var import_http_proxy = __toESM(require("http-proxy"), 1);
249
254
 
250
255
  // src/config/microfrontends-config/isomorphic/index.ts
@@ -392,8 +397,106 @@ function parseOverrides(cookies) {
392
397
  return overridesConfig;
393
398
  }
394
399
 
395
- // src/config/microfrontends-config/isomorphic/validation.ts
400
+ // src/config/microfrontends-config/client/index.ts
396
401
  var import_path_to_regexp = require("path-to-regexp");
402
+ var regexpCache = /* @__PURE__ */ new Map();
403
+ var getRegexp = (path7) => {
404
+ const existing = regexpCache.get(path7);
405
+ if (existing) {
406
+ return existing;
407
+ }
408
+ const regexp = (0, import_path_to_regexp.pathToRegexp)(path7);
409
+ regexpCache.set(path7, regexp);
410
+ return regexp;
411
+ };
412
+ var MicrofrontendConfigClient = class {
413
+ constructor(config, opts) {
414
+ this.pathCache = {};
415
+ this.hasFlaggedPaths = config.hasFlaggedPaths ?? false;
416
+ for (const app of Object.values(config.applications)) {
417
+ if (app.routing) {
418
+ if (app.routing.some((match) => match.flag)) {
419
+ this.hasFlaggedPaths = true;
420
+ }
421
+ const newRouting = [];
422
+ const pathsWithoutFlags = [];
423
+ for (const group of app.routing) {
424
+ if (group.flag) {
425
+ if (opts?.removeFlaggedPaths) {
426
+ continue;
427
+ }
428
+ if (group.group) {
429
+ delete group.group;
430
+ }
431
+ newRouting.push(group);
432
+ } else {
433
+ pathsWithoutFlags.push(...group.paths);
434
+ }
435
+ }
436
+ if (pathsWithoutFlags.length > 0) {
437
+ newRouting.push({ paths: pathsWithoutFlags });
438
+ }
439
+ app.routing = newRouting;
440
+ }
441
+ }
442
+ this.serialized = config;
443
+ if (this.hasFlaggedPaths) {
444
+ this.serialized.hasFlaggedPaths = this.hasFlaggedPaths;
445
+ }
446
+ this.applications = config.applications;
447
+ }
448
+ /**
449
+ * Create a new `MicrofrontendConfigClient` from a JSON string.
450
+ * Config must be passed in to remain framework agnostic
451
+ */
452
+ static fromEnv(config) {
453
+ if (!config) {
454
+ throw new Error(
455
+ "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
456
+ );
457
+ }
458
+ return new MicrofrontendConfigClient(JSON.parse(config));
459
+ }
460
+ isEqual(other) {
461
+ return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
462
+ }
463
+ getApplicationNameForPath(path7) {
464
+ if (!path7.startsWith("/")) {
465
+ throw new Error(`Path must start with a /`);
466
+ }
467
+ if (this.pathCache[path7]) {
468
+ return this.pathCache[path7];
469
+ }
470
+ const pathname = new URL(path7, "https://example.com").pathname;
471
+ for (const [name, application] of Object.entries(this.applications)) {
472
+ if (application.routing) {
473
+ for (const group of application.routing) {
474
+ for (const childPath of group.paths) {
475
+ const regexp = getRegexp(childPath);
476
+ if (regexp.test(pathname)) {
477
+ this.pathCache[path7] = name;
478
+ return name;
479
+ }
480
+ }
481
+ }
482
+ }
483
+ }
484
+ const defaultApplication = Object.entries(this.applications).find(
485
+ ([, application]) => application.default
486
+ );
487
+ if (!defaultApplication) {
488
+ return null;
489
+ }
490
+ this.pathCache[path7] = defaultApplication[0];
491
+ return defaultApplication[0];
492
+ }
493
+ serialize() {
494
+ return this.serialized;
495
+ }
496
+ };
497
+
498
+ // src/config/microfrontends-config/isomorphic/validation.ts
499
+ var import_path_to_regexp2 = require("path-to-regexp");
397
500
  var LIST_FORMATTER = new Intl.ListFormat("en", {
398
501
  style: "long",
399
502
  type: "conjunction"
@@ -421,7 +524,7 @@ var validateConfigPaths = (applicationConfigsById) => {
421
524
  } else {
422
525
  pathsByApplicationId.set(path7, {
423
526
  applications: [id],
424
- matcher: (0, import_path_to_regexp.pathToRegexp)(path7),
527
+ matcher: (0, import_path_to_regexp2.pathToRegexp)(path7),
425
528
  applicationId: id
426
529
  });
427
530
  }
@@ -468,7 +571,7 @@ var validateConfigPaths = (applicationConfigsById) => {
468
571
  var PATH_DEFAULT_PATTERN = "[^\\/#\\?]+?";
469
572
  function validatePathExpression(path7) {
470
573
  try {
471
- const tokens = (0, import_path_to_regexp.parse)(path7);
574
+ const tokens = (0, import_path_to_regexp2.parse)(path7);
472
575
  if (/(?<!\\)\{/.test(path7)) {
473
576
  return `Optional paths are not supported: ${path7}`;
474
577
  }
@@ -926,6 +1029,28 @@ var MicrofrontendConfigIsomorphic = class {
926
1029
  getLocalProxyPort() {
927
1030
  return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
928
1031
  }
1032
+ toClientConfig(options) {
1033
+ const applications = Object.fromEntries(
1034
+ Object.entries(this.childApplications).map(([name, application]) => [
1035
+ hashApplicationName(name),
1036
+ {
1037
+ default: false,
1038
+ routing: application.routing
1039
+ }
1040
+ ])
1041
+ );
1042
+ applications[hashApplicationName(this.defaultApplication.name)] = {
1043
+ default: true
1044
+ };
1045
+ return new MicrofrontendConfigClient(
1046
+ {
1047
+ applications
1048
+ },
1049
+ {
1050
+ removeFlaggedPaths: options?.removeFlaggedPaths
1051
+ }
1052
+ );
1053
+ }
929
1054
  /**
930
1055
  * Serializes the class back to the Schema type.
931
1056
  *
@@ -1553,6 +1678,7 @@ var MicrofrontendsServer = class {
1553
1678
  * This can return either a Child or Main configuration.
1554
1679
  */
1555
1680
  static infer({
1681
+ appName,
1556
1682
  directory,
1557
1683
  filePath,
1558
1684
  cookies
@@ -1565,7 +1691,10 @@ var MicrofrontendsServer = class {
1565
1691
  }
1566
1692
  try {
1567
1693
  const packageRoot = findPackageRoot(directory);
1568
- const applicationContext = getApplicationContext({ packageRoot });
1694
+ const applicationContext = getApplicationContext({
1695
+ appName,
1696
+ packageRoot
1697
+ });
1569
1698
  const maybeConfig = findConfig({ dir: packageRoot });
1570
1699
  if (maybeConfig) {
1571
1700
  return MicrofrontendsServer.fromFile({
@@ -2131,7 +2260,7 @@ var ProxyRequestRouter = class {
2131
2260
  }
2132
2261
  for (const group of application.routing) {
2133
2262
  for (const childPath of group.paths) {
2134
- const regexp = (0, import_path_to_regexp2.pathToRegexp)(childPath);
2263
+ const regexp = (0, import_path_to_regexp3.pathToRegexp)(childPath);
2135
2264
  if (regexp.test(url.pathname)) {
2136
2265
  mfeDebug(
2137
2266
  `routing ${path7} to '${target.application}' at ${target.hostname}`
@@ -2180,7 +2309,7 @@ var ProxyRequestRouter = class {
2180
2309
  ]);
2181
2310
  for (const rewrite of rewrites) {
2182
2311
  for (const assetPrefix of assetPrefixes) {
2183
- if ((0, import_path_to_regexp2.pathToRegexp)(`/${assetPrefix}${rewrite}`).test(pathname)) {
2312
+ if ((0, import_path_to_regexp3.pathToRegexp)(`/${assetPrefix}${rewrite}`).test(pathname)) {
2184
2313
  mfeDebug(
2185
2314
  `routing ${pathname} to '${target.application}' at ${target.hostname}`
2186
2315
  );
@@ -2198,8 +2327,8 @@ var ProxyRequestRouter = class {
2198
2327
  referer = void 0,
2199
2328
  applications
2200
2329
  }) {
2201
- const isStackFrame = (0, import_path_to_regexp2.pathToRegexp)("/__nextjs_original-stack-frame").test(url.pathname) || // Plural form was introduced in https://github.com/vercel/next.js/pull/75557
2202
- (0, import_path_to_regexp2.pathToRegexp)("/__nextjs_original-stack-frames").test(url.pathname);
2330
+ const isStackFrame = (0, import_path_to_regexp3.pathToRegexp)("/__nextjs_original-stack-frame").test(url.pathname) || // Plural form was introduced in https://github.com/vercel/next.js/pull/75557
2331
+ (0, import_path_to_regexp3.pathToRegexp)("/__nextjs_original-stack-frames").test(url.pathname);
2203
2332
  if (!referer || !isStackFrame) {
2204
2333
  return null;
2205
2334
  }
@@ -2219,7 +2348,7 @@ var ProxyRequestRouter = class {
2219
2348
  } : null;
2220
2349
  }
2221
2350
  checkNextSourceMap({ url }) {
2222
- const isSourceMap = (0, import_path_to_regexp2.pathToRegexp)("/__nextjs_source-map").test(url.pathname);
2351
+ const isSourceMap = (0, import_path_to_regexp3.pathToRegexp)("/__nextjs_source-map").test(url.pathname);
2223
2352
  if (!isSourceMap) {
2224
2353
  return null;
2225
2354
  }
@@ -2239,7 +2368,7 @@ var ProxyRequestRouter = class {
2239
2368
  url,
2240
2369
  applications
2241
2370
  }) {
2242
- const isNextImage = (0, import_path_to_regexp2.pathToRegexp)("/_next/image").test(url.pathname);
2371
+ const isNextImage = (0, import_path_to_regexp3.pathToRegexp)("/_next/image").test(url.pathname);
2243
2372
  if (!isNextImage) {
2244
2373
  return null;
2245
2374
  }
package/dist/config.cjs CHANGED
@@ -177,8 +177,106 @@ function parseOverrides(cookies) {
177
177
  return overridesConfig;
178
178
  }
179
179
 
180
- // src/config/microfrontends-config/isomorphic/validation.ts
180
+ // src/config/microfrontends-config/client/index.ts
181
181
  var import_path_to_regexp = require("path-to-regexp");
182
+ var regexpCache = /* @__PURE__ */ new Map();
183
+ var getRegexp = (path) => {
184
+ const existing = regexpCache.get(path);
185
+ if (existing) {
186
+ return existing;
187
+ }
188
+ const regexp = (0, import_path_to_regexp.pathToRegexp)(path);
189
+ regexpCache.set(path, regexp);
190
+ return regexp;
191
+ };
192
+ var MicrofrontendConfigClient = class {
193
+ constructor(config, opts) {
194
+ this.pathCache = {};
195
+ this.hasFlaggedPaths = config.hasFlaggedPaths ?? false;
196
+ for (const app of Object.values(config.applications)) {
197
+ if (app.routing) {
198
+ if (app.routing.some((match) => match.flag)) {
199
+ this.hasFlaggedPaths = true;
200
+ }
201
+ const newRouting = [];
202
+ const pathsWithoutFlags = [];
203
+ for (const group of app.routing) {
204
+ if (group.flag) {
205
+ if (opts?.removeFlaggedPaths) {
206
+ continue;
207
+ }
208
+ if (group.group) {
209
+ delete group.group;
210
+ }
211
+ newRouting.push(group);
212
+ } else {
213
+ pathsWithoutFlags.push(...group.paths);
214
+ }
215
+ }
216
+ if (pathsWithoutFlags.length > 0) {
217
+ newRouting.push({ paths: pathsWithoutFlags });
218
+ }
219
+ app.routing = newRouting;
220
+ }
221
+ }
222
+ this.serialized = config;
223
+ if (this.hasFlaggedPaths) {
224
+ this.serialized.hasFlaggedPaths = this.hasFlaggedPaths;
225
+ }
226
+ this.applications = config.applications;
227
+ }
228
+ /**
229
+ * Create a new `MicrofrontendConfigClient` from a JSON string.
230
+ * Config must be passed in to remain framework agnostic
231
+ */
232
+ static fromEnv(config) {
233
+ if (!config) {
234
+ throw new Error(
235
+ "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`?"
236
+ );
237
+ }
238
+ return new MicrofrontendConfigClient(JSON.parse(config));
239
+ }
240
+ isEqual(other) {
241
+ return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
242
+ }
243
+ getApplicationNameForPath(path) {
244
+ if (!path.startsWith("/")) {
245
+ throw new Error(`Path must start with a /`);
246
+ }
247
+ if (this.pathCache[path]) {
248
+ return this.pathCache[path];
249
+ }
250
+ const pathname = new URL(path, "https://example.com").pathname;
251
+ for (const [name, application] of Object.entries(this.applications)) {
252
+ if (application.routing) {
253
+ for (const group of application.routing) {
254
+ for (const childPath of group.paths) {
255
+ const regexp = getRegexp(childPath);
256
+ if (regexp.test(pathname)) {
257
+ this.pathCache[path] = name;
258
+ return name;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ const defaultApplication = Object.entries(this.applications).find(
265
+ ([, application]) => application.default
266
+ );
267
+ if (!defaultApplication) {
268
+ return null;
269
+ }
270
+ this.pathCache[path] = defaultApplication[0];
271
+ return defaultApplication[0];
272
+ }
273
+ serialize() {
274
+ return this.serialized;
275
+ }
276
+ };
277
+
278
+ // src/config/microfrontends-config/isomorphic/validation.ts
279
+ var import_path_to_regexp2 = require("path-to-regexp");
182
280
  var LIST_FORMATTER = new Intl.ListFormat("en", {
183
281
  style: "long",
184
282
  type: "conjunction"
@@ -206,7 +304,7 @@ var validateConfigPaths = (applicationConfigsById) => {
206
304
  } else {
207
305
  pathsByApplicationId.set(path, {
208
306
  applications: [id],
209
- matcher: (0, import_path_to_regexp.pathToRegexp)(path),
307
+ matcher: (0, import_path_to_regexp2.pathToRegexp)(path),
210
308
  applicationId: id
211
309
  });
212
310
  }
@@ -253,7 +351,7 @@ var validateConfigPaths = (applicationConfigsById) => {
253
351
  var PATH_DEFAULT_PATTERN = "[^\\/#\\?]+?";
254
352
  function validatePathExpression(path) {
255
353
  try {
256
- const tokens = (0, import_path_to_regexp.parse)(path);
354
+ const tokens = (0, import_path_to_regexp2.parse)(path);
257
355
  if (/(?<!\\)\{/.test(path)) {
258
356
  return `Optional paths are not supported: ${path}`;
259
357
  }
@@ -711,6 +809,28 @@ var MicrofrontendConfigIsomorphic = class {
711
809
  getLocalProxyPort() {
712
810
  return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
713
811
  }
812
+ toClientConfig(options) {
813
+ const applications = Object.fromEntries(
814
+ Object.entries(this.childApplications).map(([name, application]) => [
815
+ hashApplicationName(name),
816
+ {
817
+ default: false,
818
+ routing: application.routing
819
+ }
820
+ ])
821
+ );
822
+ applications[hashApplicationName(this.defaultApplication.name)] = {
823
+ default: true
824
+ };
825
+ return new MicrofrontendConfigClient(
826
+ {
827
+ applications
828
+ },
829
+ {
830
+ removeFlaggedPaths: options?.removeFlaggedPaths
831
+ }
832
+ );
833
+ }
714
834
  /**
715
835
  * Serializes the class back to the Schema type.
716
836
  *