remote-components 0.0.1 → 0.0.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.
- package/dist/html/host.cjs +17 -6
- package/dist/html/host.cjs.map +1 -1
- package/dist/html/host.js +17 -6
- package/dist/html/host.js.map +1 -1
- package/dist/next/config.cjs.map +1 -1
- package/dist/next/config.d.ts +35 -2
- package/dist/next/config.js.map +1 -1
- package/dist/next/host/app-client.cjs +75 -22
- package/dist/next/host/app-client.cjs.map +1 -1
- package/dist/next/host/app-client.d.ts +12 -1
- package/dist/next/host/app-client.js +74 -21
- package/dist/next/host/app-client.js.map +1 -1
- package/dist/next/host/app-server.cjs +5 -1
- package/dist/next/host/app-server.cjs.map +1 -1
- package/dist/next/host/app-server.d.ts +28 -1
- package/dist/next/host/app-server.js +5 -1
- package/dist/next/host/app-server.js.map +1 -1
- package/dist/next/host/pages-server.cjs.map +1 -1
- package/dist/next/host/pages-server.d.ts +34 -0
- package/dist/next/host/pages-server.js.map +1 -1
- package/dist/next/remote/pages.cjs.map +1 -1
- package/dist/next/remote/pages.d.ts +36 -4
- package/dist/next/remote/pages.js.map +1 -1
- package/dist/next/remote/render-server.cjs.map +1 -1
- package/dist/next/remote/render-server.d.ts +26 -4
- package/dist/next/remote/render-server.js.map +1 -1
- package/dist/next/remote/server.d.ts +0 -2
- package/dist/shared/client/remote-component.cjs +20 -9
- package/dist/shared/client/remote-component.cjs.map +1 -1
- package/dist/shared/client/remote-component.d.ts +3 -1
- package/dist/shared/client/remote-component.js +20 -9
- package/dist/shared/client/remote-component.js.map +1 -1
- package/dist/shared/webpack/next-client-pages-loader.cjs +6 -2
- package/dist/shared/webpack/next-client-pages-loader.cjs.map +1 -1
- package/dist/shared/webpack/next-client-pages-loader.js +6 -2
- package/dist/shared/webpack/next-client-pages-loader.js.map +1 -1
- package/package.json +3 -3
|
@@ -24,7 +24,7 @@ __export(app_client_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(app_client_exports);
|
|
25
25
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
26
26
|
var import_react = require("react");
|
|
27
|
-
var
|
|
27
|
+
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({
|
|
@@ -37,12 +37,38 @@ function RemoteComponentClient({
|
|
|
37
37
|
scripts = [],
|
|
38
38
|
links = [],
|
|
39
39
|
remoteShared = {},
|
|
40
|
+
isolate,
|
|
40
41
|
children
|
|
41
42
|
}) {
|
|
42
|
-
const [component, setComponent] = (0,
|
|
43
|
-
(
|
|
43
|
+
const [component, setComponent] = (0, import_react.useState)(null);
|
|
44
|
+
if (component instanceof Error) {
|
|
45
|
+
throw component;
|
|
46
|
+
}
|
|
47
|
+
const shouldUseChildren = (!component || component && !nextData && typeof component.then !== "function") && // if the remote Next.js Pages Router application is in development mode
|
|
48
|
+
// we don't use the provided static HTML
|
|
49
|
+
// to mitigate layout shift when loading CSS using JavaScript on the client
|
|
50
|
+
nextData?.buildId !== "development";
|
|
51
|
+
const [shadowRoot, setShadowRoot] = (0, import_react.useState)(null);
|
|
52
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
53
|
+
if (isolate !== false && typeof document !== "undefined" && !shadowRoot) {
|
|
54
|
+
let shadowRootElement = null;
|
|
55
|
+
const element = document.getElementById(`shadowroot_${name}`);
|
|
56
|
+
shadowRootElement = element?.shadowRoot ?? null;
|
|
57
|
+
if (!shadowRootElement && element) {
|
|
58
|
+
element.attachShadow({ mode: "open" });
|
|
59
|
+
shadowRootElement = element.shadowRoot;
|
|
60
|
+
}
|
|
61
|
+
if (shadowRootElement) {
|
|
62
|
+
shadowRootElement.querySelectorAll("*:not(link)").forEach((node) => {
|
|
63
|
+
node.remove();
|
|
64
|
+
});
|
|
65
|
+
setShadowRoot(shadowRootElement);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}, [name, isolate, shadowRoot]);
|
|
69
|
+
(0, import_react.useEffect)(() => {
|
|
44
70
|
let mounted = true;
|
|
45
|
-
if (!component) {
|
|
71
|
+
if (!component && (isolate === false || shadowRoot)) {
|
|
46
72
|
(0, import_remote_component.loadRemoteComponent)({
|
|
47
73
|
name,
|
|
48
74
|
bundle,
|
|
@@ -52,7 +78,8 @@ function RemoteComponentClient({
|
|
|
52
78
|
nextData,
|
|
53
79
|
scripts,
|
|
54
80
|
shared: import_host.shared,
|
|
55
|
-
remoteShared
|
|
81
|
+
remoteShared,
|
|
82
|
+
container: shadowRoot
|
|
56
83
|
}).then((result) => {
|
|
57
84
|
if (mounted) {
|
|
58
85
|
if (result.error) {
|
|
@@ -81,15 +108,39 @@ function RemoteComponentClient({
|
|
|
81
108
|
nextData,
|
|
82
109
|
remoteShared,
|
|
83
110
|
children,
|
|
84
|
-
links
|
|
111
|
+
links,
|
|
112
|
+
isolate,
|
|
113
|
+
shadowRoot
|
|
85
114
|
]);
|
|
86
|
-
|
|
87
|
-
|
|
115
|
+
let componentToRender = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: shouldUseChildren ? children : component });
|
|
116
|
+
let linksToRender = links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
117
|
+
"link",
|
|
118
|
+
{
|
|
119
|
+
as: link.as,
|
|
120
|
+
href: link.href,
|
|
121
|
+
rel: link.rel
|
|
122
|
+
},
|
|
123
|
+
`${link.href}_${link.rel}`
|
|
124
|
+
));
|
|
125
|
+
if (isolate !== false) {
|
|
126
|
+
componentToRender = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { id: `shadowroot_${name}`, children: [
|
|
127
|
+
typeof document === "undefined" ? (
|
|
128
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("template", { shadowrootmode: "open", children: [
|
|
130
|
+
linksToRender,
|
|
131
|
+
componentToRender
|
|
132
|
+
] })
|
|
133
|
+
) : null,
|
|
134
|
+
shadowRoot ? (0, import_react_dom.createPortal)(
|
|
135
|
+
shadowRoot.querySelectorAll("link").length !== links.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
136
|
+
linksToRender,
|
|
137
|
+
componentToRender
|
|
138
|
+
] }) : componentToRender,
|
|
139
|
+
shadowRoot
|
|
140
|
+
) : null
|
|
141
|
+
] });
|
|
142
|
+
linksToRender = null;
|
|
88
143
|
}
|
|
89
|
-
const shouldUseChildren = (!component || component && !nextData && typeof component.then !== "function") && // if the remote Next.js Pages Router application is in development mode
|
|
90
|
-
// we don't use the provided static HTML
|
|
91
|
-
// to mitigate layout shift when loading CSS using JavaScript on the client
|
|
92
|
-
nextData?.buildId !== "development";
|
|
93
144
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
94
145
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { "data-remote-component": true, type: "application/json", children: JSON.stringify({
|
|
95
146
|
name,
|
|
@@ -97,7 +148,17 @@ function RemoteComponentClient({
|
|
|
97
148
|
route,
|
|
98
149
|
runtime
|
|
99
150
|
}) }),
|
|
100
|
-
|
|
151
|
+
linksToRender,
|
|
152
|
+
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
|
+
)),
|
|
101
162
|
data.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("script", { id: `${name}_rsc`, children: data.join("\n") }) : null,
|
|
102
163
|
nextData ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
103
164
|
"script",
|
|
@@ -106,15 +167,7 @@ function RemoteComponentClient({
|
|
|
106
167
|
type: "application/json",
|
|
107
168
|
children: JSON.stringify(nextData)
|
|
108
169
|
}
|
|
109
|
-
) : null
|
|
110
|
-
links.map((link) => /* @__PURE__ */ (0, import_react.createElement)(
|
|
111
|
-
"link",
|
|
112
|
-
{
|
|
113
|
-
...link,
|
|
114
|
-
key: `${link.href}_${link.rel}`,
|
|
115
|
-
precedence: bundle
|
|
116
|
-
}
|
|
117
|
-
))
|
|
170
|
+
) : null
|
|
118
171
|
] });
|
|
119
172
|
}
|
|
120
173
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/app-client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\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/**\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 children,\n}: RemoteComponentProps) {\n const [component, setComponent] = useState<React.ReactNode | Error>(null);\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) {\n loadRemoteComponent({\n name,\n bundle,\n route,\n runtime,\n data,\n nextData,\n scripts,\n shared,\n remoteShared,\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 ]);\n\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 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 {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 {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":[]}
|
|
@@ -19,15 +19,26 @@ interface RemoteComponentProps {
|
|
|
19
19
|
}[];
|
|
20
20
|
links?: Record<string, string | boolean>[];
|
|
21
21
|
remoteShared?: Record<string, string>;
|
|
22
|
+
isolate?: boolean;
|
|
22
23
|
children: react.ReactNode;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
declare module 'react/jsx-runtime' {
|
|
27
|
+
namespace JSX {
|
|
28
|
+
interface IntrinsicElements {
|
|
29
|
+
template: {
|
|
30
|
+
shadowrootmode?: 'open' | 'closed';
|
|
31
|
+
children: React.ReactNode;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
25
36
|
/**
|
|
26
37
|
* RemoteComponentClient - Main component for rendering remote components
|
|
27
38
|
*
|
|
28
39
|
* This component handles the loading and rendering of remote microfrontends.
|
|
29
40
|
* It supports both RSC (React Server Components) and Next.js Pages Router based components.
|
|
30
41
|
*/
|
|
31
|
-
declare function RemoteComponentClient({ name, bundle, route, runtime, data, nextData, scripts, links, remoteShared, children, }: RemoteComponentProps): react_jsx_runtime.JSX.Element;
|
|
42
|
+
declare function RemoteComponentClient({ name, bundle, route, runtime, data, nextData, scripts, links, remoteShared, isolate, children, }: RemoteComponentProps): react_jsx_runtime.JSX.Element;
|
|
32
43
|
|
|
33
44
|
export { RemoteComponentClient };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { useEffect, useState, useLayoutEffect } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
5
|
import { shared } from "@remote-component/shared/host";
|
|
6
6
|
import {
|
|
7
7
|
loadRemoteComponent,
|
|
@@ -18,12 +18,38 @@ function RemoteComponentClient({
|
|
|
18
18
|
scripts = [],
|
|
19
19
|
links = [],
|
|
20
20
|
remoteShared = {},
|
|
21
|
+
isolate,
|
|
21
22
|
children
|
|
22
23
|
}) {
|
|
23
24
|
const [component, setComponent] = useState(null);
|
|
25
|
+
if (component instanceof Error) {
|
|
26
|
+
throw component;
|
|
27
|
+
}
|
|
28
|
+
const shouldUseChildren = (!component || component && !nextData && typeof component.then !== "function") && // if the remote Next.js Pages Router application is in development mode
|
|
29
|
+
// we don't use the provided static HTML
|
|
30
|
+
// to mitigate layout shift when loading CSS using JavaScript on the client
|
|
31
|
+
nextData?.buildId !== "development";
|
|
32
|
+
const [shadowRoot, setShadowRoot] = useState(null);
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
if (isolate !== false && typeof document !== "undefined" && !shadowRoot) {
|
|
35
|
+
let shadowRootElement = null;
|
|
36
|
+
const element = document.getElementById(`shadowroot_${name}`);
|
|
37
|
+
shadowRootElement = element?.shadowRoot ?? null;
|
|
38
|
+
if (!shadowRootElement && element) {
|
|
39
|
+
element.attachShadow({ mode: "open" });
|
|
40
|
+
shadowRootElement = element.shadowRoot;
|
|
41
|
+
}
|
|
42
|
+
if (shadowRootElement) {
|
|
43
|
+
shadowRootElement.querySelectorAll("*:not(link)").forEach((node) => {
|
|
44
|
+
node.remove();
|
|
45
|
+
});
|
|
46
|
+
setShadowRoot(shadowRootElement);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, [name, isolate, shadowRoot]);
|
|
24
50
|
useEffect(() => {
|
|
25
51
|
let mounted = true;
|
|
26
|
-
if (!component) {
|
|
52
|
+
if (!component && (isolate === false || shadowRoot)) {
|
|
27
53
|
loadRemoteComponent({
|
|
28
54
|
name,
|
|
29
55
|
bundle,
|
|
@@ -33,7 +59,8 @@ function RemoteComponentClient({
|
|
|
33
59
|
nextData,
|
|
34
60
|
scripts,
|
|
35
61
|
shared,
|
|
36
|
-
remoteShared
|
|
62
|
+
remoteShared,
|
|
63
|
+
container: shadowRoot
|
|
37
64
|
}).then((result) => {
|
|
38
65
|
if (mounted) {
|
|
39
66
|
if (result.error) {
|
|
@@ -62,15 +89,39 @@ function RemoteComponentClient({
|
|
|
62
89
|
nextData,
|
|
63
90
|
remoteShared,
|
|
64
91
|
children,
|
|
65
|
-
links
|
|
92
|
+
links,
|
|
93
|
+
isolate,
|
|
94
|
+
shadowRoot
|
|
66
95
|
]);
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
let componentToRender = /* @__PURE__ */ jsx(Fragment, { children: shouldUseChildren ? children : component });
|
|
97
|
+
let linksToRender = links.map((link) => /* @__PURE__ */ jsx(
|
|
98
|
+
"link",
|
|
99
|
+
{
|
|
100
|
+
as: link.as,
|
|
101
|
+
href: link.href,
|
|
102
|
+
rel: link.rel
|
|
103
|
+
},
|
|
104
|
+
`${link.href}_${link.rel}`
|
|
105
|
+
));
|
|
106
|
+
if (isolate !== false) {
|
|
107
|
+
componentToRender = /* @__PURE__ */ jsxs("div", { id: `shadowroot_${name}`, children: [
|
|
108
|
+
typeof document === "undefined" ? (
|
|
109
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
110
|
+
/* @__PURE__ */ jsxs("template", { shadowrootmode: "open", children: [
|
|
111
|
+
linksToRender,
|
|
112
|
+
componentToRender
|
|
113
|
+
] })
|
|
114
|
+
) : null,
|
|
115
|
+
shadowRoot ? createPortal(
|
|
116
|
+
shadowRoot.querySelectorAll("link").length !== links.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
117
|
+
linksToRender,
|
|
118
|
+
componentToRender
|
|
119
|
+
] }) : componentToRender,
|
|
120
|
+
shadowRoot
|
|
121
|
+
) : null
|
|
122
|
+
] });
|
|
123
|
+
linksToRender = null;
|
|
69
124
|
}
|
|
70
|
-
const shouldUseChildren = (!component || component && !nextData && typeof component.then !== "function") && // if the remote Next.js Pages Router application is in development mode
|
|
71
|
-
// we don't use the provided static HTML
|
|
72
|
-
// to mitigate layout shift when loading CSS using JavaScript on the client
|
|
73
|
-
nextData?.buildId !== "development";
|
|
74
125
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
75
126
|
/* @__PURE__ */ jsx("script", { "data-remote-component": true, type: "application/json", children: JSON.stringify({
|
|
76
127
|
name,
|
|
@@ -78,7 +129,17 @@ function RemoteComponentClient({
|
|
|
78
129
|
route,
|
|
79
130
|
runtime
|
|
80
131
|
}) }),
|
|
81
|
-
|
|
132
|
+
linksToRender,
|
|
133
|
+
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
|
+
)),
|
|
82
143
|
data.length > 0 ? /* @__PURE__ */ jsx("script", { id: `${name}_rsc`, children: data.join("\n") }) : null,
|
|
83
144
|
nextData ? /* @__PURE__ */ jsx(
|
|
84
145
|
"script",
|
|
@@ -87,15 +148,7 @@ function RemoteComponentClient({
|
|
|
87
148
|
type: "application/json",
|
|
88
149
|
children: JSON.stringify(nextData)
|
|
89
150
|
}
|
|
90
|
-
) : null
|
|
91
|
-
links.map((link) => /* @__PURE__ */ createElement(
|
|
92
|
-
"link",
|
|
93
|
-
{
|
|
94
|
-
...link,
|
|
95
|
-
key: `${link.href}_${link.rel}`,
|
|
96
|
-
precedence: bundle
|
|
97
|
-
}
|
|
98
|
-
))
|
|
151
|
+
) : null
|
|
99
152
|
] });
|
|
100
153
|
}
|
|
101
154
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/app-client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\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/**\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 children,\n}: RemoteComponentProps) {\n const [component, setComponent] = useState<React.ReactNode | Error>(null);\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) {\n loadRemoteComponent({\n name,\n bundle,\n route,\n runtime,\n data,\n nextData,\n scripts,\n shared,\n remoteShared,\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 ]);\n\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 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 {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 {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":[]}
|
|
@@ -26,7 +26,10 @@ var import_headers = require("next/headers");
|
|
|
26
26
|
var import_fetch_remote_component = require("../../shared/ssr/fetch-remote-component");
|
|
27
27
|
var import_app_client = require("./app-client");
|
|
28
28
|
const CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;
|
|
29
|
-
async function RemoteComponent({
|
|
29
|
+
async function RemoteComponent({
|
|
30
|
+
src,
|
|
31
|
+
isolate
|
|
32
|
+
}) {
|
|
30
33
|
const headerList = await (0, import_headers.headers)();
|
|
31
34
|
const {
|
|
32
35
|
metadata,
|
|
@@ -43,6 +46,7 @@ async function RemoteComponent({ src }) {
|
|
|
43
46
|
{
|
|
44
47
|
bundle: metadata.bundle || (CURRENT_ZONE ?? name),
|
|
45
48
|
data: hydrationData,
|
|
49
|
+
isolate,
|
|
46
50
|
links,
|
|
47
51
|
name,
|
|
48
52
|
nextData,
|
|
@@ -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\nexport async function RemoteComponent({
|
|
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;AAyDI;AAzDJ,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,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,MAEC;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* RemoteComponent is a Next.js component that fetches and renders a remote component.
|
|
5
|
+
* It supports SSR and can isolate the remote component in a shadow DOM.
|
|
6
|
+
*
|
|
7
|
+
* @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.
|
|
8
|
+
* @param isolate - Whether to isolate the remote component using a Shadow DOM wrapper. Defaults to `true`. Use `false` explicitly to disable isolation.
|
|
9
|
+
* @returns A React component that renders the remote component.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
*
|
|
13
|
+
* Use the `<RemoteComponent>` in your Next.js App Router application to consume a remote component from a remote application:
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { RemoteComponent } from 'remote-components/next/host';
|
|
17
|
+
*
|
|
18
|
+
* export default function MyPage() {
|
|
19
|
+
* return (
|
|
20
|
+
* <>
|
|
21
|
+
* <h1>Welcome to My Page</h1>
|
|
22
|
+
* <p>This page consumes a remote component from another application.</p>
|
|
23
|
+
* <RemoteComponent src="/nextjs-app-remote/components/header" />
|
|
24
|
+
* </>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function RemoteComponent({ src, isolate, }: {
|
|
4
30
|
src: string | URL;
|
|
31
|
+
isolate?: boolean;
|
|
5
32
|
}): Promise<react_jsx_runtime.JSX.Element>;
|
|
6
33
|
|
|
7
34
|
export { RemoteComponent };
|
|
@@ -3,7 +3,10 @@ import { headers } from "next/headers";
|
|
|
3
3
|
import { fetchRemoteComponent } from "../../shared/ssr/fetch-remote-component";
|
|
4
4
|
import { RemoteComponentClient } from "./app-client";
|
|
5
5
|
const CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;
|
|
6
|
-
async function RemoteComponent({
|
|
6
|
+
async function RemoteComponent({
|
|
7
|
+
src,
|
|
8
|
+
isolate
|
|
9
|
+
}) {
|
|
7
10
|
const headerList = await headers();
|
|
8
11
|
const {
|
|
9
12
|
metadata,
|
|
@@ -20,6 +23,7 @@ async function RemoteComponent({ src }) {
|
|
|
20
23
|
{
|
|
21
24
|
bundle: metadata.bundle || (CURRENT_ZONE ?? name),
|
|
22
25
|
data: hydrationData,
|
|
26
|
+
isolate,
|
|
23
27
|
links,
|
|
24
28
|
name,
|
|
25
29
|
nextData,
|
|
@@ -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\nexport async function RemoteComponent({
|
|
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":"AAyDI;AAzDJ,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,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,MAEC;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/pages-server.tsx"],"sourcesContent":["import type { IncomingHttpHeaders } from 'node:http';\nimport { useEffect } from 'react';\nimport { shared } from '@remote-component/shared/host';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\n\n// patch react/jsx-runtime to support the remote-component custom element\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'remote-component': {\n src?: string;\n children: React.ReactNode;\n };\n }\n }\n}\n\n// internal symbols to access global store\nconst REMOTE_COMPONENT_STORE = Symbol('REMOTE_COMPONENT_STORE');\nconst REMOTE_COMPONENT_KEY = '__REMOTE_COMPONENT_KEY__';\n\n// temporary global store for remote component HTML\n// the store is used to save the HTML of remote components for SSR without sending the content to the client\nconst self = globalThis as typeof globalThis & {\n [REMOTE_COMPONENT_STORE]?: Map<string, string>;\n};\n\nfunction getKey({\n bundle,\n route,\n name,\n}: {\n bundle?: string;\n route?: string;\n name?: string;\n}): string {\n return `${bundle ?? '__next'}:${route ?? '/'}:${name ?? '__vercel_remote_component'}__${crypto.randomUUID()}`;\n}\n\nfunction setComponent(key: string, html: string): void {\n if (!self[REMOTE_COMPONENT_STORE]) {\n self[REMOTE_COMPONENT_STORE] = new Map();\n }\n self[REMOTE_COMPONENT_STORE].set(key, html);\n}\n\nfunction getComponent(key: string): string | undefined {\n const component = self[REMOTE_COMPONENT_STORE]?.get(key);\n // remove the component from the store after retrieving it to prevent memory leaks\n // storing the HTML in the global store is only needed for SSR and it's temporary only used for a single render\n self[REMOTE_COMPONENT_STORE]?.delete(key);\n return component;\n}\n\nexport interface RemoteComponentProps {\n src: string;\n bundle?: string;\n route?: string;\n name?: string;\n [REMOTE_COMPONENT_KEY]?: string;\n children?: React.ReactNode;\n}\n\nexport function RemoteComponent(props: RemoteComponentProps): JSX.Element {\n const remoteComponent =\n typeof document !== 'undefined'\n ? null\n : // retrieve the HTML from the global store\n getComponent(\n props[REMOTE_COMPONENT_KEY] ?? '__vercel_remote_component',\n );\n\n useEffect(() => {\n const clientSelf = globalThis as typeof globalThis & {\n __remote_component_shared__?: Record<string, () => Promise<unknown>>;\n };\n // eslint-disable-next-line camelcase\n clientSelf.__remote_component_shared__ = shared;\n import('remote-components/html');\n }, []);\n\n if (!props[REMOTE_COMPONENT_KEY]) {\n return (\n <remote-component src={props.src}>{props.children}</remote-component>\n );\n }\n\n return (\n <remote-component src={props.src}>\n <div\n dangerouslySetInnerHTML={{ __html: remoteComponent ?? '' }}\n id=\"__REMOTE_COMPONENT__\"\n suppressHydrationWarning\n />\n </remote-component>\n );\n}\n\nexport async function getRemoteComponentProps(\n src: string,\n headers: IncomingHttpHeaders,\n): Promise<RemoteComponentProps> {\n if (typeof document !== 'undefined') {\n throw new Error(\n 'getRemoteComponentProps can only be used on the server side.',\n );\n }\n\n const {\n metadata: { bundle, route, runtime },\n name,\n html,\n nextData,\n } = await fetchRemoteComponent(\n src,\n headers instanceof Headers\n ? headers\n : // convert IncomingHttpHeaders to web standard Headers\n Object.entries(headers).reduce((acc, [key, value]) => {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => acc.append(key, v));\n } else {\n acc.append(key, value);\n }\n }\n return acc;\n }, new Headers()),\n );\n\n const props = {\n src,\n bundle,\n name,\n route,\n runtime,\n };\n\n // do not render the HTML in development mode when remote is using Next.js Pages Router\n // this behavior is emulating the Next.js Pages Router FOUC as the styles are only applied on the client when running in development mode\n if (nextData?.buildId === 'development') {\n return props;\n }\n\n const key = getKey(props);\n // store the HTML in a global store\n setComponent(key, html);\n\n return {\n ...props,\n // add remote component key to the props\n [REMOTE_COMPONENT_KEY]: key,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../../src/next/host/pages-server.tsx"],"sourcesContent":["import type { IncomingHttpHeaders } from 'node:http';\nimport { useEffect } from 'react';\nimport { shared } from '@remote-component/shared/host';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\n\n// patch react/jsx-runtime to support the remote-component custom element\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'remote-component': {\n src?: string;\n children: React.ReactNode;\n };\n }\n }\n}\n\n// internal symbols to access global store\nconst REMOTE_COMPONENT_STORE = Symbol('REMOTE_COMPONENT_STORE');\nconst REMOTE_COMPONENT_KEY = '__REMOTE_COMPONENT_KEY__';\n\n// temporary global store for remote component HTML\n// the store is used to save the HTML of remote components for SSR without sending the content to the client\nconst self = globalThis as typeof globalThis & {\n [REMOTE_COMPONENT_STORE]?: Map<string, string>;\n};\n\nfunction getKey({\n bundle,\n route,\n name,\n}: {\n bundle?: string;\n route?: string;\n name?: string;\n}): string {\n return `${bundle ?? '__next'}:${route ?? '/'}:${name ?? '__vercel_remote_component'}__${crypto.randomUUID()}`;\n}\n\nfunction setComponent(key: string, html: string): void {\n if (!self[REMOTE_COMPONENT_STORE]) {\n self[REMOTE_COMPONENT_STORE] = new Map();\n }\n self[REMOTE_COMPONENT_STORE].set(key, html);\n}\n\nfunction getComponent(key: string): string | undefined {\n const component = self[REMOTE_COMPONENT_STORE]?.get(key);\n // remove the component from the store after retrieving it to prevent memory leaks\n // storing the HTML in the global store is only needed for SSR and it's temporary only used for a single render\n self[REMOTE_COMPONENT_STORE]?.delete(key);\n return component;\n}\n\nexport interface RemoteComponentProps {\n src: string;\n bundle?: string;\n route?: string;\n name?: string;\n [REMOTE_COMPONENT_KEY]?: string;\n children?: React.ReactNode;\n}\n\n/**\n * This component handles the rendering of remote microfrontends.\n *\n * @param props - The properties for the remote component.\n * @returns A React component that renders the remote component.\n */\nexport function RemoteComponent(props: RemoteComponentProps): JSX.Element {\n const remoteComponent =\n typeof document !== 'undefined'\n ? null\n : // retrieve the HTML from the global store\n getComponent(\n props[REMOTE_COMPONENT_KEY] ?? '__vercel_remote_component',\n );\n\n useEffect(() => {\n const clientSelf = globalThis as typeof globalThis & {\n __remote_component_shared__?: Record<string, () => Promise<unknown>>;\n };\n // eslint-disable-next-line camelcase\n clientSelf.__remote_component_shared__ = shared;\n import('remote-components/html');\n }, []);\n\n if (!props[REMOTE_COMPONENT_KEY]) {\n return (\n <remote-component src={props.src}>{props.children}</remote-component>\n );\n }\n\n return (\n <remote-component src={props.src}>\n <div\n dangerouslySetInnerHTML={{ __html: remoteComponent ?? '' }}\n id=\"__REMOTE_COMPONENT__\"\n suppressHydrationWarning\n />\n </remote-component>\n );\n}\n\n/**\n * Fetches the remote component properties from the server. You need to pass these properties to the `<RemoteComponent>` component to render the fetched remote component.\n *\n * @param src - The source URL of the remote component. When using the Vercel Microfrontends solution, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.\n * @param headers - The HTTP headers used for supporting the Vercel Microfrontends proxy.\n * @returns The properties of the remote component.\n *\n * @example\n *\n * ```tsx\n * import { getRemoteComponentProps } from 'remote-components/next/host/pages';\n * import type { GetServerSideProps } from 'next';\n *\n * export const getServerSideProps: GetServerSideProps<PageProps> = async function getServerSideProps({ req }) {\n * const myRemoteComponent = await getRemoteComponentProps(\n * '/nextjs-app-remote/components/header',\n * req.headers,\n * );\n * return {\n * props: {\n * remoteComponents: {\n * myRemoteComponent,\n * },\n * },\n * };\n * }\n * ```\n */\nexport async function getRemoteComponentProps(\n src: string,\n headers: IncomingHttpHeaders,\n): Promise<RemoteComponentProps> {\n if (typeof document !== 'undefined') {\n throw new Error(\n 'getRemoteComponentProps can only be used on the server side.',\n );\n }\n\n const {\n metadata: { bundle, route, runtime },\n name,\n html,\n nextData,\n } = await fetchRemoteComponent(\n src,\n headers instanceof Headers\n ? headers\n : // convert IncomingHttpHeaders to web standard Headers\n Object.entries(headers).reduce((acc, [key, value]) => {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => acc.append(key, v));\n } else {\n acc.append(key, value);\n }\n }\n return acc;\n }, new Headers()),\n );\n\n const props = {\n src,\n bundle,\n name,\n route,\n runtime,\n };\n\n // do not render the HTML in development mode when remote is using Next.js Pages Router\n // this behavior is emulating the Next.js Pages Router FOUC as the styles are only applied on the client when running in development mode\n if (nextData?.buildId === 'development') {\n return props;\n }\n\n const key = getKey(props);\n // store the HTML in a global store\n setComponent(key, html);\n\n return {\n ...props,\n // add remote component key to the props\n [REMOTE_COMPONENT_KEY]: key,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0FM;AAzFN,mBAA0B;AAC1B,kBAAuB;AACvB,oCAAqC;AAgBrC,MAAM,yBAAyB,OAAO,wBAAwB;AAC9D,MAAM,uBAAuB;AAI7B,MAAM,OAAO;AAIb,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,SAAO,GAAG,UAAU,YAAY,SAAS,OAAO,QAAQ,gCAAgC,OAAO,WAAW;AAC5G;AAEA,SAAS,aAAa,KAAa,MAAoB;AACrD,MAAI,CAAC,KAAK,sBAAsB,GAAG;AACjC,SAAK,sBAAsB,IAAI,oBAAI,IAAI;AAAA,EACzC;AACA,OAAK,sBAAsB,EAAE,IAAI,KAAK,IAAI;AAC5C;AAEA,SAAS,aAAa,KAAiC;AACrD,QAAM,YAAY,KAAK,sBAAsB,GAAG,IAAI,GAAG;AAGvD,OAAK,sBAAsB,GAAG,OAAO,GAAG;AACxC,SAAO;AACT;AAiBO,SAAS,gBAAgB,OAA0C;AACxE,QAAM,kBACJ,OAAO,aAAa,cAChB;AAAA;AAAA,IAEA;AAAA,MACE,MAAM,oBAAoB,KAAK;AAAA,IACjC;AAAA;AAEN,8BAAU,MAAM;AACd,UAAM,aAAa;AAInB,eAAW,8BAA8B;AACzC,WAAO,wBAAwB;AAAA,EACjC,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,MAAM,oBAAoB,GAAG;AAChC,WACE,4CAAC,sBAAiB,KAAK,MAAM,KAAM,gBAAM,UAAS;AAAA,EAEtD;AAEA,SACE,4CAAC,sBAAiB,KAAK,MAAM,KAC3B;AAAA,IAAC;AAAA;AAAA,MACC,yBAAyB,EAAE,QAAQ,mBAAmB,GAAG;AAAA,MACzD,IAAG;AAAA,MACH,0BAAwB;AAAA;AAAA,EAC1B,GACF;AAEJ;AA8BA,eAAsB,wBACpB,KACA,SAC+B;AAC/B,MAAI,OAAO,aAAa,aAAa;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM;AAAA,IACJ,UAAU,EAAE,QAAQ,OAAO,QAAQ;AAAA,IACnC;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,UAAM;AAAA,IACR;AAAA,IACA,mBAAmB,UACf;AAAA;AAAA,MAEA,OAAO,QAAQ,OAAO,EAAE,OAAO,CAAC,KAAK,CAACA,MAAK,KAAK,MAAM;AACpD,YAAI,OAAO;AACT,cAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,kBAAM,QAAQ,CAAC,MAAM,IAAI,OAAOA,MAAK,CAAC,CAAC;AAAA,UACzC,OAAO;AACL,gBAAI,OAAOA,MAAK,KAAK;AAAA,UACvB;AAAA,QACF;AACA,eAAO;AAAA,MACT,GAAG,IAAI,QAAQ,CAAC;AAAA;AAAA,EACtB;AAEA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAIA,MAAI,UAAU,YAAY,eAAe;AACvC,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,OAAO,KAAK;AAExB,eAAa,KAAK,IAAI;AAEtB,SAAO;AAAA,IACL,GAAG;AAAA;AAAA,IAEH,CAAC,oBAAoB,GAAG;AAAA,EAC1B;AACF;","names":["key"]}
|
|
@@ -19,7 +19,41 @@ interface RemoteComponentProps {
|
|
|
19
19
|
[REMOTE_COMPONENT_KEY]?: string;
|
|
20
20
|
children?: React.ReactNode;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* This component handles the rendering of remote microfrontends.
|
|
24
|
+
*
|
|
25
|
+
* @param props - The properties for the remote component.
|
|
26
|
+
* @returns A React component that renders the remote component.
|
|
27
|
+
*/
|
|
22
28
|
declare function RemoteComponent(props: RemoteComponentProps): JSX.Element;
|
|
29
|
+
/**
|
|
30
|
+
* Fetches the remote component properties from the server. You need to pass these properties to the `<RemoteComponent>` component to render the fetched remote component.
|
|
31
|
+
*
|
|
32
|
+
* @param src - The source URL of the remote component. When using the Vercel Microfrontends solution, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.
|
|
33
|
+
* @param headers - The HTTP headers used for supporting the Vercel Microfrontends proxy.
|
|
34
|
+
* @returns The properties of the remote component.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
*
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import { getRemoteComponentProps } from 'remote-components/next/host/pages';
|
|
40
|
+
* import type { GetServerSideProps } from 'next';
|
|
41
|
+
*
|
|
42
|
+
* export const getServerSideProps: GetServerSideProps<PageProps> = async function getServerSideProps({ req }) {
|
|
43
|
+
* const myRemoteComponent = await getRemoteComponentProps(
|
|
44
|
+
* '/nextjs-app-remote/components/header',
|
|
45
|
+
* req.headers,
|
|
46
|
+
* );
|
|
47
|
+
* return {
|
|
48
|
+
* props: {
|
|
49
|
+
* remoteComponents: {
|
|
50
|
+
* myRemoteComponent,
|
|
51
|
+
* },
|
|
52
|
+
* },
|
|
53
|
+
* };
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
23
57
|
declare function getRemoteComponentProps(src: string, headers: IncomingHttpHeaders): Promise<RemoteComponentProps>;
|
|
24
58
|
|
|
25
59
|
export { RemoteComponent, RemoteComponentProps, getRemoteComponentProps };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/host/pages-server.tsx"],"sourcesContent":["import type { IncomingHttpHeaders } from 'node:http';\nimport { useEffect } from 'react';\nimport { shared } from '@remote-component/shared/host';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\n\n// patch react/jsx-runtime to support the remote-component custom element\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'remote-component': {\n src?: string;\n children: React.ReactNode;\n };\n }\n }\n}\n\n// internal symbols to access global store\nconst REMOTE_COMPONENT_STORE = Symbol('REMOTE_COMPONENT_STORE');\nconst REMOTE_COMPONENT_KEY = '__REMOTE_COMPONENT_KEY__';\n\n// temporary global store for remote component HTML\n// the store is used to save the HTML of remote components for SSR without sending the content to the client\nconst self = globalThis as typeof globalThis & {\n [REMOTE_COMPONENT_STORE]?: Map<string, string>;\n};\n\nfunction getKey({\n bundle,\n route,\n name,\n}: {\n bundle?: string;\n route?: string;\n name?: string;\n}): string {\n return `${bundle ?? '__next'}:${route ?? '/'}:${name ?? '__vercel_remote_component'}__${crypto.randomUUID()}`;\n}\n\nfunction setComponent(key: string, html: string): void {\n if (!self[REMOTE_COMPONENT_STORE]) {\n self[REMOTE_COMPONENT_STORE] = new Map();\n }\n self[REMOTE_COMPONENT_STORE].set(key, html);\n}\n\nfunction getComponent(key: string): string | undefined {\n const component = self[REMOTE_COMPONENT_STORE]?.get(key);\n // remove the component from the store after retrieving it to prevent memory leaks\n // storing the HTML in the global store is only needed for SSR and it's temporary only used for a single render\n self[REMOTE_COMPONENT_STORE]?.delete(key);\n return component;\n}\n\nexport interface RemoteComponentProps {\n src: string;\n bundle?: string;\n route?: string;\n name?: string;\n [REMOTE_COMPONENT_KEY]?: string;\n children?: React.ReactNode;\n}\n\nexport function RemoteComponent(props: RemoteComponentProps): JSX.Element {\n const remoteComponent =\n typeof document !== 'undefined'\n ? null\n : // retrieve the HTML from the global store\n getComponent(\n props[REMOTE_COMPONENT_KEY] ?? '__vercel_remote_component',\n );\n\n useEffect(() => {\n const clientSelf = globalThis as typeof globalThis & {\n __remote_component_shared__?: Record<string, () => Promise<unknown>>;\n };\n // eslint-disable-next-line camelcase\n clientSelf.__remote_component_shared__ = shared;\n import('remote-components/html');\n }, []);\n\n if (!props[REMOTE_COMPONENT_KEY]) {\n return (\n <remote-component src={props.src}>{props.children}</remote-component>\n );\n }\n\n return (\n <remote-component src={props.src}>\n <div\n dangerouslySetInnerHTML={{ __html: remoteComponent ?? '' }}\n id=\"__REMOTE_COMPONENT__\"\n suppressHydrationWarning\n />\n </remote-component>\n );\n}\n\nexport async function getRemoteComponentProps(\n src: string,\n headers: IncomingHttpHeaders,\n): Promise<RemoteComponentProps> {\n if (typeof document !== 'undefined') {\n throw new Error(\n 'getRemoteComponentProps can only be used on the server side.',\n );\n }\n\n const {\n metadata: { bundle, route, runtime },\n name,\n html,\n nextData,\n } = await fetchRemoteComponent(\n src,\n headers instanceof Headers\n ? headers\n : // convert IncomingHttpHeaders to web standard Headers\n Object.entries(headers).reduce((acc, [key, value]) => {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => acc.append(key, v));\n } else {\n acc.append(key, value);\n }\n }\n return acc;\n }, new Headers()),\n );\n\n const props = {\n src,\n bundle,\n name,\n route,\n runtime,\n };\n\n // do not render the HTML in development mode when remote is using Next.js Pages Router\n // this behavior is emulating the Next.js Pages Router FOUC as the styles are only applied on the client when running in development mode\n if (nextData?.buildId === 'development') {\n return props;\n }\n\n const key = getKey(props);\n // store the HTML in a global store\n setComponent(key, html);\n\n return {\n ...props,\n // add remote component key to the props\n [REMOTE_COMPONENT_KEY]: key,\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/next/host/pages-server.tsx"],"sourcesContent":["import type { IncomingHttpHeaders } from 'node:http';\nimport { useEffect } from 'react';\nimport { shared } from '@remote-component/shared/host';\nimport { fetchRemoteComponent } from '../../shared/ssr/fetch-remote-component';\n\n// patch react/jsx-runtime to support the remote-component custom element\ndeclare module 'react/jsx-runtime' {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace JSX {\n interface IntrinsicElements {\n 'remote-component': {\n src?: string;\n children: React.ReactNode;\n };\n }\n }\n}\n\n// internal symbols to access global store\nconst REMOTE_COMPONENT_STORE = Symbol('REMOTE_COMPONENT_STORE');\nconst REMOTE_COMPONENT_KEY = '__REMOTE_COMPONENT_KEY__';\n\n// temporary global store for remote component HTML\n// the store is used to save the HTML of remote components for SSR without sending the content to the client\nconst self = globalThis as typeof globalThis & {\n [REMOTE_COMPONENT_STORE]?: Map<string, string>;\n};\n\nfunction getKey({\n bundle,\n route,\n name,\n}: {\n bundle?: string;\n route?: string;\n name?: string;\n}): string {\n return `${bundle ?? '__next'}:${route ?? '/'}:${name ?? '__vercel_remote_component'}__${crypto.randomUUID()}`;\n}\n\nfunction setComponent(key: string, html: string): void {\n if (!self[REMOTE_COMPONENT_STORE]) {\n self[REMOTE_COMPONENT_STORE] = new Map();\n }\n self[REMOTE_COMPONENT_STORE].set(key, html);\n}\n\nfunction getComponent(key: string): string | undefined {\n const component = self[REMOTE_COMPONENT_STORE]?.get(key);\n // remove the component from the store after retrieving it to prevent memory leaks\n // storing the HTML in the global store is only needed for SSR and it's temporary only used for a single render\n self[REMOTE_COMPONENT_STORE]?.delete(key);\n return component;\n}\n\nexport interface RemoteComponentProps {\n src: string;\n bundle?: string;\n route?: string;\n name?: string;\n [REMOTE_COMPONENT_KEY]?: string;\n children?: React.ReactNode;\n}\n\n/**\n * This component handles the rendering of remote microfrontends.\n *\n * @param props - The properties for the remote component.\n * @returns A React component that renders the remote component.\n */\nexport function RemoteComponent(props: RemoteComponentProps): JSX.Element {\n const remoteComponent =\n typeof document !== 'undefined'\n ? null\n : // retrieve the HTML from the global store\n getComponent(\n props[REMOTE_COMPONENT_KEY] ?? '__vercel_remote_component',\n );\n\n useEffect(() => {\n const clientSelf = globalThis as typeof globalThis & {\n __remote_component_shared__?: Record<string, () => Promise<unknown>>;\n };\n // eslint-disable-next-line camelcase\n clientSelf.__remote_component_shared__ = shared;\n import('remote-components/html');\n }, []);\n\n if (!props[REMOTE_COMPONENT_KEY]) {\n return (\n <remote-component src={props.src}>{props.children}</remote-component>\n );\n }\n\n return (\n <remote-component src={props.src}>\n <div\n dangerouslySetInnerHTML={{ __html: remoteComponent ?? '' }}\n id=\"__REMOTE_COMPONENT__\"\n suppressHydrationWarning\n />\n </remote-component>\n );\n}\n\n/**\n * Fetches the remote component properties from the server. You need to pass these properties to the `<RemoteComponent>` component to render the fetched remote component.\n *\n * @param src - The source URL of the remote component. When using the Vercel Microfrontends solution, you can use relative paths, e.g. `/nextjs-app-remote/components/header`. Absolute URLs are also supported.\n * @param headers - The HTTP headers used for supporting the Vercel Microfrontends proxy.\n * @returns The properties of the remote component.\n *\n * @example\n *\n * ```tsx\n * import { getRemoteComponentProps } from 'remote-components/next/host/pages';\n * import type { GetServerSideProps } from 'next';\n *\n * export const getServerSideProps: GetServerSideProps<PageProps> = async function getServerSideProps({ req }) {\n * const myRemoteComponent = await getRemoteComponentProps(\n * '/nextjs-app-remote/components/header',\n * req.headers,\n * );\n * return {\n * props: {\n * remoteComponents: {\n * myRemoteComponent,\n * },\n * },\n * };\n * }\n * ```\n */\nexport async function getRemoteComponentProps(\n src: string,\n headers: IncomingHttpHeaders,\n): Promise<RemoteComponentProps> {\n if (typeof document !== 'undefined') {\n throw new Error(\n 'getRemoteComponentProps can only be used on the server side.',\n );\n }\n\n const {\n metadata: { bundle, route, runtime },\n name,\n html,\n nextData,\n } = await fetchRemoteComponent(\n src,\n headers instanceof Headers\n ? headers\n : // convert IncomingHttpHeaders to web standard Headers\n Object.entries(headers).reduce((acc, [key, value]) => {\n if (value) {\n if (Array.isArray(value)) {\n value.forEach((v) => acc.append(key, v));\n } else {\n acc.append(key, value);\n }\n }\n return acc;\n }, new Headers()),\n );\n\n const props = {\n src,\n bundle,\n name,\n route,\n runtime,\n };\n\n // do not render the HTML in development mode when remote is using Next.js Pages Router\n // this behavior is emulating the Next.js Pages Router FOUC as the styles are only applied on the client when running in development mode\n if (nextData?.buildId === 'development') {\n return props;\n }\n\n const key = getKey(props);\n // store the HTML in a global store\n setComponent(key, html);\n\n return {\n ...props,\n // add remote component key to the props\n [REMOTE_COMPONENT_KEY]: key,\n };\n}\n"],"mappings":"AA0FM;AAzFN,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,4BAA4B;AAgBrC,MAAM,yBAAyB,OAAO,wBAAwB;AAC9D,MAAM,uBAAuB;AAI7B,MAAM,OAAO;AAIb,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,SAAO,GAAG,UAAU,YAAY,SAAS,OAAO,QAAQ,gCAAgC,OAAO,WAAW;AAC5G;AAEA,SAAS,aAAa,KAAa,MAAoB;AACrD,MAAI,CAAC,KAAK,sBAAsB,GAAG;AACjC,SAAK,sBAAsB,IAAI,oBAAI,IAAI;AAAA,EACzC;AACA,OAAK,sBAAsB,EAAE,IAAI,KAAK,IAAI;AAC5C;AAEA,SAAS,aAAa,KAAiC;AACrD,QAAM,YAAY,KAAK,sBAAsB,GAAG,IAAI,GAAG;AAGvD,OAAK,sBAAsB,GAAG,OAAO,GAAG;AACxC,SAAO;AACT;AAiBO,SAAS,gBAAgB,OAA0C;AACxE,QAAM,kBACJ,OAAO,aAAa,cAChB;AAAA;AAAA,IAEA;AAAA,MACE,MAAM,oBAAoB,KAAK;AAAA,IACjC;AAAA;AAEN,YAAU,MAAM;AACd,UAAM,aAAa;AAInB,eAAW,8BAA8B;AACzC,WAAO,wBAAwB;AAAA,EACjC,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,MAAM,oBAAoB,GAAG;AAChC,WACE,oBAAC,sBAAiB,KAAK,MAAM,KAAM,gBAAM,UAAS;AAAA,EAEtD;AAEA,SACE,oBAAC,sBAAiB,KAAK,MAAM,KAC3B;AAAA,IAAC;AAAA;AAAA,MACC,yBAAyB,EAAE,QAAQ,mBAAmB,GAAG;AAAA,MACzD,IAAG;AAAA,MACH,0BAAwB;AAAA;AAAA,EAC1B,GACF;AAEJ;AA8BA,eAAsB,wBACpB,KACA,SAC+B;AAC/B,MAAI,OAAO,aAAa,aAAa;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM;AAAA,IACJ,UAAU,EAAE,QAAQ,OAAO,QAAQ;AAAA,IACnC;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM;AAAA,IACR;AAAA,IACA,mBAAmB,UACf;AAAA;AAAA,MAEA,OAAO,QAAQ,OAAO,EAAE,OAAO,CAAC,KAAK,CAACA,MAAK,KAAK,MAAM;AACpD,YAAI,OAAO;AACT,cAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,kBAAM,QAAQ,CAAC,MAAM,IAAI,OAAOA,MAAK,CAAC,CAAC;AAAA,UACzC,OAAO;AACL,gBAAI,OAAOA,MAAK,KAAK;AAAA,UACvB;AAAA,QACF;AACA,eAAO;AAAA,MACT,GAAG,IAAI,QAAQ,CAAC;AAAA;AAAA,EACtB;AAEA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAIA,MAAI,UAAU,YAAY,eAAe;AACvC,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,OAAO,KAAK;AAExB,eAAa,KAAK,IAAI;AAEtB,SAAO;AAAA,IACL,GAAG;AAAA;AAAA,IAEH,CAAC,oBAAoB,GAAG;AAAA,EAC1B;AACF;","names":["key"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/next/remote/pages.ts"],"sourcesContent":["const CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\n
|
|
1
|
+
{"version":3,"sources":["../../../src/next/remote/pages.ts"],"sourcesContent":["const CURRENT_ZONE = process.env.NEXT_PUBLIC_MFE_CURRENT_APPLICATION;\n\nexport interface RemoteComponentMetadata {\n __REMOTE_COMPONENT__: {\n bundle: string | undefined;\n runtime: 'turbopack' | 'webpack';\n };\n}\n\n/**\n * Returns the metadata for the remote component.\n * This metadata is used to identify the remote component and its bundle.\n *\n * Extend your Next.js Pages Router page props with this metadata to ensure proper remote component loading.\n *\n * @returns The metadata for the remote component.\n *\n * @example\n *\n * Create a custom App component in your Next.js application to include the remote component metadata:\n *\n * ```\n * import {\n * getRemoteComponentMetadata,\n * type RemoteComponentMetadata,\n * } from 'remote-components/next/pages';\n * import App from 'next/app';\n * import type { AppContext, AppInitialProps, AppProps } from 'next/app';\n *\n * export default function MyApp({ Component, pageProps }: AppProps) {\n * return <Component {...pageProps} />;\n * }\n *\n * MyApp.getInitialProps = async (\n * context: AppContext,\n * ): Promise<RemoteComponentMetadata & AppInitialProps> => {\n * const ctx = await App.getInitialProps(context);\n * return { ...ctx, ...getRemoteComponentMetadata() };\n * };\n * ```\n */\nexport function getRemoteComponentMetadata(): RemoteComponentMetadata {\n return {\n __REMOTE_COMPONENT__: {\n bundle: CURRENT_ZONE,\n runtime: process.env.TURBOPACK ? 'turbopack' : 'webpack',\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAM,eAAe,QAAQ,IAAI;AAyC1B,SAAS,6BAAsD;AACpE,SAAO;AAAA,IACL,sBAAsB;AAAA,MACpB,QAAQ;AAAA,MACR,SAAS,QAAQ,IAAI,YAAY,cAAc;AAAA,IACjD;AAAA,EACF;AACF;","names":[]}
|