@ecopages/react 0.2.0-alpha.9 → 0.2.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +30 -13
  2. package/package.json +23 -12
  3. package/src/eco-embed.d.ts +11 -0
  4. package/src/eco-embed.js +11 -0
  5. package/src/react-hmr-strategy.d.ts +102 -18
  6. package/src/react-hmr-strategy.js +427 -50
  7. package/src/react-renderer.d.ts +100 -92
  8. package/src/react-renderer.js +356 -340
  9. package/src/react.constants.d.ts +1 -0
  10. package/src/react.constants.js +4 -0
  11. package/src/react.plugin.d.ts +25 -107
  12. package/src/react.plugin.js +109 -61
  13. package/src/react.types.d.ts +88 -0
  14. package/src/react.types.js +0 -0
  15. package/src/router-adapter.d.ts +7 -14
  16. package/src/runtime/use-sync-external-store-with-selector.d.ts +3 -0
  17. package/src/runtime/use-sync-external-store-with-selector.js +56 -0
  18. package/src/services/pages-index.d.ts +64 -0
  19. package/src/services/pages-index.js +73 -0
  20. package/src/services/react-bundle.service.d.ts +24 -9
  21. package/src/services/react-bundle.service.js +35 -24
  22. package/src/services/react-hmr-page-metadata-cache.d.ts +10 -1
  23. package/src/services/react-hmr-page-metadata-cache.js +18 -2
  24. package/src/services/react-hydration-asset.service.d.ts +28 -19
  25. package/src/services/react-hydration-asset.service.js +83 -64
  26. package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
  27. package/src/services/react-mdx-config-dependency.service.js +122 -0
  28. package/src/services/react-page-module.service.d.ts +8 -3
  29. package/src/services/react-page-module.service.js +33 -26
  30. package/src/services/react-page-payload.service.d.ts +46 -0
  31. package/src/services/react-page-payload.service.js +67 -0
  32. package/src/services/react-runtime-bundle.service.d.ts +9 -2
  33. package/src/services/react-runtime-bundle.service.js +77 -16
  34. package/src/utils/client-graph-boundary-cache.d.ts +108 -0
  35. package/src/utils/client-graph-boundary-cache.js +116 -0
  36. package/src/utils/client-graph-boundary-plugin.d.ts +13 -5
  37. package/src/utils/client-graph-boundary-plugin.js +63 -5
  38. package/src/utils/component-config-traversal.d.ts +36 -0
  39. package/src/utils/component-config-traversal.js +54 -0
  40. package/src/utils/declared-modules.d.ts +1 -1
  41. package/src/utils/declared-modules.js +7 -16
  42. package/src/utils/dynamic.test.browser.d.ts +1 -0
  43. package/src/utils/dynamic.test.browser.js +33 -0
  44. package/src/utils/hydration-scripts.d.ts +9 -5
  45. package/src/utils/hydration-scripts.js +119 -34
  46. package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
  47. package/src/utils/hydration-scripts.test.browser.js +198 -0
  48. package/src/utils/react-dom-runtime-interop-plugin.d.ts +1 -1
  49. package/src/utils/react-dom-runtime-interop-plugin.js +9 -0
  50. package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
  51. package/src/utils/{react-runtime-specifier-map.d.ts → react-runtime-alias-map.d.ts} +3 -1
  52. package/src/utils/react-runtime-alias-map.js +90 -0
  53. package/CHANGELOG.md +0 -27
  54. package/src/react-hmr-strategy.ts +0 -386
  55. package/src/react-renderer.ts +0 -803
  56. package/src/react.plugin.ts +0 -276
  57. package/src/router-adapter.ts +0 -95
  58. package/src/services/react-bundle.service.ts +0 -108
  59. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  60. package/src/services/react-hydration-asset.service.ts +0 -263
  61. package/src/services/react-page-module.service.ts +0 -224
  62. package/src/services/react-runtime-bundle.service.ts +0 -172
  63. package/src/utils/client-graph-boundary-plugin.ts +0 -831
  64. package/src/utils/client-only.ts +0 -27
  65. package/src/utils/declared-modules.ts +0 -99
  66. package/src/utils/dynamic.ts +0 -27
  67. package/src/utils/hmr-scripts.ts +0 -47
  68. package/src/utils/html-boundary.ts +0 -66
  69. package/src/utils/hydration-scripts.ts +0 -459
  70. package/src/utils/reachability-analyzer.ts +0 -593
  71. package/src/utils/react-dom-runtime-interop-plugin.ts +0 -33
  72. package/src/utils/react-mdx-loader-plugin.ts +0 -63
  73. package/src/utils/react-runtime-specifier-map.js +0 -37
  74. package/src/utils/react-runtime-specifier-map.ts +0 -45
  75. package/src/utils/use-sync-external-store-shim-plugin.d.ts +0 -5
  76. package/src/utils/use-sync-external-store-shim-plugin.js +0 -41
  77. package/src/utils/use-sync-external-store-shim-plugin.ts +0 -45
@@ -31,19 +31,44 @@ function getProdPageRootCleanupScript() {
31
31
  return 'window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.cleanupPageRoot=()=>{const a=window.__ECO_PAGES__.react?.pageRoot||root;if(!a){window.__ECO_PAGES__.react.pageRoot=null;window.__ECO_PAGES__?.navigation?.releaseOwnership?.("react-router");delete window.__ECO_PAGES__.page;return}window.__ECO_PAGES__.react.pageRoot=null;window.__ECO_PAGES__?.navigation?.releaseOwnership?.("react-router");delete window.__ECO_PAGES__.page;root=null;a.unmount()};';
32
32
  }
33
33
  function getDevRouterBootstrapRegistrationScript() {
34
- return `window.__ECO_PAGES__?.navigation?.register({
34
+ return `const currentOwnerState = window.__ECO_PAGES__?.navigation?.getOwnerState?.();
35
+ if (!(currentOwnerState?.owner === "react-router" && currentOwnerState.canHandleSpaNavigation)) {
36
+ window.__ECO_PAGES__?.navigation?.register({
35
37
  owner: "react-router",
36
38
  cleanupBeforeHandoff: async () => {
37
39
  window.__ECO_PAGES__?.react?.cleanupPageRoot?.();
38
40
  }
39
41
  });
40
- window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router");`;
42
+ window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router");
43
+ }`;
41
44
  }
42
45
  function getProdRouterBootstrapRegistrationScript() {
43
- return 'window.__ECO_PAGES__?.navigation?.register({owner:"react-router",cleanupBeforeHandoff:async()=>{window.__ECO_PAGES__?.react?.cleanupPageRoot?.()}});window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router");';
46
+ return 'const o=window.__ECO_PAGES__?.navigation?.getOwnerState?.();if(!(o?.owner==="react-router"&&o.canHandleSpaNavigation)){window.__ECO_PAGES__?.navigation?.register({owner:"react-router",cleanupBeforeHandoff:async()=>{window.__ECO_PAGES__?.react?.cleanupPageRoot?.()}});window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router")}';
47
+ }
48
+ function getDevReuseExistingRouterRootScript() {
49
+ return `const shouldReuseExistingRouterRoot = () => {
50
+ const ownerState = window.__ECO_PAGES__?.navigation?.getOwnerState?.();
51
+ return Boolean(
52
+ window.__ECO_PAGES__.react?.pageRoot &&
53
+ ownerState?.owner === "react-router" &&
54
+ ownerState.canHandleSpaNavigation
55
+ );
56
+ };`;
57
+ }
58
+ function getProdReuseExistingRouterRootScript() {
59
+ return 'const sr=()=>{const o=window.__ECO_PAGES__?.navigation?.getOwnerState?.();return!!(window.__ECO_PAGES__.react?.pageRoot&&o?.owner==="react-router"&&o.canHandleSpaNavigation)};';
60
+ }
61
+ function getDevRerunRegistrationScript(scriptId) {
62
+ return `window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
63
+ window.__ECO_PAGES__.rerunScripts = window.__ECO_PAGES__.rerunScripts || {};
64
+ window.__ECO_PAGES__.rerunScripts[${JSON.stringify(scriptId)}] = mount;`;
65
+ }
66
+ function getProdRerunRegistrationScript(scriptId) {
67
+ return `window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.rerunScripts=window.__ECO_PAGES__.rerunScripts||{};window.__ECO_PAGES__.rerunScripts[${JSON.stringify(scriptId)}]=m;`;
44
68
  }
45
69
  function createDevScriptWithRouter(options) {
46
- const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath } = options;
70
+ const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath, scriptId } = options;
71
+ const pageModuleUrlExpression = options.pageModuleUrlExpression ?? "import.meta.url";
47
72
  const { components, getRouterProps } = router;
48
73
  if (!routerImportPath) {
49
74
  throw new Error("routerImportPath is required when router adapter is configured");
@@ -53,6 +78,12 @@ import { hydrateRoot } from "${reactDomClientImportPath}";
53
78
  import { createElement } from "${reactImportPath}";
54
79
  import { ${components.router}, ${components.pageContent} } from "${routerImportPath}";
55
80
  ${getImportStatement(importPath, isMdx)}
81
+ const pageModuleUrl = ${pageModuleUrlExpression};
82
+ export default Page;
83
+ export const config = Page.config;
84
+ const isActivePageEntry = Boolean(document.querySelector('script[data-eco-script-id="${scriptId}"]'));
85
+
86
+ if (isActivePageEntry) {
56
87
 
57
88
  window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
58
89
  window.__ECO_PAGES__.hmrHandlers = window.__ECO_PAGES__.hmrHandlers || {};
@@ -61,6 +92,7 @@ window.__ECO_PAGES__.react.pageRoot = window.__ECO_PAGES__.react.pageRoot || nul
61
92
  let root = window.__ECO_PAGES__.react.pageRoot;
62
93
  ${getDevPageRootCleanupScript()}
63
94
  ${getDevRouterBootstrapRegistrationScript()}
95
+ ${getDevReuseExistingRouterRootScript()}
64
96
 
65
97
  const getPageData = () => {
66
98
  const el = document.getElementById("__ECO_PAGE_DATA__");
@@ -73,7 +105,7 @@ const getPageData = () => {
73
105
  const props = getPageData();
74
106
 
75
107
  window.__ECO_PAGES__.page = {
76
- module: "${importPath}",
108
+ module: pageModuleUrl,
77
109
  props
78
110
  };
79
111
 
@@ -83,25 +115,49 @@ const createTree = (Component, props) => {
83
115
  };
84
116
 
85
117
  const mount = () => {
118
+ const props = getPageData();
119
+ window.__ECO_PAGES__.page = {
120
+ module: pageModuleUrl,
121
+ props
122
+ };
123
+
124
+ if (shouldReuseExistingRouterRoot()) {
125
+ root = window.__ECO_PAGES__.react.pageRoot;
126
+ return;
127
+ }
128
+
86
129
  if (window.__ECO_PAGES__.react?.pageRoot) {
87
130
  root = window.__ECO_PAGES__.react.pageRoot;
88
- root.render(createTree(Page, props));
89
- } else {
90
- root = hydrateRoot(document.body, createTree(Page, props), {
91
- onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
92
- });
93
- window.__ECO_PAGES__.react.pageRoot = root;
131
+ return;
94
132
  }
133
+
134
+ root = hydrateRoot(document.body, createTree(Page, props), {
135
+ onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
136
+ });
137
+ window.__ECO_PAGES__.react.pageRoot = root;
95
138
  window.__ECO_PAGES__.hmrHandlers["${importPath}"] = async (newUrl) => {
96
- if (window.__ECO_PAGES__?.navigation?.getOwnerState().owner === "react-router") {
97
- await window.__ECO_PAGES__?.navigation?.reloadCurrentPage?.({ clearCache: false, source: "react-router" });
98
- console.log("[ecopages] ${getComponentType(isMdx)} component updated via router");
99
- return;
100
- }
101
139
  try {
102
140
  const newModule = await import(newUrl);
141
+ const nextProps = getPageData();
103
142
  ${getHmrImportStatement(isMdx)}
104
- root.render(createTree(NewPage, props));
143
+ const currentPageLayout = Page.config?.layout;
144
+ const nextPageLayout = NewPage.config?.layout;
145
+
146
+ if (window.__ECO_PAGES__?.navigation?.getOwnerState().owner === "react-router") {
147
+ await window.__ECO_PAGES__?.navigation?.reloadCurrentPage?.({
148
+ clearCache: currentPageLayout !== nextPageLayout,
149
+ moduleUrl: "${importPath}",
150
+ source: "react-router"
151
+ });
152
+ console.log("[ecopages] ${getComponentType(isMdx)} component updated via router");
153
+ return;
154
+ }
155
+
156
+ window.__ECO_PAGES__.page = {
157
+ module: pageModuleUrl,
158
+ props: nextProps
159
+ };
160
+ root.render(createTree(NewPage, nextProps));
105
161
  console.log("[ecopages] ${getComponentType(isMdx)} component updated");
106
162
  } catch (e) {
107
163
  console.error("[ecopages] Failed to hot-reload ${getComponentType(isMdx)} component:", e);
@@ -109,19 +165,29 @@ const mount = () => {
109
165
  };
110
166
  };
111
167
 
168
+ ${getDevRerunRegistrationScript(scriptId)}
169
+
112
170
  if (document.readyState === "loading") {
113
171
  document.addEventListener("DOMContentLoaded", mount);
114
172
  } else {
115
173
  mount();
116
174
  }
175
+ }
117
176
  `.trim();
118
177
  }
119
178
  function createDevScriptWithoutRouter(options) {
120
- const { importPath, isMdx, reactImportPath, reactDomClientImportPath } = options;
179
+ const { importPath, isMdx, reactImportPath, reactDomClientImportPath, scriptId } = options;
180
+ const pageModuleUrlExpression = options.pageModuleUrlExpression ?? "import.meta.url";
121
181
  return `
122
182
  import { hydrateRoot } from "${reactDomClientImportPath}";
123
183
  import { createElement } from "${reactImportPath}";
124
184
  ${getImportStatement(importPath, isMdx)}
185
+ const pageModuleUrl = ${pageModuleUrlExpression};
186
+ export default Page;
187
+ export const config = Page.config;
188
+ const isActivePageEntry = Boolean(document.querySelector('script[data-eco-script-id="${scriptId}"]'));
189
+
190
+ if (isActivePageEntry) {
125
191
 
126
192
  window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
127
193
  window.__ECO_PAGES__.hmrHandlers = window.__ECO_PAGES__.hmrHandlers || {};
@@ -141,7 +207,7 @@ const getPageData = () => {
141
207
  const props = getPageData();
142
208
 
143
209
  window.__ECO_PAGES__.page = {
144
- module: "${importPath}",
210
+ module: pageModuleUrl,
145
211
  props
146
212
  };
147
213
 
@@ -153,6 +219,12 @@ const createTree = (Component, props) => {
153
219
  };
154
220
 
155
221
  const mount = () => {
222
+ const props = getPageData();
223
+ window.__ECO_PAGES__.page = {
224
+ module: pageModuleUrl,
225
+ props
226
+ };
227
+
156
228
  if (window.__ECO_PAGES__.react?.pageRoot) {
157
229
  root = window.__ECO_PAGES__.react.pageRoot;
158
230
  root.render(createTree(Page, props));
@@ -174,30 +246,35 @@ const mount = () => {
174
246
  };
175
247
  };
176
248
 
249
+ ${getDevRerunRegistrationScript(scriptId)}
250
+
177
251
  if (document.readyState === "loading") {
178
252
  document.addEventListener("DOMContentLoaded", mount);
179
253
  } else {
180
254
  mount();
181
255
  }
256
+ }
182
257
  `.trim();
183
258
  }
184
259
  function createProdScriptWithRouter(options) {
185
- const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath } = options;
260
+ const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath, scriptId } = options;
261
+ const pageModuleUrlExpression = options.pageModuleUrlExpression ?? "import.meta.url";
186
262
  const { components, getRouterProps } = router;
187
263
  if (!routerImportPath) {
188
264
  throw new Error("routerImportPath is required when router adapter is configured");
189
265
  }
190
266
  if (isMdx) {
191
- return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import{${components.router} as R,${components.pageContent} as PC}from"${routerImportPath}";import*as M from"${importPath}";const P=M.default;if(M.config)P.config=M.config;window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}${getProdRouterBootstrapRegistrationScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGES__.page={module:"${importPath}",props:pr};const ct=(C,p)=>ce(R,${getRouterProps("C", "p")},ce(PC));const m=()=>{if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;root.render(ct(P,pr));return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
267
+ return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import{${components.router} as R,${components.pageContent} as PC}from"${routerImportPath}";import*as M from"${importPath}";const P=M.default;if(M.config)P.config=M.config;const u=${pageModuleUrlExpression};export default P;export const config=P.config;const a=!!document.querySelector('script[data-eco-script-id="${scriptId}"]');if(a){window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}${getProdRouterBootstrapRegistrationScript()}${getProdReuseExistingRouterRootScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const ct=(C,p)=>ce(R,${getRouterProps("C", "p")},ce(PC));const m=()=>{const pr=gd();window.__ECO_PAGES__.page={module:u,props:pr};if(sr()){root=window.__ECO_PAGES__.react.pageRoot;return}if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};${getProdRerunRegistrationScript(scriptId)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()}`;
192
268
  }
193
- return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import{${components.router} as R,${components.pageContent} as PC}from"${routerImportPath}";import P from"${importPath}";window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}${getProdRouterBootstrapRegistrationScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGES__.page={module:"${importPath}",props:pr};const ct=(C,p)=>ce(R,${getRouterProps("C", "p")},ce(PC));const m=()=>{if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;root.render(ct(P,pr));return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
269
+ return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import{${components.router} as R,${components.pageContent} as PC}from"${routerImportPath}";import P from"${importPath}";const u=${pageModuleUrlExpression};export default P;export const config=P.config;const a=!!document.querySelector('script[data-eco-script-id="${scriptId}"]');if(a){window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}${getProdRouterBootstrapRegistrationScript()}${getProdReuseExistingRouterRootScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const ct=(C,p)=>ce(R,${getRouterProps("C", "p")},ce(PC));const m=()=>{const pr=gd();window.__ECO_PAGES__.page={module:u,props:pr};if(sr()){root=window.__ECO_PAGES__.react.pageRoot;return}if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};${getProdRerunRegistrationScript(scriptId)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()}`;
194
270
  }
195
271
  function createProdScriptWithoutRouter(options) {
196
- const { importPath, isMdx, reactImportPath, reactDomClientImportPath } = options;
272
+ const { importPath, isMdx, reactImportPath, reactDomClientImportPath, scriptId } = options;
273
+ const pageModuleUrlExpression = options.pageModuleUrlExpression ?? "import.meta.url";
197
274
  if (isMdx) {
198
- return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import*as M from"${importPath}";const P=M.default;if(M.config)P.config=M.config;window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGES__.page={module:"${importPath}",props:pr};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);const lp=p?.locals?{locals:p.locals}:null;return L?ce(L,lp,pe):pe};const m=()=>{if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;root.render(ct(P,pr));return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
275
+ return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import*as M from"${importPath}";const P=M.default;if(M.config)P.config=M.config;const u=${pageModuleUrlExpression};export default P;export const config=P.config;const a=!!document.querySelector('script[data-eco-script-id="${scriptId}"]');if(a){window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);const lp=p?.locals?{locals:p.locals}:null;return L?ce(L,lp,pe):pe};const m=()=>{const pr=gd();window.__ECO_PAGES__.page={module:u,props:pr};if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;root.render(ct(P,pr));return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};${getProdRerunRegistrationScript(scriptId)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()}`;
199
276
  }
200
- return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import P from"${importPath}";window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGES__.page={module:"${importPath}",props:pr};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);const lp=p?.locals?{locals:p.locals}:null;return L?ce(L,lp,pe):pe};const m=()=>{if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;root.render(ct(P,pr));return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
277
+ return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import P from"${importPath}";const u=${pageModuleUrlExpression};export default P;export const config=P.config;const a=!!document.querySelector('script[data-eco-script-id="${scriptId}"]');if(a){window.__ECO_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.react=window.__ECO_PAGES__.react||{};window.__ECO_PAGES__.react.pageRoot=window.__ECO_PAGES__.react.pageRoot||null;let root=window.__ECO_PAGES__.react.pageRoot;${getProdPageRootCleanupScript()}const gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);const lp=p?.locals?{locals:p.locals}:null;return L?ce(L,lp,pe):pe};const m=()=>{const pr=gd();window.__ECO_PAGES__.page={module:u,props:pr};if(window.__ECO_PAGES__.react?.pageRoot){root=window.__ECO_PAGES__.react.pageRoot;root.render(ct(P,pr));return}root=hr(document.body,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});window.__ECO_PAGES__.react.pageRoot=root};${getProdRerunRegistrationScript(scriptId)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()}`;
201
278
  }
202
279
  function createHydrationScript(options) {
203
280
  const { isDevelopment, router } = options;
@@ -210,6 +287,7 @@ function createIslandHydrationScript(options) {
210
287
  const targetSelector = JSON.stringify(options.targetSelector);
211
288
  const componentRef = JSON.stringify(options.componentRef ?? "");
212
289
  const componentFile = JSON.stringify(options.componentFile ?? "");
290
+ const scriptId = options.scriptId;
213
291
  if (options.isDevelopment) {
214
292
  return `
215
293
  import { createRoot } from "${options.reactDomClientImportPath}";
@@ -245,19 +323,26 @@ const resolveComponent = () => {
245
323
  };
246
324
 
247
325
  const mount = () => {
248
- const target = document.querySelector(${targetSelector});
326
+ const targets = document.querySelectorAll(${targetSelector});
249
327
  const Component = resolveComponent();
250
- if (!target || !Component) {
328
+ if (!Component || targets.length === 0) {
251
329
  return;
252
330
  }
253
- const props = JSON.parse(atob(target.getAttribute("data-eco-props") || "e30="));
254
- const container = document.createElement("eco-island");
255
- container.style.display = "block";
256
- target.replaceWith(container);
257
- const root = createRoot(container);
258
- root.render(createElement(Component, props));
331
+ targets.forEach((target) => {
332
+ if (!(target instanceof HTMLElement)) {
333
+ return;
334
+ }
335
+ const props = JSON.parse(atob(target.getAttribute("data-eco-props") || "e30="));
336
+ const container = document.createElement("eco-island");
337
+ container.style.display = "block";
338
+ target.replaceWith(container);
339
+ const root = createRoot(container);
340
+ root.render(createElement(Component, props));
341
+ });
259
342
  };
260
343
 
344
+ ${getDevRerunRegistrationScript(scriptId)}
345
+
261
346
  if (document.readyState === "loading") {
262
347
  document.addEventListener("DOMContentLoaded", mount, { once: true });
263
348
  } else {
@@ -265,7 +350,7 @@ if (document.readyState === "loading") {
265
350
  }
266
351
  `.trim();
267
352
  }
268
- return `import{createRoot as cr}from"${options.reactDomClientImportPath}";import{createElement as ce}from"${options.reactImportPath}";import*as M from"${options.importPath}";const r=${componentRef};const f=${componentFile};const mv=Object.values(M);const c=mv.find((e)=>{if(typeof e!=="function")return false;const ec=e.config?.__eco;if(!ec)return false;if(r&&ec.id===r)return true;if(f&&ec.file===f)return true;return false;})??(typeof M.default==="function"?M.default:mv.find((e)=>typeof e==="function")??null);const m=()=>{const t=document.querySelector(${targetSelector});if(!t||!c)return;const p=JSON.parse(atob(t.getAttribute("data-eco-props")||"e30="));const ct=document.createElement("eco-island");ct.style.display="block";t.replaceWith(ct);cr(ct).render(ce(c,p))};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m,{once:true}):m()`;
353
+ return `import{createRoot as cr}from"${options.reactDomClientImportPath}";import{createElement as ce}from"${options.reactImportPath}";import*as M from"${options.importPath}";const r=${componentRef};const f=${componentFile};const mv=Object.values(M);const c=mv.find((e)=>{if(typeof e!=="function")return false;const ec=e.config?.__eco;if(!ec)return false;if(r&&ec.id===r)return true;if(f&&ec.file===f)return true;return false;})??(typeof M.default==="function"?M.default:mv.find((e)=>typeof e==="function")??null);const m=()=>{const ts=document.querySelectorAll(${targetSelector});if(!c||ts.length===0)return;ts.forEach((t)=>{if(!(t instanceof HTMLElement))return;const p=JSON.parse(atob(t.getAttribute("data-eco-props")||"e30="));const ct=document.createElement("eco-island");ct.style.display="block";t.replaceWith(ct);cr(ct).render(ce(c,p))})};${getProdRerunRegistrationScript(scriptId)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m,{once:true}):m()`;
269
354
  }
270
355
  export {
271
356
  createHydrationScript,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,198 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { createHydrationScript } from "./hydration-scripts.js";
3
+ const routerAdapter = {
4
+ name: "eco-router",
5
+ bundle: {
6
+ importPath: "/assets/router.js",
7
+ outputName: "router",
8
+ externals: []
9
+ },
10
+ components: {
11
+ router: "EcoRouter",
12
+ pageContent: "PageContent"
13
+ },
14
+ getRouterProps: (page, props) => `{ page: ${page}, pageProps: ${props} }`
15
+ };
16
+ function createModuleUrl(source) {
17
+ return `data:text/javascript;base64,${btoa(source)}`;
18
+ }
19
+ async function importModule(moduleUrl, scriptId) {
20
+ let marker;
21
+ if (scriptId) {
22
+ marker = document.createElement("script");
23
+ marker.setAttribute("data-eco-script-id", scriptId);
24
+ document.head.appendChild(marker);
25
+ }
26
+ await import(
27
+ /* @vite-ignore */
28
+ moduleUrl
29
+ );
30
+ marker?.remove();
31
+ }
32
+ function createRuntimeModules() {
33
+ const reactImportPath = createModuleUrl("export const createElement = (...args) => ({ args });");
34
+ const reactDomClientImportPath = createModuleUrl(`
35
+ export const hydrateRoot = (container, tree, options) => {
36
+ const runtime = window.__ECO_REACT_HYDRATION_TEST__;
37
+ runtime.hydrateCalls.push({
38
+ containerTag: container.tagName,
39
+ hasRecoverableErrorHandler: typeof options?.onRecoverableError === "function",
40
+ tree,
41
+ });
42
+
43
+ return {
44
+ render() {},
45
+ unmount() {
46
+ runtime.unmountCount += 1;
47
+ },
48
+ };
49
+ };
50
+ `);
51
+ const importPath = createModuleUrl("export default function Page() { return null; }");
52
+ const routerImportPath = createModuleUrl(`
53
+ export function EcoRouter(props) {
54
+ return props;
55
+ }
56
+
57
+ export function PageContent() {
58
+ return null;
59
+ }
60
+ `);
61
+ return {
62
+ importPath,
63
+ reactImportPath,
64
+ reactDomClientImportPath,
65
+ routerImportPath
66
+ };
67
+ }
68
+ describe("createHydrationScript browser execution", () => {
69
+ afterEach(() => {
70
+ document.body.innerHTML = "";
71
+ const testWindow = window;
72
+ delete testWindow.__ECO_PAGES__;
73
+ delete testWindow.__ECO_REACT_HYDRATION_TEST__;
74
+ });
75
+ it("registers router ownership and cleanup when the browser hydration bootstrap runs", async () => {
76
+ const runtimeModules = createRuntimeModules();
77
+ const testWindow = window;
78
+ testWindow.__ECO_REACT_HYDRATION_TEST__ = {
79
+ hydrateCalls: [],
80
+ renderCalls: [],
81
+ claimedOwners: [],
82
+ releasedOwners: [],
83
+ registrations: [],
84
+ unmountCount: 0
85
+ };
86
+ testWindow.__ECO_PAGES__ = {
87
+ navigation: {
88
+ getOwnerState: () => ({
89
+ owner: "html",
90
+ canHandleSpaNavigation: false
91
+ }),
92
+ register: (registration) => {
93
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations.push(registration);
94
+ },
95
+ claimOwnership: (owner) => {
96
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners.push(owner);
97
+ },
98
+ releaseOwnership: (owner) => {
99
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.releasedOwners.push(owner);
100
+ }
101
+ }
102
+ };
103
+ document.body.innerHTML = `<script id="__ECO_PAGE_DATA__" type="application/json">${JSON.stringify({
104
+ title: "Hello React",
105
+ locals: { theme: "dark" }
106
+ })}<\/script>`;
107
+ const script = createHydrationScript({
108
+ ...runtimeModules,
109
+ scriptId: "ecopages-react-page",
110
+ isDevelopment: true,
111
+ isMdx: false,
112
+ router: routerAdapter
113
+ });
114
+ const moduleUrl = createModuleUrl(script);
115
+ await importModule(moduleUrl, "ecopages-react-page");
116
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls).toHaveLength(1);
117
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls[0]?.containerTag).toBe("BODY");
118
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls[0]?.hasRecoverableErrorHandler).toBe(true);
119
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners).toEqual(["react-router"]);
120
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations).toHaveLength(1);
121
+ expect(typeof testWindow.__ECO_PAGES__?.react?.cleanupPageRoot).toBe("function");
122
+ expect(testWindow.__ECO_PAGES__?.page).toEqual({
123
+ module: moduleUrl,
124
+ props: {
125
+ title: "Hello React",
126
+ locals: { theme: "dark" }
127
+ }
128
+ });
129
+ await testWindow.__ECO_PAGES__?.react?.cleanupPageRoot?.();
130
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.unmountCount).toBe(1);
131
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.releasedOwners).toEqual(["react-router"]);
132
+ expect(testWindow.__ECO_PAGES__?.page).toBeUndefined();
133
+ expect(testWindow.__ECO_PAGES__?.react?.pageRoot).toBeNull();
134
+ });
135
+ it("reuses an existing router-owned page root during rerun bootstrap execution", async () => {
136
+ const runtimeModules = createRuntimeModules();
137
+ const testWindow = window;
138
+ testWindow.__ECO_REACT_HYDRATION_TEST__ = {
139
+ hydrateCalls: [],
140
+ renderCalls: [],
141
+ claimedOwners: [],
142
+ releasedOwners: [],
143
+ registrations: [],
144
+ unmountCount: 0
145
+ };
146
+ const existingRoot = {
147
+ render: (tree) => {
148
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.renderCalls.push(tree);
149
+ },
150
+ unmount: () => {
151
+ testWindow.__ECO_REACT_HYDRATION_TEST__.unmountCount += 1;
152
+ }
153
+ };
154
+ testWindow.__ECO_PAGES__ = {
155
+ navigation: {
156
+ getOwnerState: () => ({
157
+ owner: "react-router",
158
+ canHandleSpaNavigation: true
159
+ }),
160
+ register: (registration) => {
161
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations.push(registration);
162
+ },
163
+ claimOwnership: (owner) => {
164
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners.push(owner);
165
+ },
166
+ releaseOwnership: (owner) => {
167
+ testWindow.__ECO_REACT_HYDRATION_TEST__?.releasedOwners.push(owner);
168
+ }
169
+ },
170
+ react: {
171
+ pageRoot: existingRoot
172
+ }
173
+ };
174
+ document.body.innerHTML = `<script id="__ECO_PAGE_DATA__" type="application/json">${JSON.stringify({
175
+ title: "Rerun"
176
+ })}<\/script>`;
177
+ const script = createHydrationScript({
178
+ ...runtimeModules,
179
+ scriptId: "ecopages-react-page-rerun",
180
+ isDevelopment: true,
181
+ isMdx: false,
182
+ router: routerAdapter
183
+ });
184
+ const moduleUrl = createModuleUrl(script);
185
+ await importModule(moduleUrl, "ecopages-react-page-rerun");
186
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.hydrateCalls).toHaveLength(0);
187
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.renderCalls).toHaveLength(0);
188
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.claimedOwners).toHaveLength(0);
189
+ expect(testWindow.__ECO_REACT_HYDRATION_TEST__?.registrations).toHaveLength(0);
190
+ expect(testWindow.__ECO_PAGES__?.react?.pageRoot).toBe(existingRoot);
191
+ expect(testWindow.__ECO_PAGES__?.page).toEqual({
192
+ module: moduleUrl,
193
+ props: {
194
+ title: "Rerun"
195
+ }
196
+ });
197
+ });
198
+ });
@@ -1,4 +1,4 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
1
+ import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
2
  export declare function createReactDomRuntimeInteropPlugin(options?: {
3
3
  name?: string;
4
4
  reactSpecifier?: string;
@@ -1,5 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ function escapeRegExp(value) {
4
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
3
6
  function createReactDomRuntimeInteropPlugin(options) {
4
7
  const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
5
8
  const reactRequirePattern = /\brequire\((['"])react\1\)/g;
@@ -7,6 +10,12 @@ function createReactDomRuntimeInteropPlugin(options) {
7
10
  return {
8
11
  name: options?.name ?? "react-dom-runtime-interop",
9
12
  setup(build) {
13
+ if (reactSpecifier.startsWith("/")) {
14
+ build.onResolve({ filter: new RegExp(`^${escapeRegExp(reactSpecifier)}$`) }, (args) => ({
15
+ path: args.path,
16
+ external: true
17
+ }));
18
+ }
10
19
  build.onLoad({ filter: reactDomFileFilter }, (args) => {
11
20
  const content = fs.readFileSync(args.path, "utf-8");
12
21
  if (!reactRequirePattern.test(content)) {
@@ -1,3 +1,3 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
1
+ import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
2
  import { type CompileOptions } from '@mdx-js/mdx';
3
3
  export declare function createReactMdxLoaderPlugin(compilerOptions?: CompileOptions): EcoBuildPlugin;
@@ -1,6 +1,8 @@
1
1
  import type { ReactRouterAdapter } from '../router-adapter.js';
2
2
  import type { ReactRuntimeImports } from '../services/react-runtime-bundle.service.js';
3
+ import { type BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
3
4
  export declare const REACT_RUNTIME_SPECIFIERS: readonly ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom/client"];
4
- export declare function buildReactRuntimeSpecifierMap(runtimeImports: ReactRuntimeImports, routerAdapter?: ReactRouterAdapter): Record<string, string>;
5
+ export declare function buildReactRuntimeAliasMap(runtimeImports: ReactRuntimeImports): Record<string, string>;
6
+ export declare function buildReactRuntimeManifest(runtimeImports: ReactRuntimeImports): BrowserRuntimeManifest;
5
7
  export declare function getReactRuntimeExternalSpecifiers(): string[];
6
8
  export declare function getReactClientGraphAllowSpecifiers(runtimeSpecifiers: Iterable<string>, routerAdapter?: ReactRouterAdapter): string[];
@@ -0,0 +1,90 @@
1
+ import {
2
+ createBrowserRuntimeManifest,
3
+ getBrowserRuntimeSpecifierMap
4
+ } from "@ecopages/core/build/browser-runtime-manifest";
5
+ const REACT_RUNTIME_SPECIFIERS = [
6
+ "react",
7
+ "react-dom",
8
+ "react/jsx-runtime",
9
+ "react/jsx-dev-runtime",
10
+ "react-dom/client"
11
+ ];
12
+ function buildReactRuntimeAliasMap(runtimeImports) {
13
+ return Object.fromEntries(getBrowserRuntimeSpecifierMap(buildReactRuntimeManifest(runtimeImports)));
14
+ }
15
+ function buildReactRuntimeManifest(runtimeImports) {
16
+ return createBrowserRuntimeManifest([
17
+ {
18
+ specifier: "react",
19
+ owner: "@ecopages/react",
20
+ importPath: "react",
21
+ publicPath: runtimeImports.react
22
+ },
23
+ {
24
+ specifier: "react/jsx-runtime",
25
+ owner: "@ecopages/react",
26
+ importPath: "react/jsx-runtime",
27
+ publicPath: runtimeImports.reactJsxRuntime
28
+ },
29
+ {
30
+ specifier: "react/jsx-dev-runtime",
31
+ owner: "@ecopages/react",
32
+ importPath: "react/jsx-dev-runtime",
33
+ publicPath: runtimeImports.reactJsxDevRuntime
34
+ },
35
+ {
36
+ specifier: "react-dom",
37
+ owner: "@ecopages/react",
38
+ importPath: "react-dom",
39
+ publicPath: runtimeImports.reactDom
40
+ },
41
+ {
42
+ specifier: "react-dom/client",
43
+ owner: "@ecopages/react",
44
+ importPath: "react-dom/client",
45
+ publicPath: runtimeImports.reactDomClient
46
+ },
47
+ {
48
+ specifier: "use-sync-external-store/shim",
49
+ owner: "@ecopages/react",
50
+ importPath: "use-sync-external-store/shim",
51
+ publicPath: runtimeImports.react
52
+ },
53
+ {
54
+ specifier: "use-sync-external-store/shim/index.js",
55
+ owner: "@ecopages/react",
56
+ importPath: "use-sync-external-store/shim/index.js",
57
+ publicPath: runtimeImports.react
58
+ },
59
+ {
60
+ specifier: "use-sync-external-store/shim/with-selector",
61
+ owner: "@ecopages/react",
62
+ importPath: "use-sync-external-store/shim/with-selector",
63
+ publicPath: runtimeImports.useSyncExternalStoreWithSelector
64
+ },
65
+ {
66
+ specifier: "use-sync-external-store/shim/with-selector.js",
67
+ owner: "@ecopages/react",
68
+ importPath: "use-sync-external-store/shim/with-selector.js",
69
+ publicPath: runtimeImports.useSyncExternalStoreWithSelector
70
+ }
71
+ ]);
72
+ }
73
+ function getReactRuntimeExternalSpecifiers() {
74
+ return [...REACT_RUNTIME_SPECIFIERS];
75
+ }
76
+ function getReactClientGraphAllowSpecifiers(runtimeSpecifiers, routerAdapter) {
77
+ return [
78
+ "@ecopages/core",
79
+ ...REACT_RUNTIME_SPECIFIERS,
80
+ ...routerAdapter ? [routerAdapter.bundle.importPath] : [],
81
+ ...Array.from(runtimeSpecifiers)
82
+ ];
83
+ }
84
+ export {
85
+ REACT_RUNTIME_SPECIFIERS,
86
+ buildReactRuntimeAliasMap,
87
+ buildReactRuntimeManifest,
88
+ getReactClientGraphAllowSpecifiers,
89
+ getReactRuntimeExternalSpecifiers
90
+ };