remote-components 0.2.2 → 0.3.1

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 (100) hide show
  1. package/dist/config/nextjs.cjs +2 -4
  2. package/dist/config/nextjs.cjs.map +1 -1
  3. package/dist/config/nextjs.d.ts +1 -1
  4. package/dist/config/nextjs.js +2 -4
  5. package/dist/config/nextjs.js.map +1 -1
  6. package/dist/host/html.cjs +128 -112
  7. package/dist/host/html.cjs.map +1 -1
  8. package/dist/host/html.js +128 -115
  9. package/dist/host/html.js.map +1 -1
  10. package/dist/host/nextjs/app/client-only.cjs +233 -259
  11. package/dist/host/nextjs/app/client-only.cjs.map +1 -1
  12. package/dist/host/nextjs/app/client-only.js +234 -260
  13. package/dist/host/nextjs/app/client-only.js.map +1 -1
  14. package/dist/host/nextjs/app.cjs +5 -6
  15. package/dist/host/nextjs/app.cjs.map +1 -1
  16. package/dist/host/nextjs/app.js +5 -6
  17. package/dist/host/nextjs/app.js.map +1 -1
  18. package/dist/host/nextjs/pages.cjs +7 -19
  19. package/dist/host/nextjs/pages.cjs.map +1 -1
  20. package/dist/host/nextjs/pages.js +11 -20
  21. package/dist/host/nextjs/pages.js.map +1 -1
  22. package/dist/host/react.cjs +101 -93
  23. package/dist/host/react.cjs.map +1 -1
  24. package/dist/host/react.js +101 -93
  25. package/dist/host/react.js.map +1 -1
  26. package/dist/internal/host/nextjs/app-client.cjs +3 -8
  27. package/dist/internal/host/nextjs/app-client.cjs.map +1 -1
  28. package/dist/internal/host/nextjs/app-client.js +4 -9
  29. package/dist/internal/host/nextjs/app-client.js.map +1 -1
  30. package/dist/internal/host/nextjs/dom-flight.cjs +16 -7
  31. package/dist/internal/host/nextjs/dom-flight.cjs.map +1 -1
  32. package/dist/internal/host/nextjs/dom-flight.d.ts +2 -2
  33. package/dist/internal/host/nextjs/dom-flight.js +16 -7
  34. package/dist/internal/host/nextjs/dom-flight.js.map +1 -1
  35. package/dist/internal/host/nextjs/image-shared.cjs +25 -15
  36. package/dist/internal/host/nextjs/image-shared.cjs.map +1 -1
  37. package/dist/internal/host/nextjs/image-shared.d.ts +19 -6
  38. package/dist/internal/host/nextjs/image-shared.js +24 -14
  39. package/dist/internal/host/nextjs/image-shared.js.map +1 -1
  40. package/dist/internal/host/react/hooks/use-resolve-client-url.cjs +1 -5
  41. package/dist/internal/host/react/hooks/use-resolve-client-url.cjs.map +1 -1
  42. package/dist/internal/host/react/hooks/use-resolve-client-url.d.ts +1 -4
  43. package/dist/internal/host/react/hooks/use-resolve-client-url.js +1 -5
  44. package/dist/internal/host/react/hooks/use-resolve-client-url.js.map +1 -1
  45. package/dist/internal/host/server/fetch-remote-component.cjs +164 -149
  46. package/dist/internal/host/server/fetch-remote-component.cjs.map +1 -1
  47. package/dist/internal/host/server/fetch-remote-component.js +166 -149
  48. package/dist/internal/host/server/fetch-remote-component.js.map +1 -1
  49. package/dist/internal/host/shared/polyfill.cjs +10 -65
  50. package/dist/internal/host/shared/polyfill.cjs.map +1 -1
  51. package/dist/internal/host/shared/polyfill.d.ts +1 -3
  52. package/dist/internal/host/shared/polyfill.js +9 -63
  53. package/dist/internal/host/shared/polyfill.js.map +1 -1
  54. package/dist/internal/host/shared/remote-image-loader.cjs +53 -0
  55. package/dist/internal/host/shared/remote-image-loader.cjs.map +1 -0
  56. package/dist/internal/host/shared/remote-image-loader.d.ts +30 -0
  57. package/dist/internal/host/shared/remote-image-loader.js +29 -0
  58. package/dist/internal/host/shared/remote-image-loader.js.map +1 -0
  59. package/dist/internal/runtime/constants.cjs +6 -6
  60. package/dist/internal/runtime/constants.cjs.map +1 -1
  61. package/dist/internal/runtime/constants.d.ts +3 -3
  62. package/dist/internal/runtime/constants.js +4 -4
  63. package/dist/internal/runtime/constants.js.map +1 -1
  64. package/dist/internal/runtime/html/parse-remote-html.cjs +11 -15
  65. package/dist/internal/runtime/html/parse-remote-html.cjs.map +1 -1
  66. package/dist/internal/runtime/html/parse-remote-html.d.ts +2 -12
  67. package/dist/internal/runtime/html/parse-remote-html.js +17 -15
  68. package/dist/internal/runtime/html/parse-remote-html.js.map +1 -1
  69. package/dist/internal/runtime/loaders/script-loader.cjs +2 -2
  70. package/dist/internal/runtime/loaders/script-loader.cjs.map +1 -1
  71. package/dist/internal/runtime/loaders/script-loader.js +1 -1
  72. package/dist/internal/runtime/loaders/script-loader.js.map +1 -1
  73. package/dist/internal/runtime/metadata.cjs +42 -0
  74. package/dist/internal/runtime/metadata.cjs.map +1 -1
  75. package/dist/internal/runtime/metadata.d.ts +21 -1
  76. package/dist/internal/runtime/metadata.js +38 -0
  77. package/dist/internal/runtime/metadata.js.map +1 -1
  78. package/dist/internal/runtime/patterns.cjs +38 -0
  79. package/dist/internal/runtime/patterns.cjs.map +1 -0
  80. package/dist/internal/runtime/patterns.d.ts +5 -0
  81. package/dist/internal/runtime/patterns.js +12 -0
  82. package/dist/internal/runtime/patterns.js.map +1 -0
  83. package/dist/internal/runtime/turbopack/chunk-loader.cjs +4 -3
  84. package/dist/internal/runtime/turbopack/chunk-loader.cjs.map +1 -1
  85. package/dist/internal/runtime/turbopack/chunk-loader.js +1 -1
  86. package/dist/internal/runtime/turbopack/chunk-loader.js.map +1 -1
  87. package/dist/internal/runtime/turbopack/webpack-runtime.cjs +11 -2
  88. package/dist/internal/runtime/turbopack/webpack-runtime.cjs.map +1 -1
  89. package/dist/internal/runtime/turbopack/webpack-runtime.js +10 -2
  90. package/dist/internal/runtime/turbopack/webpack-runtime.js.map +1 -1
  91. package/dist/remote/nextjs/app.cjs +2 -1
  92. package/dist/remote/nextjs/app.cjs.map +1 -1
  93. package/dist/remote/nextjs/app.js +2 -1
  94. package/dist/remote/nextjs/app.js.map +1 -1
  95. package/package.json +1 -1
  96. package/dist/internal/host/nextjs/image-impl.cjs +0 -64
  97. package/dist/internal/host/nextjs/image-impl.cjs.map +0 -1
  98. package/dist/internal/host/nextjs/image-impl.d.ts +0 -10
  99. package/dist/internal/host/nextjs/image-impl.js +0 -40
  100. package/dist/internal/host/nextjs/image-impl.js.map +0 -1
@@ -237,58 +237,35 @@ function getClientOrServerUrl(src, serverFallback) {
237
237
  return typeof src === "string" ? new URL(src, fallback) : src;
238
238
  }
239
239
 
240
- // src/host/shared/polyfill.tsx
241
- var import_jsx_runtime = (
242
- // eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text
243
- require("react/jsx-runtime")
244
- );
245
- function applyBundleUrlToSrc(bundle, src) {
240
+ // src/host/shared/remote-image-loader.ts
241
+ function getRemoteBundleOrigin(bundle) {
246
242
  const self = globalThis;
247
- if (self.__remote_bundle_url__?.[bundle]?.origin === location.origin) {
248
- return src;
249
- }
250
- const { assetPrefix, path } = /^(?<assetPrefix>.*?)\/_next\/(?<path>.*)/.exec(src)?.groups ?? {};
251
- if (!path) {
252
- return new URL(src, self.__remote_bundle_url__?.[bundle]?.origin).href;
253
- }
254
- return `${self.__remote_bundle_url__?.[bundle]?.origin ?? ""}${assetPrefix}/_next/${path}`;
255
- }
256
- function applyBundleUrlToImagePropsSrc(bundle, src) {
257
- if (typeof src === "string") {
258
- return applyBundleUrlToSrc(bundle, src);
259
- }
260
- const propSrc = src;
261
- return applyBundleUrlToSrc(bundle, propSrc.src);
262
- }
263
- var imageImpl = (bundle, resolveClientUrl) => function RemoteImage({
264
- fill: _fill,
265
- loader: _loader,
266
- quality: _quality,
267
- priority: _priority,
268
- loading: _loading,
269
- placeholder: _placeholder,
270
- blurDataURL: _blurDataURL,
271
- unoptimized: _unoptimized,
272
- overrideSrc: _overrideSrc,
273
- src,
274
- ...props
275
- }) {
276
- const newSrc = applyBundleUrlToImagePropsSrc(
277
- bundle,
278
- typeof src === "string" ? src : src.src
279
- );
280
- const proxiedSrc = resolveClientUrl?.(newSrc) ?? newSrc;
281
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
282
- "img",
283
- {
284
- decoding: "async",
285
- style: { color: "transparent" },
286
- ...props,
287
- src: proxiedSrc,
288
- suppressHydrationWarning: true
289
- }
243
+ return self.__remote_bundle_url__?.[bundle]?.origin ?? "";
244
+ }
245
+ function createRemoteImageLoader(bundle, resolveClientUrl) {
246
+ const loader = Object.assign(
247
+ ({
248
+ config,
249
+ src,
250
+ width,
251
+ quality
252
+ }) => {
253
+ const q = quality ?? 75;
254
+ const remoteOrigin = getRemoteBundleOrigin(bundle);
255
+ const isCrossOrigin = remoteOrigin && remoteOrigin !== location.origin;
256
+ const basePath = isCrossOrigin ? `${remoteOrigin}${config.path ?? "/_next/image"}` : config.path ?? `${remoteOrigin}/_next/image`;
257
+ const url = `${basePath}?url=${encodeURIComponent(src)}&w=${width}&q=${q}`;
258
+ return resolveClientUrl?.(url) ?? url;
259
+ },
260
+ // Signals to getImgProps that this is a default loader (not a user-defined
261
+ // one), enabling srcSet generation with device/image sizes from the config.
262
+ { __next_img_default: true }
290
263
  );
291
- };
264
+ return loader;
265
+ }
266
+
267
+ // src/host/shared/polyfill.tsx
268
+ var import_jsx_runtime = require("react/jsx-runtime");
292
269
  function sharedPolyfills(shared, resolveClientUrl) {
293
270
  const self = globalThis;
294
271
  const polyfill = {
@@ -379,17 +356,13 @@ function sharedPolyfills(shared, resolveClientUrl) {
379
356
  },
380
357
  __esModule: true
381
358
  })),
382
- "next/dist/client/image-component": self.__remote_component_host_shared_modules__?.["next/image"] ?? shared?.["next/image"] ?? ((bundle) => Promise.resolve({
383
- Image: imageImpl(bundle, resolveClientUrl),
384
- __esModule: true
385
- })),
386
- "next/image": self.__remote_component_host_shared_modules__?.["next/image"] ?? shared?.["next/image"] ?? ((bundle) => Promise.resolve({
387
- default: imageImpl(bundle, resolveClientUrl),
388
- getImageProps: (_imgProps) => {
389
- throw new Error(
390
- "Next.js getImageProps() is not implemented in remote components"
391
- );
392
- },
359
+ // Instead of replacing next/image entirely, we let the real Next.js Image
360
+ // component load from the remote bundle and only replace its default loader.
361
+ // This gives us full next/image fidelity (fill, priority, srcSet, blur
362
+ // placeholders, error handling) while routing image optimization through the
363
+ // remote app's /_next/image endpoint.
364
+ "next/dist/shared/lib/image-loader": self.__remote_component_host_shared_modules__?.["next/dist/shared/lib/image-loader"] ?? shared?.["next/dist/shared/lib/image-loader"] ?? ((bundle) => Promise.resolve({
365
+ default: createRemoteImageLoader(bundle, resolveClientUrl),
393
366
  __esModule: true
394
367
  })),
395
368
  "next/dist/client/script": self.__remote_component_host_shared_modules__?.["next/script"] ?? shared?.["next/script"] ?? (() => Promise.resolve({
@@ -429,7 +402,7 @@ function sharedPolyfills(shared, resolveClientUrl) {
429
402
  polyfill["next/navigation"] = polyfill["next/dist/client/components/navigation"];
430
403
  polyfill["next/link"] = polyfill["next/dist/client/app-dir/link"];
431
404
  polyfill["next/form"] = polyfill["next/dist/client/app-dir/form"];
432
- polyfill["next/dist/api/image"] = polyfill["next/dist/client/image-component"];
405
+ polyfill["next/dist/esm/shared/lib/image-loader"] = polyfill["next/dist/shared/lib/image-loader"];
433
406
  polyfill["next/script"] = polyfill["next/dist/client/script"];
434
407
  return polyfill;
435
408
  }
@@ -473,12 +446,12 @@ var attrToProp = {
473
446
  };
474
447
 
475
448
  // src/runtime/constants.ts
449
+ var DEFAULT_BUNDLE_NAME = "__vercel_remote_bundle";
450
+ var DEFAULT_COMPONENT_NAME = "__vercel_remote_component";
476
451
  var DEFAULT_ROUTE = "/";
477
452
  var RUNTIME_WEBPACK = "webpack";
478
453
  var RUNTIME_TURBOPACK = "turbopack";
479
454
  var RUNTIME_SCRIPT = "script";
480
- var REMOTE_COMPONENT_REGEX = /(?<prefix>.*?)\[(?<bundle>[^\]]+)\](?:%20| )(?<id>.+)/;
481
- var NEXT_BUNDLE_PATH_RE = /\/_next\/\[.+\](?:%20| )/;
482
455
  function getBundleKey(bundle) {
483
456
  return escapeString(bundle);
484
457
  }
@@ -545,6 +518,38 @@ function applyOriginToNodes(doc, url, resolveClientUrl) {
545
518
  }
546
519
  }
547
520
 
521
+ // src/runtime/metadata.ts
522
+ var VALID_RUNTIMES = /* @__PURE__ */ new Set(["webpack", "turbopack", "script"]);
523
+ var VALID_TYPES = /* @__PURE__ */ new Set([
524
+ "nextjs",
525
+ "remote-component",
526
+ "unknown"
527
+ ]);
528
+ function isRuntime(value) {
529
+ return VALID_RUNTIMES.has(value);
530
+ }
531
+ function isComponentType(value) {
532
+ return VALID_TYPES.has(value);
533
+ }
534
+ function toRuntime(value) {
535
+ return value && isRuntime(value) ? value : "webpack";
536
+ }
537
+ function toComponentType(value) {
538
+ return value && isComponentType(value) ? value : "unknown";
539
+ }
540
+ function buildMetadata(attrs, url) {
541
+ const id = attrs.id || DEFAULT_COMPONENT_NAME;
542
+ const bundle = attrs.bundle || process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION || DEFAULT_BUNDLE_NAME;
543
+ return {
544
+ name: attrs.name || id.replace(/_ssr$/, ""),
545
+ bundle,
546
+ route: attrs.route || url.pathname || DEFAULT_ROUTE,
547
+ runtime: toRuntime(attrs.runtime),
548
+ id,
549
+ type: toComponentType(attrs.type)
550
+ };
551
+ }
552
+
548
553
  // src/runtime/html/parse-remote-html.ts
549
554
  function validateSingleComponent(doc, name, url) {
550
555
  if (doc.querySelectorAll("div[data-bundle][data-route]").length > 1 && !doc.querySelector(`div[data-bundle][data-route][id^="${name}"]`) || doc.querySelectorAll("remote-component:not([src])").length > 1 && !doc.querySelector(`remote-component[name="${name}"]`)) {
@@ -564,14 +569,6 @@ function resolveComponentName(component, nextData, fallbackName) {
564
569
  const name = component?.getAttribute("id")?.replace(/_ssr$/, "") || isRemoteComponent && component?.getAttribute("name") || (nextData ? "__next" : fallbackName);
565
570
  return { name, isRemoteComponent };
566
571
  }
567
- function extractComponentMetadata(component, nextData, name, url) {
568
- return {
569
- name,
570
- bundle: component?.getAttribute("data-bundle") || nextData?.props.__REMOTE_COMPONENT__?.bundle || "default",
571
- route: component?.getAttribute("data-route") ?? nextData?.page ?? (url.pathname || DEFAULT_ROUTE),
572
- runtime: component?.getAttribute("data-runtime") ?? (nextData?.props.__REMOTE_COMPONENT__?.runtime || RUNTIME_SCRIPT)
573
- };
574
- }
575
572
  function extractRemoteShared(doc, name, nextData) {
576
573
  const remoteSharedEl = doc.querySelector(
577
574
  `#${name}_shared[data-remote-components-shared]`
@@ -583,7 +580,7 @@ function extractRemoteShared(doc, name, nextData) {
583
580
  function validateComponentFound(component, rsc, nextData, isRemoteComponent, url, name) {
584
581
  if (!component || !(rsc || nextData || isRemoteComponent)) {
585
582
  throw new RemoteComponentsError(
586
- `Remote Component not found on ${url}.${name !== "__vercel_remote_component" ? ` The name for the <RemoteComponent> is "${name}". Check <RemoteComponent> usage.` : ""} Did you forget to wrap the content in <RemoteComponent>?`
583
+ `Remote Component not found on ${url}.${name !== DEFAULT_COMPONENT_NAME ? ` The name for the <RemoteComponent> is "${name}". Check <RemoteComponent> usage.` : ""} Did you forget to wrap the content in <RemoteComponent>?`
587
584
  );
588
585
  }
589
586
  }
@@ -609,10 +606,15 @@ function parseRemoteComponentDocument(doc, name, url) {
609
606
  name
610
607
  );
611
608
  const rsc = doc.querySelector(`#${resolvedName}_rsc`);
612
- const metadata = extractComponentMetadata(
613
- component,
614
- nextData,
615
- resolvedName,
609
+ const metadata = buildMetadata(
610
+ {
611
+ name: resolvedName,
612
+ bundle: component?.getAttribute("data-bundle") || nextData?.props.__REMOTE_COMPONENT__?.bundle,
613
+ route: component?.getAttribute("data-route") ?? nextData?.page,
614
+ runtime: component?.getAttribute("data-runtime") ?? nextData?.props.__REMOTE_COMPONENT__?.runtime ?? RUNTIME_SCRIPT,
615
+ id: component?.getAttribute("id"),
616
+ type: component?.getAttribute("data-type")
617
+ },
616
618
  url
617
619
  );
618
620
  const remoteShared = extractRemoteShared(doc, resolvedName, nextData);
@@ -928,6 +930,14 @@ function createRSCStream(rscName, data) {
928
930
  });
929
931
  }
930
932
 
933
+ // src/runtime/patterns.ts
934
+ var REMOTE_COMPONENT_REGEX = /(?<prefix>.*?)\[(?<bundle>[^\]]+)\](?:%20| )(?<id>.+)/;
935
+ var NEXT_BUNDLE_PATH_RE = /\/_next\/\[.+\](?:%20| )/;
936
+ var DOUBLE_SLASH_RE = /(?<!:)\/\//g;
937
+ function collapseDoubleSlashes(path) {
938
+ return path.replace(DOUBLE_SLASH_RE, "/");
939
+ }
940
+
931
941
  // src/runtime/turbopack/patterns.ts
932
942
  var REMOTE_SHARED_MARKER_RE = /(?:self|[a-z])\.TURBOPACK_REMOTE_SHARED/;
933
943
  var REMOTE_SHARED_ASSIGNMENT_RE = /\.TURBOPACK_REMOTE_SHARED=await (?:__turbopack_context__|[a-z])\.A\((?<sharedModuleId>[0-9]+)\)/;
@@ -1517,7 +1527,7 @@ async function setupWebpackRuntime(runtime, scripts = [], url = new URL(location
1517
1527
  }
1518
1528
  }
1519
1529
  if (runtime === RUNTIME_TURBOPACK) {
1520
- await Promise.all(
1530
+ const results = await Promise.allSettled(
1521
1531
  scripts.map((script) => {
1522
1532
  if (script.src) {
1523
1533
  return self.__webpack_chunk_load__?.(script.src, bundle);
@@ -1525,6 +1535,14 @@ async function setupWebpackRuntime(runtime, scripts = [], url = new URL(location
1525
1535
  return Promise.resolve(void 0);
1526
1536
  })
1527
1537
  );
1538
+ for (const result of results) {
1539
+ if (result.status === "rejected") {
1540
+ logWarn(
1541
+ "WebpackRuntime",
1542
+ `Initial chunk load failed: ${String(result.reason)}`
1543
+ );
1544
+ }
1545
+ }
1528
1546
  }
1529
1547
  const coreShared = {
1530
1548
  react: async () => (await import("react")).default,
@@ -1941,11 +1959,7 @@ function bindResolveClientUrl(prop, remoteSrc) {
1941
1959
  function useResolveClientUrl(prop, urlHref) {
1942
1960
  const { resolveClientUrl: contextValue } = (0, import_context.useRemoteComponentsContext)();
1943
1961
  const raw = prop ?? contextValue;
1944
- const bound = (0, import_react.useMemo)(
1945
- () => bindResolveClientUrl(raw, urlHref),
1946
- [raw, urlHref]
1947
- );
1948
- return { bound, raw };
1962
+ return (0, import_react.useMemo)(() => bindResolveClientUrl(raw, urlHref), [raw, urlHref]);
1949
1963
  }
1950
1964
 
1951
1965
  // src/host/react/hooks/use-shadow-root.ts
@@ -2011,7 +2025,7 @@ function getRemoteComponentHtml(html) {
2011
2025
  return ssrRemoteComponentContainer.innerHTML;
2012
2026
  }
2013
2027
  const remoteComponentContainer = temp.querySelectorAll(
2014
- `div[data-bundle][data-route][data-runtime][id^="__vercel_remote_component"],div[data-bundle][data-route],div#__next,remote-component:not([src])`
2028
+ `div[data-bundle][data-route][data-runtime][id^="${DEFAULT_COMPONENT_NAME}"],div[data-bundle][data-route],div#__next,remote-component:not([src])`
2015
2029
  );
2016
2030
  if (remoteComponentContainer.length > 0) {
2017
2031
  return `${Array.from(temp.querySelectorAll("link,script")).map((link) => link.outerHTML).join("")}${Array.from(remoteComponentContainer).map((container) => container.outerHTML).join("")}`;
@@ -2031,7 +2045,7 @@ function ConsumeRemoteComponent({
2031
2045
  mode = "open",
2032
2046
  reset,
2033
2047
  credentials: credentialsProp,
2034
- name: nameProp = "__vercel_remote_component",
2048
+ name: nameProp = DEFAULT_COMPONENT_NAME,
2035
2049
  shared: sharedProp,
2036
2050
  children,
2037
2051
  onBeforeLoad,
@@ -2054,10 +2068,7 @@ function ConsumeRemoteComponent({
2054
2068
  null
2055
2069
  );
2056
2070
  const url = (0, import_react3.useMemo)(() => getClientOrServerUrl(src, DUMMY_FALLBACK), [src]);
2057
- const { bound: resolveClientUrl } = useResolveClientUrl(
2058
- resolveClientUrlProp,
2059
- url.href
2060
- );
2071
+ const resolveClientUrl = useResolveClientUrl(resolveClientUrlProp, url.href);
2061
2072
  const id = url.origin === (typeof location !== "undefined" ? location.origin : DUMMY_FALLBACK) ? url.pathname : url.href;
2062
2073
  const keySuffix = `${escapeString(id)}_${escapeString(
2063
2074
  data?.name ?? name
@@ -2373,10 +2384,7 @@ function ConsumeRemoteComponent({
2373
2384
  };
2374
2385
  return {
2375
2386
  src: new URL(
2376
- `${prefix ?? ""}${path}`.replace(
2377
- /(?<char>[^:])(?<double>\/\/)/g,
2378
- "$1/"
2379
- ),
2387
+ collapseDoubleSlashes(`${prefix ?? ""}${path}`),
2380
2388
  url
2381
2389
  ).href
2382
2390
  };