@ecopages/react 0.2.0-alpha.5 → 0.2.0-alpha.7

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 (41) hide show
  1. package/CHANGELOG.md +22 -41
  2. package/README.md +135 -29
  3. package/package.json +3 -3
  4. package/src/react-hmr-strategy.d.ts +22 -30
  5. package/src/react-hmr-strategy.js +57 -120
  6. package/src/react-hmr-strategy.ts +76 -145
  7. package/src/react-renderer.d.ts +130 -11
  8. package/src/react-renderer.js +368 -64
  9. package/src/react-renderer.ts +490 -90
  10. package/src/react.plugin.d.ts +17 -5
  11. package/src/react.plugin.js +44 -13
  12. package/src/react.plugin.ts +49 -14
  13. package/src/router-adapter.d.ts +2 -2
  14. package/src/router-adapter.ts +2 -2
  15. package/src/services/react-bundle.service.d.ts +2 -30
  16. package/src/services/react-bundle.service.js +19 -94
  17. package/src/services/react-bundle.service.ts +20 -129
  18. package/src/services/react-hydration-asset.service.js +3 -3
  19. package/src/services/react-hydration-asset.service.ts +7 -4
  20. package/src/services/react-page-module.service.d.ts +3 -0
  21. package/src/services/react-page-module.service.js +20 -16
  22. package/src/services/react-page-module.service.ts +27 -17
  23. package/src/services/react-runtime-bundle.service.d.ts +12 -12
  24. package/src/services/react-runtime-bundle.service.js +98 -180
  25. package/src/services/react-runtime-bundle.service.ts +112 -211
  26. package/src/utils/client-graph-boundary-plugin.js +78 -1
  27. package/src/utils/client-graph-boundary-plugin.ts +122 -1
  28. package/src/utils/hydration-scripts.d.ts +18 -1
  29. package/src/utils/hydration-scripts.js +83 -32
  30. package/src/utils/hydration-scripts.ts +159 -38
  31. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  32. package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
  33. package/src/utils/react-dom-runtime-interop-plugin.ts +33 -0
  34. package/src/utils/react-mdx-loader-plugin.js +13 -5
  35. package/src/utils/react-mdx-loader-plugin.ts +28 -5
  36. package/src/utils/react-runtime-specifier-map.d.ts +6 -0
  37. package/src/utils/react-runtime-specifier-map.js +37 -0
  38. package/src/utils/react-runtime-specifier-map.ts +45 -0
  39. package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
  40. package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
  41. package/src/utils/use-sync-external-store-shim-plugin.ts +45 -0
@@ -9,6 +9,39 @@ function getHmrImportStatement(isMdx) {
9
9
  function getComponentType(isMdx) {
10
10
  return isMdx ? "MDX" : "React";
11
11
  }
12
+ function getDevPageRootCleanupScript() {
13
+ return `window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
14
+ window.__ECO_PAGES__.react = window.__ECO_PAGES__.react || {};
15
+ window.__ECO_PAGES__.react.cleanupPageRoot = () => {
16
+ const activeRoot = window.__ECO_PAGES__.react?.pageRoot || root;
17
+ if (!activeRoot) {
18
+ window.__ECO_PAGES__.react.pageRoot = null;
19
+ window.__ECO_PAGES__?.navigation?.releaseOwnership?.("react-router");
20
+ delete window.__ECO_PAGES__.page;
21
+ return;
22
+ }
23
+ window.__ECO_PAGES__.react.pageRoot = null;
24
+ window.__ECO_PAGES__?.navigation?.releaseOwnership?.("react-router");
25
+ delete window.__ECO_PAGES__.page;
26
+ root = null;
27
+ activeRoot.unmount();
28
+ };`;
29
+ }
30
+ function getProdPageRootCleanupScript() {
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
+ }
33
+ function getDevRouterBootstrapRegistrationScript() {
34
+ return `window.__ECO_PAGES__?.navigation?.register({
35
+ owner: "react-router",
36
+ cleanupBeforeHandoff: async () => {
37
+ window.__ECO_PAGES__?.react?.cleanupPageRoot?.();
38
+ }
39
+ });
40
+ window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router");`;
41
+ }
42
+ 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");';
44
+ }
12
45
  function createDevScriptWithRouter(options) {
13
46
  const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath } = options;
14
47
  const { components, getRouterProps } = router;
@@ -21,10 +54,13 @@ import { createElement } from "${reactImportPath}";
21
54
  import { ${components.router}, ${components.pageContent} } from "${routerImportPath}";
22
55
  ${getImportStatement(importPath, isMdx)}
23
56
 
24
- window.__ecopages_hmr_handlers__ = window.__ecopages_hmr_handlers__ || {};
25
- window.__ecopages_router_active__ = false;
26
- window.__ecopages_reload_current_page__ = null;
27
- let root = null;
57
+ window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
58
+ window.__ECO_PAGES__.hmrHandlers = window.__ECO_PAGES__.hmrHandlers || {};
59
+ window.__ECO_PAGES__.react = window.__ECO_PAGES__.react || {};
60
+ window.__ECO_PAGES__.react.pageRoot = window.__ECO_PAGES__.react.pageRoot || null;
61
+ let root = window.__ECO_PAGES__.react.pageRoot;
62
+ ${getDevPageRootCleanupScript()}
63
+ ${getDevRouterBootstrapRegistrationScript()}
28
64
 
29
65
  const getPageData = () => {
30
66
  const el = document.getElementById("__ECO_PAGE_DATA__");
@@ -36,7 +72,7 @@ const getPageData = () => {
36
72
 
37
73
  const props = getPageData();
38
74
 
39
- window.__ECO_PAGE__ = {
75
+ window.__ECO_PAGES__.page = {
40
76
  module: "${importPath}",
41
77
  props
42
78
  };
@@ -47,13 +83,18 @@ const createTree = (Component, props) => {
47
83
  };
48
84
 
49
85
  const mount = () => {
50
- root = hydrateRoot(document, createTree(Page, props), {
51
- onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
52
- });
53
- window.__ecopages_router_active__ = true;
54
- window.__ecopages_hmr_handlers__["${importPath}"] = async (newUrl) => {
55
- if (window.__ecopages_router_active__ && window.__ecopages_reload_current_page__) {
56
- await window.__ecopages_reload_current_page__();
86
+ if (window.__ECO_PAGES__.react?.pageRoot) {
87
+ 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;
94
+ }
95
+ 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" });
57
98
  console.log("[ecopages] ${getComponentType(isMdx)} component updated via router");
58
99
  return;
59
100
  }
@@ -82,8 +123,12 @@ import { hydrateRoot } from "${reactDomClientImportPath}";
82
123
  import { createElement } from "${reactImportPath}";
83
124
  ${getImportStatement(importPath, isMdx)}
84
125
 
85
- window.__ecopages_hmr_handlers__ = window.__ecopages_hmr_handlers__ || {};
86
- let root = null;
126
+ window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
127
+ window.__ECO_PAGES__.hmrHandlers = window.__ECO_PAGES__.hmrHandlers || {};
128
+ window.__ECO_PAGES__.react = window.__ECO_PAGES__.react || {};
129
+ window.__ECO_PAGES__.react.pageRoot = window.__ECO_PAGES__.react.pageRoot || null;
130
+ let root = window.__ECO_PAGES__.react.pageRoot;
131
+ ${getDevPageRootCleanupScript()}
87
132
 
88
133
  const getPageData = () => {
89
134
  const el = document.getElementById("__ECO_PAGE_DATA__");
@@ -95,7 +140,7 @@ const getPageData = () => {
95
140
 
96
141
  const props = getPageData();
97
142
 
98
- window.__ECO_PAGE__ = {
143
+ window.__ECO_PAGES__.page = {
99
144
  module: "${importPath}",
100
145
  props
101
146
  };
@@ -103,14 +148,21 @@ window.__ECO_PAGE__ = {
103
148
  const createTree = (Component, props) => {
104
149
  const Layout = Component.config?.layout;
105
150
  const pageElement = createElement(Component, props);
106
- return Layout ? createElement(Layout, null, pageElement) : pageElement;
151
+ const layoutProps = props?.locals ? { locals: props.locals } : null;
152
+ return Layout ? createElement(Layout, layoutProps, pageElement) : pageElement;
107
153
  };
108
154
 
109
155
  const mount = () => {
110
- root = hydrateRoot(document, createTree(Page, props), {
111
- onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
112
- });
113
- window.__ecopages_hmr_handlers__["${importPath}"] = async (newUrl) => {
156
+ if (window.__ECO_PAGES__.react?.pageRoot) {
157
+ root = window.__ECO_PAGES__.react.pageRoot;
158
+ root.render(createTree(Page, props));
159
+ } else {
160
+ root = hydrateRoot(document.body, createTree(Page, props), {
161
+ onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
162
+ });
163
+ window.__ECO_PAGES__.react.pageRoot = root;
164
+ }
165
+ window.__ECO_PAGES__.hmrHandlers["${importPath}"] = async (newUrl) => {
114
166
  try {
115
167
  const newModule = await import(newUrl);
116
168
  ${getHmrImportStatement(isMdx)}
@@ -136,16 +188,16 @@ function createProdScriptWithRouter(options) {
136
188
  throw new Error("routerImportPath is required when router adapter is configured");
137
189
  }
138
190
  if (isMdx) {
139
- 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 gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>ce(R,${getRouterProps("C", "p")},ce(PC));const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
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()`;
140
192
  }
141
- 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 gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>ce(R,${getRouterProps("C", "p")},ce(PC));const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
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()`;
142
194
  }
143
195
  function createProdScriptWithoutRouter(options) {
144
196
  const { importPath, isMdx, reactImportPath, reactDomClientImportPath } = options;
145
197
  if (isMdx) {
146
- 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 gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);return L?ce(L,null,pe):pe};const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
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()`;
147
199
  }
148
- return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import P from"${importPath}";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_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);return L?ce(L,null,pe):pe};const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
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()`;
149
201
  }
150
202
  function createHydrationScript(options) {
151
203
  const { isDevelopment, router } = options;
@@ -156,10 +208,8 @@ function createHydrationScript(options) {
156
208
  }
157
209
  function createIslandHydrationScript(options) {
158
210
  const targetSelector = JSON.stringify(options.targetSelector);
159
- const serializedProps = JSON.stringify(options.props ?? {});
160
211
  const componentRef = JSON.stringify(options.componentRef ?? "");
161
212
  const componentFile = JSON.stringify(options.componentFile ?? "");
162
- const mountedAttribute = "data-eco-react-mounted";
163
213
  if (options.isDevelopment) {
164
214
  return `
165
215
  import { createRoot } from "${options.reactDomClientImportPath}";
@@ -197,16 +247,17 @@ const resolveComponent = () => {
197
247
  const mount = () => {
198
248
  const target = document.querySelector(${targetSelector});
199
249
  const Component = resolveComponent();
200
- if (!target || !Component || target.hasAttribute("${mountedAttribute}")) {
250
+ if (!target || !Component) {
201
251
  return;
202
252
  }
203
- const props = ${serializedProps};
204
- target.setAttribute("${mountedAttribute}", "true");
205
- const root = createRoot(target);
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);
206
258
  root.render(createElement(Component, props));
207
259
  };
208
260
 
209
- document.addEventListener("eco:after-swap", mount);
210
261
  if (document.readyState === "loading") {
211
262
  document.addEventListener("DOMContentLoaded", mount, { once: true });
212
263
  } else {
@@ -214,7 +265,7 @@ if (document.readyState === "loading") {
214
265
  }
215
266
  `.trim();
216
267
  }
217
- 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||t.hasAttribute("${mountedAttribute}"))return;const p=${serializedProps};t.setAttribute("${mountedAttribute}","true");cr(t).render(ce(c,p))};document.addEventListener("eco:after-swap",m);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m,{once:true}):m()`;
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()`;
218
269
  }
219
270
  export {
220
271
  createHydrationScript,
@@ -72,10 +72,77 @@ function getComponentType(isMdx: boolean): string {
72
72
  return isMdx ? 'MDX' : 'React';
73
73
  }
74
74
 
75
+ /**
76
+ * Generates the development cleanup hook for the page-level React root.
77
+ *
78
+ * Why this exists:
79
+ * browser-router and React-router hand document ownership back and forth. The
80
+ * client runtime therefore needs a single cleanup entry point that can unmount
81
+ * the active React root, clear ownership flags, and discard serialized page data
82
+ * before a non-React renderer or a fresh React bootstrap takes over.
83
+ *
84
+ * Why it is emitted as a string:
85
+ * this module generates browser bootstraps, so the cleanup behavior must be
86
+ * embedded directly into the emitted module source.
87
+ */
88
+ function getDevPageRootCleanupScript(): string {
89
+ return `window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
90
+ window.__ECO_PAGES__.react = window.__ECO_PAGES__.react || {};
91
+ window.__ECO_PAGES__.react.cleanupPageRoot = () => {
92
+ const activeRoot = window.__ECO_PAGES__.react?.pageRoot || root;
93
+ if (!activeRoot) {
94
+ window.__ECO_PAGES__.react.pageRoot = null;
95
+ window.__ECO_PAGES__?.navigation?.releaseOwnership?.("react-router");
96
+ delete window.__ECO_PAGES__.page;
97
+ return;
98
+ }
99
+ window.__ECO_PAGES__.react.pageRoot = null;
100
+ window.__ECO_PAGES__?.navigation?.releaseOwnership?.("react-router");
101
+ delete window.__ECO_PAGES__.page;
102
+ root = null;
103
+ activeRoot.unmount();
104
+ };`;
105
+ }
106
+
107
+ /**
108
+ * Minified production variant of the page-root cleanup hook.
109
+ *
110
+ * It mirrors the development behavior exactly so navigation ownership semantics
111
+ * remain identical across environments while keeping the emitted payload small.
112
+ */
113
+ function getProdPageRootCleanupScript(): string {
114
+ 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()};';
115
+ }
116
+
117
+ function getDevRouterBootstrapRegistrationScript(): string {
118
+ return `window.__ECO_PAGES__?.navigation?.register({
119
+ owner: "react-router",
120
+ cleanupBeforeHandoff: async () => {
121
+ window.__ECO_PAGES__?.react?.cleanupPageRoot?.();
122
+ }
123
+ });
124
+ window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router");`;
125
+ }
126
+
127
+ function getProdRouterBootstrapRegistrationScript(): string {
128
+ return 'window.__ECO_PAGES__?.navigation?.register({owner:"react-router",cleanupBeforeHandoff:async()=>{window.__ECO_PAGES__?.react?.cleanupPageRoot?.()}});window.__ECO_PAGES__?.navigation?.claimOwnership?.("react-router");';
129
+ }
130
+
75
131
  /**
76
132
  * Creates development hydration script with router support.
77
- * Includes HMR handlers for hot module replacement.
78
- * Layout is NOT applied here since PageContent handles it.
133
+ *
134
+ * Why this branch exists:
135
+ * router-managed React pages keep a long-lived root across client-side
136
+ * navigations. The bootstrap therefore hydrates once, reuses that root for
137
+ * future renders, exposes cleanup for ownership handoff, and lets the router
138
+ * adapter reconstruct page content instead of rebuilding layout trees here.
139
+ *
140
+ * How it works:
141
+ * - imports the page module and router runtime pieces
142
+ * - reads serialized page props from the server payload
143
+ * - hydrates or re-renders the shared page root
144
+ * - installs HMR handlers that either ask the router to reload the active page
145
+ * or patch the current root directly when the router is inactive
79
146
  */
80
147
  function createDevScriptWithRouter(options: HydrationScriptOptions): string {
81
148
  const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath } = options;
@@ -90,10 +157,13 @@ import { createElement } from "${reactImportPath}";
90
157
  import { ${components.router}, ${components.pageContent} } from "${routerImportPath}";
91
158
  ${getImportStatement(importPath, isMdx)}
92
159
 
93
- window.__ecopages_hmr_handlers__ = window.__ecopages_hmr_handlers__ || {};
94
- window.__ecopages_router_active__ = false;
95
- window.__ecopages_reload_current_page__ = null;
96
- let root = null;
160
+ window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
161
+ window.__ECO_PAGES__.hmrHandlers = window.__ECO_PAGES__.hmrHandlers || {};
162
+ window.__ECO_PAGES__.react = window.__ECO_PAGES__.react || {};
163
+ window.__ECO_PAGES__.react.pageRoot = window.__ECO_PAGES__.react.pageRoot || null;
164
+ let root = window.__ECO_PAGES__.react.pageRoot;
165
+ ${getDevPageRootCleanupScript()}
166
+ ${getDevRouterBootstrapRegistrationScript()}
97
167
 
98
168
  const getPageData = () => {
99
169
  const el = document.getElementById("__ECO_PAGE_DATA__");
@@ -105,7 +175,7 @@ const getPageData = () => {
105
175
 
106
176
  const props = getPageData();
107
177
 
108
- window.__ECO_PAGE__ = {
178
+ window.__ECO_PAGES__.page = {
109
179
  module: "${importPath}",
110
180
  props
111
181
  };
@@ -116,13 +186,18 @@ const createTree = (Component, props) => {
116
186
  };
117
187
 
118
188
  const mount = () => {
119
- root = hydrateRoot(document, createTree(Page, props), {
120
- onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
121
- });
122
- window.__ecopages_router_active__ = true;
123
- window.__ecopages_hmr_handlers__["${importPath}"] = async (newUrl) => {
124
- if (window.__ecopages_router_active__ && window.__ecopages_reload_current_page__) {
125
- await window.__ecopages_reload_current_page__();
189
+ if (window.__ECO_PAGES__.react?.pageRoot) {
190
+ root = window.__ECO_PAGES__.react.pageRoot;
191
+ root.render(createTree(Page, props));
192
+ } else {
193
+ root = hydrateRoot(document.body, createTree(Page, props), {
194
+ onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
195
+ });
196
+ window.__ECO_PAGES__.react.pageRoot = root;
197
+ }
198
+ window.__ECO_PAGES__.hmrHandlers["${importPath}"] = async (newUrl) => {
199
+ if (window.__ECO_PAGES__?.navigation?.getOwnerState().owner === "react-router") {
200
+ await window.__ECO_PAGES__?.navigation?.reloadCurrentPage?.({ clearCache: false, source: "react-router" });
126
201
  console.log("[ecopages] ${getComponentType(isMdx)} component updated via router");
127
202
  return;
128
203
  }
@@ -147,7 +222,17 @@ if (document.readyState === "loading") {
147
222
 
148
223
  /**
149
224
  * Creates development hydration script without router.
150
- * Includes HMR handlers for hot module replacement.
225
+ *
226
+ * Why this branch exists:
227
+ * non-router React pages rebuild their layout tree directly from the page
228
+ * module on the client. That means the bootstrap must recreate the page and its
229
+ * optional layout so hydration matches the server HTML exactly.
230
+ *
231
+ * How it works:
232
+ * - imports the page module directly
233
+ * - reconstructs the layout wrapper from `Page.config?.layout`
234
+ * - hydrates a single document root
235
+ * - patches that root during HMR without involving a router adapter
151
236
  */
152
237
  function createDevScriptWithoutRouter(options: HydrationScriptOptions): string {
153
238
  const { importPath, isMdx, reactImportPath, reactDomClientImportPath } = options;
@@ -157,8 +242,12 @@ import { hydrateRoot } from "${reactDomClientImportPath}";
157
242
  import { createElement } from "${reactImportPath}";
158
243
  ${getImportStatement(importPath, isMdx)}
159
244
 
160
- window.__ecopages_hmr_handlers__ = window.__ecopages_hmr_handlers__ || {};
161
- let root = null;
245
+ window.__ECO_PAGES__ = window.__ECO_PAGES__ || {};
246
+ window.__ECO_PAGES__.hmrHandlers = window.__ECO_PAGES__.hmrHandlers || {};
247
+ window.__ECO_PAGES__.react = window.__ECO_PAGES__.react || {};
248
+ window.__ECO_PAGES__.react.pageRoot = window.__ECO_PAGES__.react.pageRoot || null;
249
+ let root = window.__ECO_PAGES__.react.pageRoot;
250
+ ${getDevPageRootCleanupScript()}
162
251
 
163
252
  const getPageData = () => {
164
253
  const el = document.getElementById("__ECO_PAGE_DATA__");
@@ -170,7 +259,7 @@ const getPageData = () => {
170
259
 
171
260
  const props = getPageData();
172
261
 
173
- window.__ECO_PAGE__ = {
262
+ window.__ECO_PAGES__.page = {
174
263
  module: "${importPath}",
175
264
  props
176
265
  };
@@ -178,14 +267,21 @@ window.__ECO_PAGE__ = {
178
267
  const createTree = (Component, props) => {
179
268
  const Layout = Component.config?.layout;
180
269
  const pageElement = createElement(Component, props);
181
- return Layout ? createElement(Layout, null, pageElement) : pageElement;
270
+ const layoutProps = props?.locals ? { locals: props.locals } : null;
271
+ return Layout ? createElement(Layout, layoutProps, pageElement) : pageElement;
182
272
  };
183
273
 
184
274
  const mount = () => {
185
- root = hydrateRoot(document, createTree(Page, props), {
186
- onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
187
- });
188
- window.__ecopages_hmr_handlers__["${importPath}"] = async (newUrl) => {
275
+ if (window.__ECO_PAGES__.react?.pageRoot) {
276
+ root = window.__ECO_PAGES__.react.pageRoot;
277
+ root.render(createTree(Page, props));
278
+ } else {
279
+ root = hydrateRoot(document.body, createTree(Page, props), {
280
+ onRecoverableError: (err) => console.warn("[ecopages] Hydration error:", err)
281
+ });
282
+ window.__ECO_PAGES__.react.pageRoot = root;
283
+ }
284
+ window.__ECO_PAGES__.hmrHandlers["${importPath}"] = async (newUrl) => {
189
285
  try {
190
286
  const newModule = await import(newUrl);
191
287
  ${getHmrImportStatement(isMdx)}
@@ -207,7 +303,10 @@ if (document.readyState === "loading") {
207
303
 
208
304
  /**
209
305
  * Creates minified production hydration script with router support.
210
- * Layout is NOT applied here since PageContent handles it.
306
+ *
307
+ * This is the production counterpart to `createDevScriptWithRouter()`. The
308
+ * ownership and hydration behavior is the same; only the emitted source is
309
+ * compressed for delivery.
211
310
  */
212
311
  function createProdScriptWithRouter(options: HydrationScriptOptions): string {
213
312
  const { importPath, isMdx, router, reactImportPath, reactDomClientImportPath, routerImportPath } = options;
@@ -217,28 +316,45 @@ function createProdScriptWithRouter(options: HydrationScriptOptions): string {
217
316
  }
218
317
 
219
318
  if (isMdx) {
220
- 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 gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>ce(R,${getRouterProps('C', 'p')},ce(PC));const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
319
+ 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()`;
221
320
  }
222
321
 
223
- 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 gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>ce(R,${getRouterProps('C', 'p')},ce(PC));const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
322
+ 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()`;
224
323
  }
225
324
 
226
325
  /**
227
- * Creates minified production hydration script without router.
326
+ * Creates the minified production hydration script for non-router pages.
327
+ *
328
+ * In this mode the page module is responsible for reconstructing its own layout
329
+ * tree. If the server serialized request `locals`, the script forwards those
330
+ * values to the layout as well as the page so hydration matches the server HTML.
331
+ * The runtime semantics mirror the development path; only the emitted source is
332
+ * condensed.
228
333
  */
229
334
  function createProdScriptWithoutRouter(options: HydrationScriptOptions): string {
230
335
  const { importPath, isMdx, reactImportPath, reactDomClientImportPath } = options;
231
336
 
232
337
  if (isMdx) {
233
- 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 gd=()=>{const e=document.getElementById("__ECO_PAGE_DATA__");if(e?.textContent){try{return JSON.parse(e.textContent)}catch{}}return{}};const pr=gd();window.__ECO_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);return L?ce(L,null,pe):pe};const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
338
+ 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()`;
234
339
  }
235
340
 
236
- return `import{hydrateRoot as hr}from"${reactDomClientImportPath}";import{createElement as ce}from"${reactImportPath}";import P from"${importPath}";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_PAGE__={module:"${importPath}",props:pr};const ct=(C,p)=>{const L=C.config?.layout;const pe=ce(C,p);return L?ce(L,null,pe):pe};const m=()=>hr(document,ct(P,pr),{onRecoverableError:(e)=>console.warn("[ecopages] Hydration error:",e)});document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m):m()`;
341
+ 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()`;
237
342
  }
238
343
 
239
344
  /**
240
345
  * Creates a hydration script for client-side React hydration.
241
- * Generates appropriate script based on environment and router configuration.
346
+ *
347
+ * Why this dispatcher exists:
348
+ * the runtime matrix is small but behaviorally different across development vs
349
+ * production and router vs non-router pages. Keeping that branch here preserves
350
+ * a compact public API while allowing each emitted script to stay focused.
351
+ *
352
+ * Selection rules:
353
+ * - development uses readable scripts with HMR hooks
354
+ * - production uses minified equivalents
355
+ * - router presence decides whether page updates flow through the router runtime
356
+ * or rebuild directly from the page module
357
+ *
242
358
  * @param options - Configuration options for script generation
243
359
  * @returns The generated hydration script as a string
244
360
  */
@@ -267,17 +383,21 @@ export function createHydrationScript(options: HydrationScriptOptions): string {
267
383
  * - resolves the component export by metadata (`componentRef`, `componentFile`)
268
384
  * before falling back to default/first function export
269
385
  * - selects island root using `targetSelector`
386
+ * - replaces the SSR host with a dedicated client-owned container
270
387
  * - creates a fresh React root and renders with serialized `props`
271
388
  *
389
+ * Why it remounts instead of hydrating:
390
+ * island SSR intentionally avoids synthetic wrapper elements. The runtime swaps
391
+ * the authored SSR node for a dedicated client-owned container before mounting
392
+ * so the server markup stays clean while the client still gets a stable root.
393
+ *
272
394
  * @param options Island script generation options.
273
395
  * @returns Browser-executable JavaScript module source.
274
396
  */
275
397
  export function createIslandHydrationScript(options: IslandHydrationScriptOptions): string {
276
398
  const targetSelector = JSON.stringify(options.targetSelector);
277
- const serializedProps = JSON.stringify(options.props ?? {});
278
399
  const componentRef = JSON.stringify(options.componentRef ?? '');
279
400
  const componentFile = JSON.stringify(options.componentFile ?? '');
280
- const mountedAttribute = 'data-eco-react-mounted';
281
401
 
282
402
  if (options.isDevelopment) {
283
403
  return `
@@ -316,16 +436,17 @@ const resolveComponent = () => {
316
436
  const mount = () => {
317
437
  const target = document.querySelector(${targetSelector});
318
438
  const Component = resolveComponent();
319
- if (!target || !Component || target.hasAttribute("${mountedAttribute}")) {
439
+ if (!target || !Component) {
320
440
  return;
321
441
  }
322
- const props = ${serializedProps};
323
- target.setAttribute("${mountedAttribute}", "true");
324
- const root = createRoot(target);
442
+ const props = JSON.parse(atob(target.getAttribute("data-eco-props") || "e30="));
443
+ const container = document.createElement("eco-island");
444
+ container.style.display = "block";
445
+ target.replaceWith(container);
446
+ const root = createRoot(container);
325
447
  root.render(createElement(Component, props));
326
448
  };
327
449
 
328
- document.addEventListener("eco:after-swap", mount);
329
450
  if (document.readyState === "loading") {
330
451
  document.addEventListener("DOMContentLoaded", mount, { once: true });
331
452
  } else {
@@ -334,5 +455,5 @@ if (document.readyState === "loading") {
334
455
  `.trim();
335
456
  }
336
457
 
337
- 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||t.hasAttribute("${mountedAttribute}"))return;const p=${serializedProps};t.setAttribute("${mountedAttribute}","true");cr(t).render(ce(c,p))};document.addEventListener("eco:after-swap",m);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",m,{once:true}):m()`;
458
+ 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()`;
338
459
  }
@@ -0,0 +1,5 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
+ export declare function createReactDomRuntimeInteropPlugin(options?: {
3
+ name?: string;
4
+ reactSpecifier?: string;
5
+ }): EcoBuildPlugin;
@@ -0,0 +1,29 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ function createReactDomRuntimeInteropPlugin(options) {
4
+ const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
5
+ const reactRequirePattern = /\brequire\((['"])react\1\)/g;
6
+ const reactSpecifier = options?.reactSpecifier ?? "react";
7
+ return {
8
+ name: options?.name ?? "react-dom-runtime-interop",
9
+ setup(build) {
10
+ build.onLoad({ filter: reactDomFileFilter }, (args) => {
11
+ const content = fs.readFileSync(args.path, "utf-8");
12
+ if (!reactRequirePattern.test(content)) {
13
+ return void 0;
14
+ }
15
+ reactRequirePattern.lastIndex = 0;
16
+ const rewritten = content.replace(reactRequirePattern, "__ecopages_react_runtime");
17
+ return {
18
+ contents: `import * as __ecopages_react_runtime from '${reactSpecifier}';
19
+ ${rewritten}`,
20
+ loader: "js",
21
+ resolveDir: path.dirname(args.path)
22
+ };
23
+ });
24
+ }
25
+ };
26
+ }
27
+ export {
28
+ createReactDomRuntimeInteropPlugin
29
+ };
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
4
+
5
+ export function createReactDomRuntimeInteropPlugin(options?: {
6
+ name?: string;
7
+ reactSpecifier?: string;
8
+ }): EcoBuildPlugin {
9
+ const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
10
+ const reactRequirePattern = /\brequire\((['"])react\1\)/g;
11
+ const reactSpecifier = options?.reactSpecifier ?? 'react';
12
+
13
+ return {
14
+ name: options?.name ?? 'react-dom-runtime-interop',
15
+ setup(build) {
16
+ build.onLoad({ filter: reactDomFileFilter }, (args) => {
17
+ const content = fs.readFileSync(args.path, 'utf-8');
18
+ if (!reactRequirePattern.test(content)) {
19
+ return undefined;
20
+ }
21
+
22
+ reactRequirePattern.lastIndex = 0;
23
+ const rewritten = content.replace(reactRequirePattern, '__ecopages_react_runtime');
24
+
25
+ return {
26
+ contents: `import * as __ecopages_react_runtime from '${reactSpecifier}';\n${rewritten}`,
27
+ loader: 'js',
28
+ resolveDir: path.dirname(args.path),
29
+ };
30
+ });
31
+ },
32
+ };
33
+ }