remote-components 0.0.2 → 0.0.4
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.
- package/dist/html/host.cjs +67 -51
- package/dist/html/host.cjs.map +1 -1
- package/dist/html/host.js +67 -51
- package/dist/html/host.js.map +1 -1
- package/dist/next/config.cjs +76 -32
- package/dist/next/config.cjs.map +1 -1
- package/dist/next/config.js +76 -35
- package/dist/next/config.js.map +1 -1
- package/dist/next/host/app-client.cjs +5 -11
- package/dist/next/host/app-client.cjs.map +1 -1
- package/dist/next/host/app-client.d.ts +2 -1
- package/dist/next/host/app-client.js +5 -11
- package/dist/next/host/app-client.js.map +1 -1
- package/dist/next/host/app-server.cjs +3 -1
- package/dist/next/host/app-server.cjs.map +1 -1
- package/dist/next/host/app-server.js +3 -1
- package/dist/next/host/app-server.js.map +1 -1
- package/dist/next/middleware.cjs +71 -0
- package/dist/next/middleware.cjs.map +1 -0
- package/dist/next/middleware.d.ts +28 -0
- package/dist/next/middleware.js +45 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/next/remote/render-server.cjs +32 -7
- package/dist/next/remote/render-server.cjs.map +1 -1
- package/dist/next/remote/render-server.js +32 -7
- package/dist/next/remote/render-server.js.map +1 -1
- package/dist/shared/client/remote-component.cjs +69 -52
- package/dist/shared/client/remote-component.cjs.map +1 -1
- package/dist/shared/client/remote-component.d.ts +6 -4
- package/dist/shared/client/remote-component.js +69 -51
- package/dist/shared/client/remote-component.js.map +1 -1
- package/package.json +11 -3
|
@@ -28,6 +28,7 @@ var import_react_dom = require("react-dom");
|
|
|
28
28
|
var import_host = require("@remote-component/shared/host");
|
|
29
29
|
var import_remote_component = require("../../shared/client/remote-component");
|
|
30
30
|
function RemoteComponentClient({
|
|
31
|
+
url,
|
|
31
32
|
name,
|
|
32
33
|
bundle,
|
|
33
34
|
route = import_remote_component.DEFAULT_ROUTE,
|
|
@@ -65,11 +66,12 @@ function RemoteComponentClient({
|
|
|
65
66
|
setShadowRoot(shadowRootElement);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
}, [name, isolate, shadowRoot]);
|
|
69
|
+
}, [name, isolate, shadowRoot, links, url]);
|
|
69
70
|
(0, import_react.useEffect)(() => {
|
|
70
71
|
let mounted = true;
|
|
71
72
|
if (!component && (isolate === false || shadowRoot)) {
|
|
72
73
|
(0, import_remote_component.loadRemoteComponent)({
|
|
74
|
+
url: new URL(url, location.origin),
|
|
73
75
|
name,
|
|
74
76
|
bundle,
|
|
75
77
|
route,
|
|
@@ -98,6 +100,7 @@ function RemoteComponentClient({
|
|
|
98
100
|
mounted = false;
|
|
99
101
|
};
|
|
100
102
|
}, [
|
|
103
|
+
url,
|
|
101
104
|
component,
|
|
102
105
|
name,
|
|
103
106
|
bundle,
|
|
@@ -117,7 +120,7 @@ function RemoteComponentClient({
|
|
|
117
120
|
"link",
|
|
118
121
|
{
|
|
119
122
|
as: link.as,
|
|
120
|
-
href: link.href,
|
|
123
|
+
href: new URL(link.href, url || location.origin).href,
|
|
121
124
|
rel: link.rel
|
|
122
125
|
},
|
|
123
126
|
`${link.href}_${link.rel}`
|
|
@@ -150,15 +153,6 @@ function RemoteComponentClient({
|
|
|
150
153
|
}) }),
|
|
151
154
|
linksToRender,
|
|
152
155
|
componentToRender,
|
|
153
|
-
links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
154
|
-
"link",
|
|
155
|
-
{
|
|
156
|
-
as: link.as,
|
|
157
|
-
href: link.href,
|
|
158
|
-
rel: link.rel
|
|
159
|
-
},
|
|
160
|
-
`${link.href}_${link.rel}`
|
|
161
|
-
)),
|
|
162
156
|
data.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { id: `${name}_rsc`, children: data.join("\n") }) : null,
|
|
163
157
|
nextData ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
164
158
|
"script",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/app-client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState, useLayoutEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { shared } from '@remote-component/shared/host';\nimport {\n loadRemoteComponent,\n DEFAULT_ROUTE,\n RUNTIME_WEBPACK,\n} from '../../shared/client/remote-component';\nimport type { RemoteComponentProps } from '../../shared/client/remote-component';\n\n// patch react/jsx-runtime to support the shadowrootmode attribute on template elements\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n template: {\n shadowrootmode?: 'open' | 'closed';\n children: React.ReactNode;\n };\n }\n }\n}\n\n/**\n * RemoteComponentClient - Main component for rendering remote components\n *\n * This component handles the loading and rendering of remote microfrontends.\n * It supports both RSC (React Server Components) and Next.js Pages Router based components.\n */\nexport function RemoteComponentClient({\n name,\n bundle,\n route = DEFAULT_ROUTE,\n runtime = RUNTIME_WEBPACK,\n data,\n nextData,\n scripts = [],\n links = [],\n remoteShared = {},\n isolate,\n children,\n}: RemoteComponentProps) {\n const [component, setComponent] = useState<React.ReactNode | Error>(null);\n\n // Handle errors by re-throwing them\n if (component instanceof Error) {\n throw component;\n }\n\n // determine whether to use children or loaded component\n const shouldUseChildren =\n (!component ||\n (component &&\n !nextData &&\n typeof (component as unknown as Promise<unknown>).then !==\n 'function')) &&\n // if the remote Next.js Pages Router application is in development mode\n // we don't use the provided static HTML\n // to mitigate layout shift when loading CSS using JavaScript on the client\n nextData?.buildId !== 'development';\n\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useLayoutEffect(() => {\n if (isolate !== false && typeof document !== 'undefined' && !shadowRoot) {\n let shadowRootElement: ShadowRoot | null = null;\n const element = document.getElementById(`shadowroot_${name}`);\n shadowRootElement = element?.shadowRoot ?? null;\n\n if (!shadowRootElement && element) {\n // create a shadow root if it doesn't exist\n // this is a fallback for browsers that don't support declarative shadow DOM\n element.attachShadow({ mode: 'open' });\n shadowRootElement = element.shadowRoot;\n }\n\n if (shadowRootElement) {\n // remove all nodes from the shadow root except links\n shadowRootElement.querySelectorAll('*:not(link)').forEach((node) => {\n node.remove();\n });\n setShadowRoot(shadowRootElement);\n }\n }\n }, [name, isolate, shadowRoot]);\n\n useEffect(() => {\n let mounted = true;\n\n // if we have a component, we don't need to load it again\n if (!component && (isolate === false || shadowRoot)) {\n loadRemoteComponent({\n name,\n bundle,\n route,\n runtime,\n data,\n nextData,\n scripts,\n shared,\n remoteShared,\n container: shadowRoot,\n })\n .then((result) => {\n if (mounted) {\n if (result.error) {\n setComponent(result.error);\n } else {\n setComponent(result.component);\n }\n }\n })\n .catch((error: Error) => {\n if (mounted) {\n setComponent(error);\n }\n });\n }\n\n return () => {\n mounted = false;\n };\n }, [\n component,\n name,\n bundle,\n route,\n runtime,\n scripts,\n data,\n nextData,\n remoteShared,\n children,\n links,\n isolate,\n shadowRoot,\n ]);\n\n let componentToRender = (\n <>{shouldUseChildren ? children : (component as React.ReactNode)}</>\n );\n let linksToRender: React.ReactNode[] | null = links.map((link) => (\n <link\n as={link.as as string}\n href={link.href as string}\n key={`${link.href as string}_${link.rel}`}\n rel={link.rel as string}\n />\n ));\n\n if (isolate !== false) {\n componentToRender = (\n <div id={`shadowroot_${name}`}>\n {typeof document === 'undefined' ? (\n // eslint-disable-next-line react/no-unknown-property\n <template shadowrootmode=\"open\">\n {linksToRender}\n {componentToRender}\n </template>\n ) : null}\n {shadowRoot\n ? createPortal(\n shadowRoot.querySelectorAll('link').length !== links.length ? (\n <>\n {linksToRender}\n {componentToRender}\n </>\n ) : (\n componentToRender\n ),\n shadowRoot,\n )\n : null}\n </div>\n );\n linksToRender = null;\n }\n\n return (\n <>\n <script data-remote-component type=\"application/json\">\n {JSON.stringify({\n name,\n bundle,\n route,\n runtime,\n })}\n </script>\n {linksToRender}\n {componentToRender}\n {
|
|
1
|
+
{"version":3,"sources":["../../../src/next/host/app-client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState, useLayoutEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { shared } from '@remote-component/shared/host';\nimport {\n loadRemoteComponent,\n DEFAULT_ROUTE,\n RUNTIME_WEBPACK,\n} from '../../shared/client/remote-component';\nimport type { RemoteComponentProps } from '../../shared/client/remote-component';\n\n// patch react/jsx-runtime to support the shadowrootmode attribute on template elements\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n template: {\n shadowrootmode?: 'open' | 'closed';\n children: React.ReactNode;\n };\n }\n }\n}\n\n/**\n * RemoteComponentClient - Main component for rendering remote components\n *\n * This component handles the loading and rendering of remote microfrontends.\n * It supports both RSC (React Server Components) and Next.js Pages Router based components.\n */\nexport function RemoteComponentClient({\n url,\n name,\n bundle,\n route = DEFAULT_ROUTE,\n runtime = RUNTIME_WEBPACK,\n data,\n nextData,\n scripts = [],\n links = [],\n remoteShared = {},\n isolate,\n children,\n}: RemoteComponentProps) {\n const [component, setComponent] = useState<React.ReactNode | Error>(null);\n\n // Handle errors by re-throwing them\n if (component instanceof Error) {\n throw component;\n }\n\n // determine whether to use children or loaded component\n const shouldUseChildren =\n (!component ||\n (component &&\n !nextData &&\n typeof (component as unknown as Promise<unknown>).then !==\n 'function')) &&\n // if the remote Next.js Pages Router application is in development mode\n // we don't use the provided static HTML\n // to mitigate layout shift when loading CSS using JavaScript on the client\n nextData?.buildId !== 'development';\n\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useLayoutEffect(() => {\n if (isolate !== false && typeof document !== 'undefined' && !shadowRoot) {\n let shadowRootElement: ShadowRoot | null = null;\n const element = document.getElementById(`shadowroot_${name}`);\n shadowRootElement = element?.shadowRoot ?? null;\n\n if (!shadowRootElement && element) {\n // create a shadow root if it doesn't exist\n // this is a fallback for browsers that don't support declarative shadow DOM\n element.attachShadow({ mode: 'open' });\n shadowRootElement = element.shadowRoot;\n }\n\n if (shadowRootElement) {\n // remove all nodes from the shadow root except links\n shadowRootElement.querySelectorAll('*:not(link)').forEach((node) => {\n node.remove();\n });\n setShadowRoot(shadowRootElement);\n }\n }\n }, [name, isolate, shadowRoot, links, url]);\n\n useEffect(() => {\n let mounted = true;\n\n // if we have a component, we don't need to load it again\n if (!component && (isolate === false || shadowRoot)) {\n loadRemoteComponent({\n url: new URL(url, location.origin),\n name,\n bundle,\n route,\n runtime,\n data,\n nextData,\n scripts,\n shared,\n remoteShared,\n container: shadowRoot,\n })\n .then((result) => {\n if (mounted) {\n if (result.error) {\n setComponent(result.error);\n } else {\n setComponent(result.component);\n }\n }\n })\n .catch((error: Error) => {\n if (mounted) {\n setComponent(error);\n }\n });\n }\n\n return () => {\n mounted = false;\n };\n }, [\n url,\n component,\n name,\n bundle,\n route,\n runtime,\n scripts,\n data,\n nextData,\n remoteShared,\n children,\n links,\n isolate,\n shadowRoot,\n ]);\n\n let componentToRender = (\n <>{shouldUseChildren ? children : (component as React.ReactNode)}</>\n );\n let linksToRender: React.ReactNode[] | null = links.map((link) => (\n <link\n as={link.as as string}\n href={new URL(link.href as string, url || location.origin).href}\n key={`${link.href as string}_${link.rel}`}\n rel={link.rel as string}\n />\n ));\n\n if (isolate !== false) {\n componentToRender = (\n <div id={`shadowroot_${name}`}>\n {typeof document === 'undefined' ? (\n // eslint-disable-next-line react/no-unknown-property\n <template shadowrootmode=\"open\">\n {linksToRender}\n {componentToRender}\n </template>\n ) : null}\n {shadowRoot\n ? createPortal(\n shadowRoot.querySelectorAll('link').length !== links.length ? (\n <>\n {linksToRender}\n {componentToRender}\n </>\n ) : (\n componentToRender\n ),\n shadowRoot,\n )\n : null}\n </div>\n );\n linksToRender = null;\n }\n\n return (\n <>\n <script data-remote-component type=\"application/json\">\n {JSON.stringify({\n name,\n bundle,\n route,\n runtime,\n })}\n </script>\n {linksToRender}\n {componentToRender}\n {data.length > 0 ? (\n <script id={`${name}_rsc`}>{data.join('\\n')}</script>\n ) : null}\n {nextData ? (\n <script\n id={`${bundle}_${route.replace(/\\//g, '_')}${name}_next_data`}\n type=\"application/json\"\n >\n {JSON.stringify(nextData)}\n </script>\n ) : null}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,6 +3,7 @@ import * as react from 'react';
|
|
|
3
3
|
import { R as RemoteComponentMetadata } from '../../types-280a3640.js';
|
|
4
4
|
|
|
5
5
|
interface RemoteComponentProps {
|
|
6
|
+
url: string;
|
|
6
7
|
name: string;
|
|
7
8
|
bundle: string;
|
|
8
9
|
route?: string;
|
|
@@ -39,6 +40,6 @@ declare module 'react/jsx-runtime' {
|
|
|
39
40
|
* This component handles the loading and rendering of remote microfrontends.
|
|
40
41
|
* It supports both RSC (React Server Components) and Next.js Pages Router based components.
|
|
41
42
|
*/
|
|
42
|
-
declare function RemoteComponentClient({ name, bundle, route, runtime, data, nextData, scripts, links, remoteShared, isolate, children, }: RemoteComponentProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
declare function RemoteComponentClient({ url, name, bundle, route, runtime, data, nextData, scripts, links, remoteShared, isolate, children, }: RemoteComponentProps): react_jsx_runtime.JSX.Element;
|
|
43
44
|
|
|
44
45
|
export { RemoteComponentClient };
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
RUNTIME_WEBPACK
|
|
10
10
|
} from "../../shared/client/remote-component";
|
|
11
11
|
function RemoteComponentClient({
|
|
12
|
+
url,
|
|
12
13
|
name,
|
|
13
14
|
bundle,
|
|
14
15
|
route = DEFAULT_ROUTE,
|
|
@@ -46,11 +47,12 @@ function RemoteComponentClient({
|
|
|
46
47
|
setShadowRoot(shadowRootElement);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
}, [name, isolate, shadowRoot]);
|
|
50
|
+
}, [name, isolate, shadowRoot, links, url]);
|
|
50
51
|
useEffect(() => {
|
|
51
52
|
let mounted = true;
|
|
52
53
|
if (!component && (isolate === false || shadowRoot)) {
|
|
53
54
|
loadRemoteComponent({
|
|
55
|
+
url: new URL(url, location.origin),
|
|
54
56
|
name,
|
|
55
57
|
bundle,
|
|
56
58
|
route,
|
|
@@ -79,6 +81,7 @@ function RemoteComponentClient({
|
|
|
79
81
|
mounted = false;
|
|
80
82
|
};
|
|
81
83
|
}, [
|
|
84
|
+
url,
|
|
82
85
|
component,
|
|
83
86
|
name,
|
|
84
87
|
bundle,
|
|
@@ -98,7 +101,7 @@ function RemoteComponentClient({
|
|
|
98
101
|
"link",
|
|
99
102
|
{
|
|
100
103
|
as: link.as,
|
|
101
|
-
href: link.href,
|
|
104
|
+
href: new URL(link.href, url || location.origin).href,
|
|
102
105
|
rel: link.rel
|
|
103
106
|
},
|
|
104
107
|
`${link.href}_${link.rel}`
|
|
@@ -131,15 +134,6 @@ function RemoteComponentClient({
|
|
|
131
134
|
}) }),
|
|
132
135
|
linksToRender,
|
|
133
136
|
componentToRender,
|
|
134
|
-
links.map((link) => /* @__PURE__ */ jsx(
|
|
135
|
-
"link",
|
|
136
|
-
{
|
|
137
|
-
as: link.as,
|
|
138
|
-
href: link.href,
|
|
139
|
-
rel: link.rel
|
|
140
|
-
},
|
|
141
|
-
`${link.href}_${link.rel}`
|
|
142
|
-
)),
|
|
143
137
|
data.length > 0 ? /* @__PURE__ */ jsx("script", { id: `${name}_rsc`, children: data.join("\n") }) : null,
|
|
144
138
|
nextData ? /* @__PURE__ */ jsx(
|
|
145
139
|
"script",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/app-client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState, useLayoutEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { shared } from '@remote-component/shared/host';\nimport {\n loadRemoteComponent,\n DEFAULT_ROUTE,\n RUNTIME_WEBPACK,\n} from '../../shared/client/remote-component';\nimport type { RemoteComponentProps } from '../../shared/client/remote-component';\n\n// patch react/jsx-runtime to support the shadowrootmode attribute on template elements\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n template: {\n shadowrootmode?: 'open' | 'closed';\n children: React.ReactNode;\n };\n }\n }\n}\n\n/**\n * RemoteComponentClient - Main component for rendering remote components\n *\n * This component handles the loading and rendering of remote microfrontends.\n * It supports both RSC (React Server Components) and Next.js Pages Router based components.\n */\nexport function RemoteComponentClient({\n name,\n bundle,\n route = DEFAULT_ROUTE,\n runtime = RUNTIME_WEBPACK,\n data,\n nextData,\n scripts = [],\n links = [],\n remoteShared = {},\n isolate,\n children,\n}: RemoteComponentProps) {\n const [component, setComponent] = useState<React.ReactNode | Error>(null);\n\n // Handle errors by re-throwing them\n if (component instanceof Error) {\n throw component;\n }\n\n // determine whether to use children or loaded component\n const shouldUseChildren =\n (!component ||\n (component &&\n !nextData &&\n typeof (component as unknown as Promise<unknown>).then !==\n 'function')) &&\n // if the remote Next.js Pages Router application is in development mode\n // we don't use the provided static HTML\n // to mitigate layout shift when loading CSS using JavaScript on the client\n nextData?.buildId !== 'development';\n\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useLayoutEffect(() => {\n if (isolate !== false && typeof document !== 'undefined' && !shadowRoot) {\n let shadowRootElement: ShadowRoot | null = null;\n const element = document.getElementById(`shadowroot_${name}`);\n shadowRootElement = element?.shadowRoot ?? null;\n\n if (!shadowRootElement && element) {\n // create a shadow root if it doesn't exist\n // this is a fallback for browsers that don't support declarative shadow DOM\n element.attachShadow({ mode: 'open' });\n shadowRootElement = element.shadowRoot;\n }\n\n if (shadowRootElement) {\n // remove all nodes from the shadow root except links\n shadowRootElement.querySelectorAll('*:not(link)').forEach((node) => {\n node.remove();\n });\n setShadowRoot(shadowRootElement);\n }\n }\n }, [name, isolate, shadowRoot]);\n\n useEffect(() => {\n let mounted = true;\n\n // if we have a component, we don't need to load it again\n if (!component && (isolate === false || shadowRoot)) {\n loadRemoteComponent({\n name,\n bundle,\n route,\n runtime,\n data,\n nextData,\n scripts,\n shared,\n remoteShared,\n container: shadowRoot,\n })\n .then((result) => {\n if (mounted) {\n if (result.error) {\n setComponent(result.error);\n } else {\n setComponent(result.component);\n }\n }\n })\n .catch((error: Error) => {\n if (mounted) {\n setComponent(error);\n }\n });\n }\n\n return () => {\n mounted = false;\n };\n }, [\n component,\n name,\n bundle,\n route,\n runtime,\n scripts,\n data,\n nextData,\n remoteShared,\n children,\n links,\n isolate,\n shadowRoot,\n ]);\n\n let componentToRender = (\n <>{shouldUseChildren ? children : (component as React.ReactNode)}</>\n );\n let linksToRender: React.ReactNode[] | null = links.map((link) => (\n <link\n as={link.as as string}\n href={link.href as string}\n key={`${link.href as string}_${link.rel}`}\n rel={link.rel as string}\n />\n ));\n\n if (isolate !== false) {\n componentToRender = (\n <div id={`shadowroot_${name}`}>\n {typeof document === 'undefined' ? (\n // eslint-disable-next-line react/no-unknown-property\n <template shadowrootmode=\"open\">\n {linksToRender}\n {componentToRender}\n </template>\n ) : null}\n {shadowRoot\n ? createPortal(\n shadowRoot.querySelectorAll('link').length !== links.length ? (\n <>\n {linksToRender}\n {componentToRender}\n </>\n ) : (\n componentToRender\n ),\n shadowRoot,\n )\n : null}\n </div>\n );\n linksToRender = null;\n }\n\n return (\n <>\n <script data-remote-component type=\"application/json\">\n {JSON.stringify({\n name,\n bundle,\n route,\n runtime,\n })}\n </script>\n {linksToRender}\n {componentToRender}\n {
|
|
1
|
+
{"version":3,"sources":["../../../src/next/host/app-client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState, useLayoutEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { shared } from '@remote-component/shared/host';\nimport {\n loadRemoteComponent,\n DEFAULT_ROUTE,\n RUNTIME_WEBPACK,\n} from '../../shared/client/remote-component';\nimport type { RemoteComponentProps } from '../../shared/client/remote-component';\n\n// patch react/jsx-runtime to support the shadowrootmode attribute on template elements\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n template: {\n shadowrootmode?: 'open' | 'closed';\n children: React.ReactNode;\n };\n }\n }\n}\n\n/**\n * RemoteComponentClient - Main component for rendering remote components\n *\n * This component handles the loading and rendering of remote microfrontends.\n * It supports both RSC (React Server Components) and Next.js Pages Router based components.\n */\nexport function RemoteComponentClient({\n url,\n name,\n bundle,\n route = DEFAULT_ROUTE,\n runtime = RUNTIME_WEBPACK,\n data,\n nextData,\n scripts = [],\n links = [],\n remoteShared = {},\n isolate,\n children,\n}: RemoteComponentProps) {\n const [component, setComponent] = useState<React.ReactNode | Error>(null);\n\n // Handle errors by re-throwing them\n if (component instanceof Error) {\n throw component;\n }\n\n // determine whether to use children or loaded component\n const shouldUseChildren =\n (!component ||\n (component &&\n !nextData &&\n typeof (component as unknown as Promise<unknown>).then !==\n 'function')) &&\n // if the remote Next.js Pages Router application is in development mode\n // we don't use the provided static HTML\n // to mitigate layout shift when loading CSS using JavaScript on the client\n nextData?.buildId !== 'development';\n\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useLayoutEffect(() => {\n if (isolate !== false && typeof document !== 'undefined' && !shadowRoot) {\n let shadowRootElement: ShadowRoot | null = null;\n const element = document.getElementById(`shadowroot_${name}`);\n shadowRootElement = element?.shadowRoot ?? null;\n\n if (!shadowRootElement && element) {\n // create a shadow root if it doesn't exist\n // this is a fallback for browsers that don't support declarative shadow DOM\n element.attachShadow({ mode: 'open' });\n shadowRootElement = element.shadowRoot;\n }\n\n if (shadowRootElement) {\n // remove all nodes from the shadow root except links\n shadowRootElement.querySelectorAll('*:not(link)').forEach((node) => {\n node.remove();\n });\n setShadowRoot(shadowRootElement);\n }\n }\n }, [name, isolate, shadowRoot, links, url]);\n\n useEffect(() => {\n let mounted = true;\n\n // if we have a component, we don't need to load it again\n if (!component && (isolate === false || shadowRoot)) {\n loadRemoteComponent({\n url: new URL(url, location.origin),\n name,\n bundle,\n route,\n runtime,\n data,\n nextData,\n scripts,\n shared,\n remoteShared,\n container: shadowRoot,\n })\n .then((result) => {\n if (mounted) {\n if (result.error) {\n setComponent(result.error);\n } else {\n setComponent(result.component);\n }\n }\n })\n .catch((error: Error) => {\n if (mounted) {\n setComponent(error);\n }\n });\n }\n\n return () => {\n mounted = false;\n };\n }, [\n url,\n component,\n name,\n bundle,\n route,\n runtime,\n scripts,\n data,\n nextData,\n remoteShared,\n children,\n links,\n isolate,\n shadowRoot,\n ]);\n\n let componentToRender = (\n <>{shouldUseChildren ? children : (component as React.ReactNode)}</>\n );\n let linksToRender: React.ReactNode[] | null = links.map((link) => (\n <link\n as={link.as as string}\n href={new URL(link.href as string, url || location.origin).href}\n key={`${link.href as string}_${link.rel}`}\n rel={link.rel as string}\n />\n ));\n\n if (isolate !== false) {\n componentToRender = (\n <div id={`shadowroot_${name}`}>\n {typeof document === 'undefined' ? (\n // eslint-disable-next-line react/no-unknown-property\n <template shadowrootmode=\"open\">\n {linksToRender}\n {componentToRender}\n </template>\n ) : null}\n {shadowRoot\n ? createPortal(\n shadowRoot.querySelectorAll('link').length !== links.length ? (\n <>\n {linksToRender}\n {componentToRender}\n </>\n ) : (\n componentToRender\n ),\n shadowRoot,\n )\n : null}\n </div>\n );\n linksToRender = null;\n }\n\n return (\n <>\n <script data-remote-component type=\"application/json\">\n {JSON.stringify({\n name,\n bundle,\n route,\n runtime,\n })}\n </script>\n {linksToRender}\n {componentToRender}\n {data.length > 0 ? (\n <script id={`${name}_rsc`}>{data.join('\\n')}</script>\n ) : null}\n {nextData ? (\n <script\n id={`${bundle}_${route.replace(/\\//g, '_')}${name}_next_data`}\n type=\"application/json\"\n >\n {JSON.stringify(nextData)}\n </script>\n ) : null}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -38,7 +38,8 @@ async function RemoteComponent({
|
|
|
38
38
|
hydrationData,
|
|
39
39
|
nextData,
|
|
40
40
|
component,
|
|
41
|
-
remoteShared
|
|
41
|
+
remoteShared,
|
|
42
|
+
url
|
|
42
43
|
} = await (0, import_fetch_remote_component.fetchRemoteComponent)(src, headerList, true);
|
|
43
44
|
const name = metadata.id.replace(/_ssr$/, "");
|
|
44
45
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -54,6 +55,7 @@ async function RemoteComponent({
|
|
|
54
55
|
route: metadata.route,
|
|
55
56
|
runtime: metadata.runtime,
|
|
56
57
|
scripts,
|
|
58
|
+
url: url.href,
|
|
57
59
|
children: component
|
|
58
60
|
}
|
|
59
61
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/app-server.tsx"],"sourcesContent":["import { headers } from 'next/headers';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\nimport { RemoteComponentClient } from './app-client';\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\n/**\n * RemoteComponent is a Next.js component that fetches and renders a remote component.\n * It supports SSR and can isolate the remote component in a shadow DOM.\n *\n * @param src - The source URL of the remote component. When using Vercel Microfrontends, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.\n * @param isolate - Whether to isolate the remote component using a Shadow DOM wrapper. Defaults to `true`. Use `false` explicitly to disable isolation.\n * @returns A React component that renders the remote component.\n *\n * @example\n *\n * Use the `<RemoteComponent>` in your Next.js App Router application to consume a remote component from a remote application:\n *\n * ```tsx\n * import { RemoteComponent } from 'remote-components/next/host';\n *\n * export default function MyPage() {\n * return (\n * <>\n * <h1>Welcome to My Page</h1>\n * <p>This page consumes a remote component from another application.</p>\n * <RemoteComponent src=\"/nextjs-app-remote/components/header\" />\n * </>\n * );\n * }\n * ```\n */\nexport async function RemoteComponent({\n src,\n isolate,\n}: {\n src: string | URL;\n isolate?: boolean;\n}) {\n // get the headers from the request\n const headerList = await headers();\n\n const {\n metadata,\n scripts,\n links,\n hydrationData,\n nextData,\n component,\n remoteShared,\n } = await fetchRemoteComponent(src, headerList, true);\n\n // pass all remote component data to the SSR/client layer\n // render remote component static HTML as children\n // remote _ssr suffix from remote component id\n const name = metadata.id.replace(/_ssr$/, '');\n return (\n <RemoteComponentClient\n bundle={metadata.bundle || (CURRENT_ZONE ?? name)}\n data={hydrationData}\n isolate={isolate}\n links={links}\n name={name}\n nextData={nextData}\n remoteShared={remoteShared}\n route={metadata.route}\n runtime={metadata.runtime}\n scripts={scripts}\n >\n {component}\n </RemoteComponentClient>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../../src/next/host/app-server.tsx"],"sourcesContent":["import { headers } from 'next/headers';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\nimport { RemoteComponentClient } from './app-client';\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\n/**\n * RemoteComponent is a Next.js component that fetches and renders a remote component.\n * It supports SSR and can isolate the remote component in a shadow DOM.\n *\n * @param src - The source URL of the remote component. When using Vercel Microfrontends, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.\n * @param isolate - Whether to isolate the remote component using a Shadow DOM wrapper. Defaults to `true`. Use `false` explicitly to disable isolation.\n * @returns A React component that renders the remote component.\n *\n * @example\n *\n * Use the `<RemoteComponent>` in your Next.js App Router application to consume a remote component from a remote application:\n *\n * ```tsx\n * import { RemoteComponent } from 'remote-components/next/host';\n *\n * export default function MyPage() {\n * return (\n * <>\n * <h1>Welcome to My Page</h1>\n * <p>This page consumes a remote component from another application.</p>\n * <RemoteComponent src=\"/nextjs-app-remote/components/header\" />\n * </>\n * );\n * }\n * ```\n */\nexport async function RemoteComponent({\n src,\n isolate,\n}: {\n src: string | URL;\n isolate?: boolean;\n}) {\n // get the headers from the request\n const headerList = await headers();\n\n const {\n metadata,\n scripts,\n links,\n hydrationData,\n nextData,\n component,\n remoteShared,\n url,\n } = await fetchRemoteComponent(src, headerList, true);\n\n // pass all remote component data to the SSR/client layer\n // render remote component static HTML as children\n // remote _ssr suffix from remote component id\n const name = metadata.id.replace(/_ssr$/, '');\n return (\n <RemoteComponentClient\n bundle={metadata.bundle || (CURRENT_ZONE ?? name)}\n data={hydrationData}\n isolate={isolate}\n links={links}\n name={name}\n nextData={nextData}\n remoteShared={remoteShared}\n route={metadata.route}\n runtime={metadata.runtime}\n scripts={scripts}\n url={url.href}\n >\n {component}\n </RemoteComponentClient>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA0DI;AA1DJ,qBAAwB;AACxB,oCAAqC;AACrC,wBAAsC;AAEtC,MAAM,eAAe,QAAQ,IAAI;AA4BjC,eAAsB,gBAAgB;AAAA,EACpC;AAAA,EACA;AACF,GAGG;AAED,QAAM,aAAa,UAAM,wBAAQ;AAEjC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,UAAM,oDAAqB,KAAK,YAAY,IAAI;AAKpD,QAAM,OAAO,SAAS,GAAG,QAAQ,SAAS,EAAE;AAC5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ,SAAS,WAAW,gBAAgB;AAAA,MAC5C,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,SAAS,SAAS;AAAA,MAClB;AAAA,MACA,KAAK,IAAI;AAAA,MAER;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -15,7 +15,8 @@ async function RemoteComponent({
|
|
|
15
15
|
hydrationData,
|
|
16
16
|
nextData,
|
|
17
17
|
component,
|
|
18
|
-
remoteShared
|
|
18
|
+
remoteShared,
|
|
19
|
+
url
|
|
19
20
|
} = await fetchRemoteComponent(src, headerList, true);
|
|
20
21
|
const name = metadata.id.replace(/_ssr$/, "");
|
|
21
22
|
return /* @__PURE__ */ jsx(
|
|
@@ -31,6 +32,7 @@ async function RemoteComponent({
|
|
|
31
32
|
route: metadata.route,
|
|
32
33
|
runtime: metadata.runtime,
|
|
33
34
|
scripts,
|
|
35
|
+
url: url.href,
|
|
34
36
|
children: component
|
|
35
37
|
}
|
|
36
38
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/app-server.tsx"],"sourcesContent":["import { headers } from 'next/headers';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\nimport { RemoteComponentClient } from './app-client';\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\n/**\n * RemoteComponent is a Next.js component that fetches and renders a remote component.\n * It supports SSR and can isolate the remote component in a shadow DOM.\n *\n * @param src - The source URL of the remote component. When using Vercel Microfrontends, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.\n * @param isolate - Whether to isolate the remote component using a Shadow DOM wrapper. Defaults to `true`. Use `false` explicitly to disable isolation.\n * @returns A React component that renders the remote component.\n *\n * @example\n *\n * Use the `<RemoteComponent>` in your Next.js App Router application to consume a remote component from a remote application:\n *\n * ```tsx\n * import { RemoteComponent } from 'remote-components/next/host';\n *\n * export default function MyPage() {\n * return (\n * <>\n * <h1>Welcome to My Page</h1>\n * <p>This page consumes a remote component from another application.</p>\n * <RemoteComponent src=\"/nextjs-app-remote/components/header\" />\n * </>\n * );\n * }\n * ```\n */\nexport async function RemoteComponent({\n src,\n isolate,\n}: {\n src: string | URL;\n isolate?: boolean;\n}) {\n // get the headers from the request\n const headerList = await headers();\n\n const {\n metadata,\n scripts,\n links,\n hydrationData,\n nextData,\n component,\n remoteShared,\n } = await fetchRemoteComponent(src, headerList, true);\n\n // pass all remote component data to the SSR/client layer\n // render remote component static HTML as children\n // remote _ssr suffix from remote component id\n const name = metadata.id.replace(/_ssr$/, '');\n return (\n <RemoteComponentClient\n bundle={metadata.bundle || (CURRENT_ZONE ?? name)}\n data={hydrationData}\n isolate={isolate}\n links={links}\n name={name}\n nextData={nextData}\n remoteShared={remoteShared}\n route={metadata.route}\n runtime={metadata.runtime}\n scripts={scripts}\n >\n {component}\n </RemoteComponentClient>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/next/host/app-server.tsx"],"sourcesContent":["import { headers } from 'next/headers';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\nimport { RemoteComponentClient } from './app-client';\n\nconst CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\n/**\n * RemoteComponent is a Next.js component that fetches and renders a remote component.\n * It supports SSR and can isolate the remote component in a shadow DOM.\n *\n * @param src - The source URL of the remote component. When using Vercel Microfrontends, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.\n * @param isolate - Whether to isolate the remote component using a Shadow DOM wrapper. Defaults to `true`. Use `false` explicitly to disable isolation.\n * @returns A React component that renders the remote component.\n *\n * @example\n *\n * Use the `<RemoteComponent>` in your Next.js App Router application to consume a remote component from a remote application:\n *\n * ```tsx\n * import { RemoteComponent } from 'remote-components/next/host';\n *\n * export default function MyPage() {\n * return (\n * <>\n * <h1>Welcome to My Page</h1>\n * <p>This page consumes a remote component from another application.</p>\n * <RemoteComponent src=\"/nextjs-app-remote/components/header\" />\n * </>\n * );\n * }\n * ```\n */\nexport async function RemoteComponent({\n src,\n isolate,\n}: {\n src: string | URL;\n isolate?: boolean;\n}) {\n // get the headers from the request\n const headerList = await headers();\n\n const {\n metadata,\n scripts,\n links,\n hydrationData,\n nextData,\n component,\n remoteShared,\n url,\n } = await fetchRemoteComponent(src, headerList, true);\n\n // pass all remote component data to the SSR/client layer\n // render remote component static HTML as children\n // remote _ssr suffix from remote component id\n const name = metadata.id.replace(/_ssr$/, '');\n return (\n <RemoteComponentClient\n bundle={metadata.bundle || (CURRENT_ZONE ?? name)}\n data={hydrationData}\n isolate={isolate}\n links={links}\n name={name}\n nextData={nextData}\n remoteShared={remoteShared}\n route={metadata.route}\n runtime={metadata.runtime}\n scripts={scripts}\n url={url.href}\n >\n {component}\n </RemoteComponentClient>\n );\n}\n"],"mappings":"AA0DI;AA1DJ,SAAS,eAAe;AACxB,SAAS,4BAA4B;AACrC,SAAS,6BAA6B;AAEtC,MAAM,eAAe,QAAQ,IAAI;AA4BjC,eAAsB,gBAAgB;AAAA,EACpC;AAAA,EACA;AACF,GAGG;AAED,QAAM,aAAa,MAAM,QAAQ;AAEjC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM,qBAAqB,KAAK,YAAY,IAAI;AAKpD,QAAM,OAAO,SAAS,GAAG,QAAQ,SAAS,EAAE;AAC5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ,SAAS,WAAW,gBAAgB;AAAA,MAC5C,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,SAAS,SAAS;AAAA,MAClB;AAAA,MACA,KAAK,IAAI;AAAA,MAER;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/next/middleware/index.ts
|
|
21
|
+
var middleware_exports = {};
|
|
22
|
+
__export(middleware_exports, {
|
|
23
|
+
config: () => config,
|
|
24
|
+
withRemoteComponents: () => withRemoteComponents
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(middleware_exports);
|
|
27
|
+
var import_server = require("next/server");
|
|
28
|
+
function corsFromOptions(options) {
|
|
29
|
+
const ALLOWED_ORIGINS = (process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS || (Array.isArray(options?.origin) ? options.origin.join(",") : options?.origin) || "*").split(",").map((origin) => origin.trim());
|
|
30
|
+
const CORS_HEADERS = {
|
|
31
|
+
"Access-Control-Allow-Methods": process.env.REMOTE_COMPONENTS_ALLOW_METHODS || (Array.isArray(options?.method) ? options.method.map((m) => m.trim()).join(",") : options?.method) || "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
32
|
+
"Access-Control-Allow-Headers": `${process.env.REMOTE_COMPONENTS_ALLOW_HEADERS || (Array.isArray(options?.headers) ? options.headers.map((h) => h.trim()).join(",") : options?.headers) || "Content-Type,Authorization"},vercel-remote-component-url`,
|
|
33
|
+
...process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || options?.credentials ? {
|
|
34
|
+
"Access-Control-Allow-Credentials": process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || "true"
|
|
35
|
+
} : {}
|
|
36
|
+
};
|
|
37
|
+
return { ALLOWED_ORIGINS, CORS_HEADERS };
|
|
38
|
+
}
|
|
39
|
+
function withRemoteComponents(middleware, options) {
|
|
40
|
+
return async (request) => {
|
|
41
|
+
const { ALLOWED_ORIGINS, CORS_HEADERS } = corsFromOptions(options?.cors);
|
|
42
|
+
const origin = request.headers.get("origin") ?? "";
|
|
43
|
+
const isAllowed = ALLOWED_ORIGINS.includes("*") || ALLOWED_ORIGINS.includes(origin);
|
|
44
|
+
if (request.method === "OPTIONS") {
|
|
45
|
+
return new Response(null, {
|
|
46
|
+
status: 200,
|
|
47
|
+
headers: isAllowed ? {
|
|
48
|
+
"Access-Control-Allow-Origin": origin,
|
|
49
|
+
...CORS_HEADERS
|
|
50
|
+
} : {}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const response = typeof middleware === "function" ? await middleware(request) : import_server.NextResponse.next();
|
|
54
|
+
if (isAllowed) {
|
|
55
|
+
response.headers.set("Access-Control-Allow-Origin", origin);
|
|
56
|
+
}
|
|
57
|
+
Object.entries(CORS_HEADERS).forEach(
|
|
58
|
+
([k, v]) => response.headers.set(k, v)
|
|
59
|
+
);
|
|
60
|
+
return response;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
var config = {
|
|
64
|
+
matcher: "/:path*"
|
|
65
|
+
};
|
|
66
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
67
|
+
0 && (module.exports = {
|
|
68
|
+
config,
|
|
69
|
+
withRemoteComponents
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=middleware.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/next/middleware/index.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\n\nexport interface RemoteComponentMiddlewareOptions {\n cors?: {\n origin?: string | string[];\n method?: string | string[];\n headers?: string | string[];\n credentials?: boolean;\n };\n}\n\nfunction corsFromOptions(options?: RemoteComponentMiddlewareOptions['cors']) {\n const ALLOWED_ORIGINS = (\n process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS ||\n (Array.isArray(options?.origin)\n ? options.origin.join(',')\n : options?.origin) ||\n '*'\n )\n .split(',')\n .map((origin) => origin.trim());\n\n const CORS_HEADERS = {\n 'Access-Control-Allow-Methods':\n process.env.REMOTE_COMPONENTS_ALLOW_METHODS ||\n (Array.isArray(options?.method)\n ? options.method.map((m) => m.trim()).join(',')\n : options?.method) ||\n 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS',\n 'Access-Control-Allow-Headers': `${\n process.env.REMOTE_COMPONENTS_ALLOW_HEADERS ||\n (Array.isArray(options?.headers)\n ? options.headers.map((h) => h.trim()).join(',')\n : options?.headers) ||\n 'Content-Type,Authorization'\n },vercel-remote-component-url`,\n ...(process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || options?.credentials\n ? {\n 'Access-Control-Allow-Credentials':\n process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || 'true',\n }\n : {}),\n };\n\n return { ALLOWED_ORIGINS, CORS_HEADERS };\n}\n\n/**\n * This middleware is used to handle CORS and other remote component related tasks.\n * It can be used to wrap a Next.js middleware function to add CORS headers and handle preflight requests.\n *\n * @param middleware - The Next.js middleware function to wrap.\n * @param options - Optional configuration for handling remote components.\n * @returns A Next.js middleware function that handles CORS and preflight requests\n */\nexport function withRemoteComponents(\n middleware?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: RemoteComponentMiddlewareOptions,\n) {\n return async (request: NextRequest) => {\n const { ALLOWED_ORIGINS, CORS_HEADERS } = corsFromOptions(options?.cors);\n\n const origin = request.headers.get('origin') ?? '';\n const isAllowed =\n ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);\n\n // Handle preflight\n if (request.method === 'OPTIONS') {\n return new Response(null, {\n status: 200,\n headers: isAllowed\n ? {\n 'Access-Control-Allow-Origin': origin,\n ...CORS_HEADERS,\n }\n : {},\n });\n }\n\n // For all other requests, continue and attach CORS\n const response =\n typeof middleware === 'function'\n ? await middleware(request)\n : NextResponse.next();\n\n if (isAllowed) {\n response.headers.set('Access-Control-Allow-Origin', origin);\n }\n\n Object.entries(CORS_HEADERS).forEach(([k, v]) =>\n response.headers.set(k, v),\n );\n\n return response;\n };\n}\n\n/**\n * This configuration is used to specify the paths that the middleware should match.\n * It matches all paths by default.\n */\nexport const config = {\n matcher: '/:path*',\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+C;AAW/C,SAAS,gBAAgB,SAAoD;AAC3E,QAAM,mBACJ,QAAQ,IAAI,sCACX,MAAM,QAAQ,SAAS,MAAM,IAC1B,QAAQ,OAAO,KAAK,GAAG,IACvB,SAAS,WACb,KAEC,MAAM,GAAG,EACT,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC;AAEhC,QAAM,eAAe;AAAA,IACnB,gCACE,QAAQ,IAAI,oCACX,MAAM,QAAQ,SAAS,MAAM,IAC1B,QAAQ,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAC5C,SAAS,WACb;AAAA,IACF,gCAAgC,GAC9B,QAAQ,IAAI,oCACX,MAAM,QAAQ,SAAS,OAAO,IAC3B,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAC7C,SAAS,YACb;AAAA,IAEF,GAAI,QAAQ,IAAI,uCAAuC,SAAS,cAC5D;AAAA,MACE,oCACE,QAAQ,IAAI,uCAAuC;AAAA,IACvD,IACA,CAAC;AAAA,EACP;AAEA,SAAO,EAAE,iBAAiB,aAAa;AACzC;AAUO,SAAS,qBACd,YACA,SACA;AACA,SAAO,OAAO,YAAyB;AACrC,UAAM,EAAE,iBAAiB,aAAa,IAAI,gBAAgB,SAAS,IAAI;AAEvE,UAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAChD,UAAM,YACJ,gBAAgB,SAAS,GAAG,KAAK,gBAAgB,SAAS,MAAM;AAGlE,QAAI,QAAQ,WAAW,WAAW;AAChC,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS,YACL;AAAA,UACE,+BAA+B;AAAA,UAC/B,GAAG;AAAA,QACL,IACA,CAAC;AAAA,MACP,CAAC;AAAA,IACH;AAGA,UAAM,WACJ,OAAO,eAAe,aAClB,MAAM,WAAW,OAAO,IACxB,2BAAa,KAAK;AAExB,QAAI,WAAW;AACb,eAAS,QAAQ,IAAI,+BAA+B,MAAM;AAAA,IAC5D;AAEA,WAAO,QAAQ,YAAY,EAAE;AAAA,MAAQ,CAAC,CAAC,GAAG,CAAC,MACzC,SAAS,QAAQ,IAAI,GAAG,CAAC;AAAA,IAC3B;AAEA,WAAO;AAAA,EACT;AACF;AAMO,IAAM,SAAS;AAAA,EACpB,SAAS;AACX;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
interface RemoteComponentMiddlewareOptions {
|
|
4
|
+
cors?: {
|
|
5
|
+
origin?: string | string[];
|
|
6
|
+
method?: string | string[];
|
|
7
|
+
headers?: string | string[];
|
|
8
|
+
credentials?: boolean;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* This middleware is used to handle CORS and other remote component related tasks.
|
|
13
|
+
* It can be used to wrap a Next.js middleware function to add CORS headers and handle preflight requests.
|
|
14
|
+
*
|
|
15
|
+
* @param middleware - The Next.js middleware function to wrap.
|
|
16
|
+
* @param options - Optional configuration for handling remote components.
|
|
17
|
+
* @returns A Next.js middleware function that handles CORS and preflight requests
|
|
18
|
+
*/
|
|
19
|
+
declare function withRemoteComponents(middleware?: (request: NextRequest) => NextResponse | Promise<NextResponse>, options?: RemoteComponentMiddlewareOptions): (request: NextRequest) => Promise<Response>;
|
|
20
|
+
/**
|
|
21
|
+
* This configuration is used to specify the paths that the middleware should match.
|
|
22
|
+
* It matches all paths by default.
|
|
23
|
+
*/
|
|
24
|
+
declare const config: {
|
|
25
|
+
matcher: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { RemoteComponentMiddlewareOptions, config, withRemoteComponents };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/next/middleware/index.ts
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
function corsFromOptions(options) {
|
|
4
|
+
const ALLOWED_ORIGINS = (process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS || (Array.isArray(options?.origin) ? options.origin.join(",") : options?.origin) || "*").split(",").map((origin) => origin.trim());
|
|
5
|
+
const CORS_HEADERS = {
|
|
6
|
+
"Access-Control-Allow-Methods": process.env.REMOTE_COMPONENTS_ALLOW_METHODS || (Array.isArray(options?.method) ? options.method.map((m) => m.trim()).join(",") : options?.method) || "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
7
|
+
"Access-Control-Allow-Headers": `${process.env.REMOTE_COMPONENTS_ALLOW_HEADERS || (Array.isArray(options?.headers) ? options.headers.map((h) => h.trim()).join(",") : options?.headers) || "Content-Type,Authorization"},vercel-remote-component-url`,
|
|
8
|
+
...process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || options?.credentials ? {
|
|
9
|
+
"Access-Control-Allow-Credentials": process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || "true"
|
|
10
|
+
} : {}
|
|
11
|
+
};
|
|
12
|
+
return { ALLOWED_ORIGINS, CORS_HEADERS };
|
|
13
|
+
}
|
|
14
|
+
function withRemoteComponents(middleware, options) {
|
|
15
|
+
return async (request) => {
|
|
16
|
+
const { ALLOWED_ORIGINS, CORS_HEADERS } = corsFromOptions(options?.cors);
|
|
17
|
+
const origin = request.headers.get("origin") ?? "";
|
|
18
|
+
const isAllowed = ALLOWED_ORIGINS.includes("*") || ALLOWED_ORIGINS.includes(origin);
|
|
19
|
+
if (request.method === "OPTIONS") {
|
|
20
|
+
return new Response(null, {
|
|
21
|
+
status: 200,
|
|
22
|
+
headers: isAllowed ? {
|
|
23
|
+
"Access-Control-Allow-Origin": origin,
|
|
24
|
+
...CORS_HEADERS
|
|
25
|
+
} : {}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const response = typeof middleware === "function" ? await middleware(request) : NextResponse.next();
|
|
29
|
+
if (isAllowed) {
|
|
30
|
+
response.headers.set("Access-Control-Allow-Origin", origin);
|
|
31
|
+
}
|
|
32
|
+
Object.entries(CORS_HEADERS).forEach(
|
|
33
|
+
([k, v]) => response.headers.set(k, v)
|
|
34
|
+
);
|
|
35
|
+
return response;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
var config = {
|
|
39
|
+
matcher: "/:path*"
|
|
40
|
+
};
|
|
41
|
+
export {
|
|
42
|
+
config,
|
|
43
|
+
withRemoteComponents
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/next/middleware/index.ts"],"sourcesContent":["import { type NextRequest, NextResponse } from 'next/server';\n\nexport interface RemoteComponentMiddlewareOptions {\n cors?: {\n origin?: string | string[];\n method?: string | string[];\n headers?: string | string[];\n credentials?: boolean;\n };\n}\n\nfunction corsFromOptions(options?: RemoteComponentMiddlewareOptions['cors']) {\n const ALLOWED_ORIGINS = (\n process.env.REMOTE_COMPONENTS_ALLOWED_ORIGINS ||\n (Array.isArray(options?.origin)\n ? options.origin.join(',')\n : options?.origin) ||\n '*'\n )\n .split(',')\n .map((origin) => origin.trim());\n\n const CORS_HEADERS = {\n 'Access-Control-Allow-Methods':\n process.env.REMOTE_COMPONENTS_ALLOW_METHODS ||\n (Array.isArray(options?.method)\n ? options.method.map((m) => m.trim()).join(',')\n : options?.method) ||\n 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS',\n 'Access-Control-Allow-Headers': `${\n process.env.REMOTE_COMPONENTS_ALLOW_HEADERS ||\n (Array.isArray(options?.headers)\n ? options.headers.map((h) => h.trim()).join(',')\n : options?.headers) ||\n 'Content-Type,Authorization'\n },vercel-remote-component-url`,\n ...(process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || options?.credentials\n ? {\n 'Access-Control-Allow-Credentials':\n process.env.REMOTE_COMPONENTS_ALLOW_CREDENTIALS || 'true',\n }\n : {}),\n };\n\n return { ALLOWED_ORIGINS, CORS_HEADERS };\n}\n\n/**\n * This middleware is used to handle CORS and other remote component related tasks.\n * It can be used to wrap a Next.js middleware function to add CORS headers and handle preflight requests.\n *\n * @param middleware - The Next.js middleware function to wrap.\n * @param options - Optional configuration for handling remote components.\n * @returns A Next.js middleware function that handles CORS and preflight requests\n */\nexport function withRemoteComponents(\n middleware?: (request: NextRequest) => NextResponse | Promise<NextResponse>,\n options?: RemoteComponentMiddlewareOptions,\n) {\n return async (request: NextRequest) => {\n const { ALLOWED_ORIGINS, CORS_HEADERS } = corsFromOptions(options?.cors);\n\n const origin = request.headers.get('origin') ?? '';\n const isAllowed =\n ALLOWED_ORIGINS.includes('*') || ALLOWED_ORIGINS.includes(origin);\n\n // Handle preflight\n if (request.method === 'OPTIONS') {\n return new Response(null, {\n status: 200,\n headers: isAllowed\n ? {\n 'Access-Control-Allow-Origin': origin,\n ...CORS_HEADERS,\n }\n : {},\n });\n }\n\n // For all other requests, continue and attach CORS\n const response =\n typeof middleware === 'function'\n ? await middleware(request)\n : NextResponse.next();\n\n if (isAllowed) {\n response.headers.set('Access-Control-Allow-Origin', origin);\n }\n\n Object.entries(CORS_HEADERS).forEach(([k, v]) =>\n response.headers.set(k, v),\n );\n\n return response;\n };\n}\n\n/**\n * This configuration is used to specify the paths that the middleware should match.\n * It matches all paths by default.\n */\nexport const config = {\n matcher: '/:path*',\n};\n"],"mappings":";AAAA,SAA2B,oBAAoB;AAW/C,SAAS,gBAAgB,SAAoD;AAC3E,QAAM,mBACJ,QAAQ,IAAI,sCACX,MAAM,QAAQ,SAAS,MAAM,IAC1B,QAAQ,OAAO,KAAK,GAAG,IACvB,SAAS,WACb,KAEC,MAAM,GAAG,EACT,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC;AAEhC,QAAM,eAAe;AAAA,IACnB,gCACE,QAAQ,IAAI,oCACX,MAAM,QAAQ,SAAS,MAAM,IAC1B,QAAQ,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAC5C,SAAS,WACb;AAAA,IACF,gCAAgC,GAC9B,QAAQ,IAAI,oCACX,MAAM,QAAQ,SAAS,OAAO,IAC3B,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAC7C,SAAS,YACb;AAAA,IAEF,GAAI,QAAQ,IAAI,uCAAuC,SAAS,cAC5D;AAAA,MACE,oCACE,QAAQ,IAAI,uCAAuC;AAAA,IACvD,IACA,CAAC;AAAA,EACP;AAEA,SAAO,EAAE,iBAAiB,aAAa;AACzC;AAUO,SAAS,qBACd,YACA,SACA;AACA,SAAO,OAAO,YAAyB;AACrC,UAAM,EAAE,iBAAiB,aAAa,IAAI,gBAAgB,SAAS,IAAI;AAEvE,UAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAChD,UAAM,YACJ,gBAAgB,SAAS,GAAG,KAAK,gBAAgB,SAAS,MAAM;AAGlE,QAAI,QAAQ,WAAW,WAAW;AAChC,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS,YACL;AAAA,UACE,+BAA+B;AAAA,UAC/B,GAAG;AAAA,QACL,IACA,CAAC;AAAA,MACP,CAAC;AAAA,IACH;AAGA,UAAM,WACJ,OAAO,eAAe,aAClB,MAAM,WAAW,OAAO,IACxB,aAAa,KAAK;AAExB,QAAI,WAAW;AACb,eAAS,QAAQ,IAAI,+BAA+B,MAAM;AAAA,IAC5D;AAEA,WAAO,QAAQ,YAAY,EAAE;AAAA,MAAQ,CAAC,CAAC,GAAG,CAAC,MACzC,SAAS,QAAQ,IAAI,GAAG,CAAC;AAAA,IAC3B;AAEA,WAAO;AAAA,EACT;AACF;AAMO,IAAM,SAAS;AAAA,EACpB,SAAS;AACX;","names":[]}
|
|
@@ -37,7 +37,14 @@ var import_work_async_storage = require("next/dist/server/app-render/work-async-
|
|
|
37
37
|
const SERVER_ACTION_MANIFESTS_SINGLETON = Symbol.for(
|
|
38
38
|
"next.server.action-manifests"
|
|
39
39
|
);
|
|
40
|
-
const
|
|
40
|
+
const PROJECT_ID = process.env.REMOTE_COMPONENTS_PROJECT_ID;
|
|
41
|
+
async function tryImport(importer) {
|
|
42
|
+
try {
|
|
43
|
+
return await importer();
|
|
44
|
+
} catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
41
48
|
function RemoteComponentData({ name, data }) {
|
|
42
49
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { id: `${name}_rsc`, children: data.map(
|
|
43
50
|
(chunk, i) => (
|
|
@@ -69,7 +76,7 @@ async function RemoteComponent({
|
|
|
69
76
|
const ssrModuleMapping = { ...manifest?.ssrModuleMapping };
|
|
70
77
|
clientModules = Object.fromEntries(
|
|
71
78
|
Object.entries(clientModules).map(([key, value]) => {
|
|
72
|
-
const remoteId = `[${
|
|
79
|
+
const remoteId = `[${PROJECT_ID}] ${value.id}`;
|
|
73
80
|
ssrModuleMapping[remoteId] = ssrModuleMapping[value.id];
|
|
74
81
|
return [
|
|
75
82
|
key,
|
|
@@ -78,21 +85,39 @@ async function RemoteComponent({
|
|
|
78
85
|
id: remoteId,
|
|
79
86
|
// prepend the current zone to the chunks to handle remote component chunk loading in Webpack
|
|
80
87
|
// this is required to avoid loading the wrong chunk in the host application
|
|
81
|
-
chunks: value.chunks.map((chunk) => `[${
|
|
88
|
+
chunks: value.chunks.map((chunk) => `[${PROJECT_ID}] ${chunk}`)
|
|
82
89
|
// async: true,
|
|
83
90
|
}
|
|
84
91
|
];
|
|
85
92
|
})
|
|
86
93
|
);
|
|
87
|
-
const [
|
|
94
|
+
const [
|
|
95
|
+
{ renderToReadableStream: nextRenderToReadableStream },
|
|
96
|
+
{ renderToReadableStream: legacyRenderToReadableStream },
|
|
97
|
+
{ RemoteComponentSSR }
|
|
98
|
+
] = await Promise.all(
|
|
88
99
|
process.env.TURBOPACK ? [
|
|
89
|
-
|
|
100
|
+
tryImport(
|
|
101
|
+
() => import("react-server-dom-turbopack/server")
|
|
102
|
+
),
|
|
103
|
+
tryImport(
|
|
104
|
+
() => import("react-server-dom-turbopack/server.edge")
|
|
105
|
+
),
|
|
90
106
|
import("./render-client-turbopack")
|
|
91
107
|
] : [
|
|
92
|
-
|
|
108
|
+
tryImport(
|
|
109
|
+
() => import("react-server-dom-webpack/server")
|
|
110
|
+
),
|
|
111
|
+
tryImport(
|
|
112
|
+
() => import("react-server-dom-webpack/server.edge")
|
|
113
|
+
),
|
|
93
114
|
import("./render-client-webpack")
|
|
94
115
|
]
|
|
95
116
|
);
|
|
117
|
+
const renderToReadableStream = nextRenderToReadableStream ?? legacyRenderToReadableStream;
|
|
118
|
+
if (typeof renderToReadableStream !== "function") {
|
|
119
|
+
throw new Error("No valid renderToReadableStream function found");
|
|
120
|
+
}
|
|
96
121
|
const stream = renderToReadableStream(children, clientModules);
|
|
97
122
|
const data = [];
|
|
98
123
|
const decoder = new TextDecoder();
|
|
@@ -106,7 +131,7 @@ async function RemoteComponent({
|
|
|
106
131
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
107
132
|
"div",
|
|
108
133
|
{
|
|
109
|
-
"data-bundle":
|
|
134
|
+
"data-bundle": PROJECT_ID,
|
|
110
135
|
"data-route": route,
|
|
111
136
|
"data-runtime": runtime,
|
|
112
137
|
id: `${remoteComponentName}_ssr`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/remote/render-server.tsx"],"sourcesContent":["import { headers } from 'next/headers';\nimport { workAsyncStorage } from 'next/dist/server/app-render/work-async-storage.external';\nimport type { Manifest } from './types';\n\n// internal Next.js symbol to access the manifest which is stored in the global scope\nconst SERVER_ACTION_MANIFESTS_SINGLETON = Symbol.for(\n 'next.server.action-manifests',\n);\n\nconst
|
|
1
|
+
{"version":3,"sources":["../../../src/next/remote/render-server.tsx"],"sourcesContent":["import { headers } from 'next/headers';\nimport { workAsyncStorage } from 'next/dist/server/app-render/work-async-storage.external';\nimport type { Manifest } from './types';\n\n// internal Next.js symbol to access the manifest which is stored in the global scope\nconst SERVER_ACTION_MANIFESTS_SINGLETON = Symbol.for(\n 'next.server.action-manifests',\n);\n\nconst PROJECT_ID = process.env.REMOTE_COMPONENTS_PROJECT_ID;\n\nasync function tryImport<T>(importer: () => Promise<T>) {\n try {\n return (await importer()) as T;\n } catch {\n return {} as T;\n }\n}\n\n// inject the RSC flight data into the HTML response using <script>\n// the RSC flight data is used to hydrate the remote component on the host\n// this approach is similar to an island architecture on the host\n// the remote component is static HTML until it is hydrated using this RSC flight data\nfunction RemoteComponentData({ name, data }: { name: string; data: string[] }) {\n return (\n <script id={`${name}_rsc`}>\n {data\n .map(\n (chunk, i) =>\n // make the data handling somewhat safe\n `${i === 0 ? `self[\"${name}\"]=self[\"${name}\"]||[];` : ''}self[\"${name}\"].push(${JSON.stringify(\n chunk,\n )});`,\n )\n .join('\\n')}\n </script>\n );\n}\n\n/**\n * RemoteComponent is a Next.js component that exposes a remote component\n * that can be used in a host application.\n *\n * @param name - The name of the remote component. Use a unique name to expose multiple remote components from the same page.\n * @param children - The content of the remote component. This is the content that will be rendered as the remote component.\n * @returns A React component that renders the remote component.\n *\n * @example\n *\n * Use the `<RemoteComponent>` in your Next.js App Router application to expose the children as a remote component:\n *\n * ```tsx\n * import { RemoteComponent } from 'remote-components/next';\n *\n * export default function MyPage() {\n * return (\n * <RemoteComponent>\n * <h1>Hello from the remote component!</h1>\n * <p>This is a remote component that can be used in a host application.</p>\n * </RemoteComponent>\n * );\n * }\n * ```\n */\nexport async function RemoteComponent({\n name = '__vercel_remote_component',\n children,\n}: {\n name?: string;\n children: React.ReactNode;\n}): Promise<React.ReactNode> {\n const headersList = await headers();\n const url = headersList.get('Vercel-Remote-Component-Url');\n const isRemote = url !== null;\n\n if (!isRemote) {\n return children;\n }\n\n // this URL passed by the remote component consumer is only used to know the public address of the remote component\n // it is only used to determine if we need to mutate the client module map for now\n\n const { pathname } = new URL(url);\n // access the internal Next.js work store to get the active page and route\n const { page, route } = workAsyncStorage.getStore() ?? { page: pathname };\n\n // get reference to the manifests from the global scope\n const manifests = (\n globalThis as typeof globalThis & {\n [SERVER_ACTION_MANIFESTS_SINGLETON]: {\n clientReferenceManifestsPerPage?: Record<string, Manifest>;\n };\n }\n )[SERVER_ACTION_MANIFESTS_SINGLETON];\n const manifest = manifests.clientReferenceManifestsPerPage?.[route ?? page];\n\n const self = globalThis as typeof globalThis & {\n [SERVER_ACTION_MANIFESTS_SINGLETON]?: {\n clientReferenceManifestsPerPage?: Record<string, Manifest>;\n };\n __RSC_MANIFEST?: Record<string, unknown>;\n };\n\n // manually handle the internal Next.js manifest\n self.__RSC_MANIFEST = self.__RSC_MANIFEST || {};\n self.__RSC_MANIFEST[page] = self.__RSC_MANIFEST[page] || manifest;\n\n // get the client and SSR module mapping to be able to use client components in the remote component\n let clientModules = manifest?.clientModules ?? {};\n const ssrModuleMapping = { ...manifest?.ssrModuleMapping };\n\n // if the remote component is used in a hosting application, we need to mutate the module map to include the zone\n clientModules = Object.fromEntries(\n Object.entries(clientModules).map(([key, value]) => {\n // append a prefix to each entry in the module map to include the zone of the remote component\n const remoteId = `[${PROJECT_ID}] ${value.id}`;\n ssrModuleMapping[remoteId] = ssrModuleMapping[value.id];\n // override the original id with the new remote id\n return [\n key,\n {\n ...value,\n id: remoteId,\n // prepend the current zone to the chunks to handle remote component chunk loading in Webpack\n // this is required to avoid loading the wrong chunk in the host application\n chunks: value.chunks.map((chunk) => `[${PROJECT_ID}] ${chunk}`),\n // async: true,\n },\n ];\n }),\n );\n\n // dynamically import the runtime specific RSC rendering functions and client component\n const [\n { renderToReadableStream: nextRenderToReadableStream },\n { renderToReadableStream: legacyRenderToReadableStream },\n { RemoteComponentSSR },\n ] = await Promise.all(\n process.env.TURBOPACK\n ? [\n tryImport<RSDWServer>(\n () => import('react-server-dom-turbopack/server'),\n ),\n tryImport<RSDWServer>(\n () => import('react-server-dom-turbopack/server.edge'),\n ),\n import('./render-client-turbopack'),\n ]\n : [\n tryImport<RSDWServer>(\n () => import('react-server-dom-webpack/server'),\n ),\n tryImport<RSDWServer>(\n () => import('react-server-dom-webpack/server.edge'),\n ),\n import('./render-client-webpack'),\n ],\n );\n const renderToReadableStream =\n nextRenderToReadableStream ?? legacyRenderToReadableStream;\n\n if (typeof renderToReadableStream !== 'function') {\n throw new Error('No valid renderToReadableStream function found');\n }\n\n // render the wrapped content of this component (children) into an RSC stream\n const stream = renderToReadableStream(children, clientModules);\n\n const data = [];\n const decoder = new TextDecoder();\n\n // convert the stream to an array for safe passing to the client\n for await (const chunk of stream as unknown as AsyncIterable<Uint8Array>) {\n data.push(decoder.decode(chunk));\n }\n\n const runtime = process.env.TURBOPACK ? 'turbopack' : 'webpack';\n const remoteComponentName = `${name}_${route?.replace(/\\//g, '_')}`;\n\n return (\n // wrap the remote component content into a div to know which part of the HTML belongs to the remote component\n <div\n data-bundle={PROJECT_ID}\n data-route={route}\n data-runtime={runtime}\n id={`${remoteComponentName}_ssr`}\n >\n <RemoteComponentSSR\n data={data}\n moduleLoading={manifest?.moduleLoading ?? {}}\n moduleMap={ssrModuleMapping}\n name={remoteComponentName}\n />\n {/* inject RSC flight data as <script> */}\n <RemoteComponentData data={data} name={remoteComponentName} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|