@ecopages/react 0.2.0-alpha.1 → 0.2.0-alpha.10

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 (47) hide show
  1. package/CHANGELOG.md +11 -46
  2. package/README.md +143 -17
  3. package/package.json +3 -3
  4. package/src/react-hmr-strategy.d.ts +22 -19
  5. package/src/react-hmr-strategy.js +57 -109
  6. package/src/react-renderer.d.ts +130 -11
  7. package/src/react-renderer.js +368 -64
  8. package/src/react.plugin.d.ts +17 -5
  9. package/src/react.plugin.js +44 -13
  10. package/src/router-adapter.d.ts +2 -2
  11. package/src/services/react-bundle.service.d.ts +2 -25
  12. package/src/services/react-bundle.service.js +21 -91
  13. package/src/services/react-hydration-asset.service.js +3 -3
  14. package/src/services/react-page-module.service.d.ts +3 -0
  15. package/src/services/react-page-module.service.js +20 -16
  16. package/src/services/react-runtime-bundle.service.d.ts +12 -12
  17. package/src/services/react-runtime-bundle.service.js +98 -180
  18. package/src/utils/client-graph-boundary-plugin.js +147 -9
  19. package/src/utils/hydration-scripts.d.ts +18 -1
  20. package/src/utils/hydration-scripts.js +83 -32
  21. package/src/utils/reachability-analyzer.d.ts +12 -1
  22. package/src/utils/reachability-analyzer.js +101 -5
  23. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  24. package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
  25. package/src/utils/react-mdx-loader-plugin.js +13 -5
  26. package/src/utils/react-runtime-specifier-map.d.ts +6 -0
  27. package/src/utils/react-runtime-specifier-map.js +37 -0
  28. package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
  29. package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
  30. package/src/react-hmr-strategy.ts +0 -444
  31. package/src/react-renderer.ts +0 -403
  32. package/src/react.plugin.ts +0 -241
  33. package/src/router-adapter.ts +0 -95
  34. package/src/services/react-bundle.service.ts +0 -212
  35. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  36. package/src/services/react-hydration-asset.service.ts +0 -260
  37. package/src/services/react-page-module.service.ts +0 -214
  38. package/src/services/react-runtime-bundle.service.ts +0 -271
  39. package/src/utils/client-graph-boundary-plugin.ts +0 -590
  40. package/src/utils/client-only.ts +0 -27
  41. package/src/utils/declared-modules.ts +0 -99
  42. package/src/utils/dynamic.ts +0 -27
  43. package/src/utils/hmr-scripts.ts +0 -47
  44. package/src/utils/html-boundary.ts +0 -66
  45. package/src/utils/hydration-scripts.ts +0 -338
  46. package/src/utils/reachability-analyzer.ts +0 -440
  47. package/src/utils/react-mdx-loader-plugin.ts +0 -40
@@ -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,
@@ -42,6 +42,15 @@ export type ReachabilityResult = {
42
42
  */
43
43
  analyzed: boolean;
44
44
  };
45
+ /**
46
+ * Optional export filter supplied by the client graph boundary when a local
47
+ * module is imported through a narrower named-export surface.
48
+ *
49
+ * `'*'` means the whole module namespace is considered reachable, while a
50
+ * `Set` restricts analysis to the named exports that are actually requested by
51
+ * downstream client-reachable modules.
52
+ */
53
+ type ExplicitlyRequestedExports = Set<string> | '*';
45
54
  /**
46
55
  * Analyzes a module using Oxc AST and extracts a strict reachability graph
47
56
  * starting from client roots (`render`, `errorBoundary`, `loadingFallback` of `eco.page` or `eco.component`).
@@ -50,6 +59,8 @@ export type ReachabilityResult = {
50
59
  * @param filename - Absolute or relative path to the module file.
51
60
  * @param program - Optional pre-parsed Oxc program AST. When supplied, the
52
61
  * internal `parseSync` call is skipped entirely (avoids double-parsing).
62
+ * @param explicitlyRequestedExports - Optional named export filter propagated
63
+ * from a downstream importer when this module is only partially reachable.
53
64
  */
54
- export declare function analyzeReachability(source: string, filename: string, program?: ReturnType<typeof parseSync>['program']): ReachabilityResult;
65
+ export declare function analyzeReachability(source: string, filename: string, program?: ReturnType<typeof parseSync>['program'], explicitlyRequestedExports?: ExplicitlyRequestedExports): ReachabilityResult;
55
66
  export {};
@@ -7,7 +7,7 @@ function parserLanguageForFile(filename) {
7
7
  if (extension === ".jsx") return "jsx";
8
8
  return "js";
9
9
  }
10
- function analyzeReachability(source, filename, program) {
10
+ function analyzeReachability(source, filename, program, explicitlyRequestedExports) {
11
11
  let resolvedProgram;
12
12
  if (program) {
13
13
  resolvedProgram = program;
@@ -114,12 +114,86 @@ function analyzeReachability(source, filename, program) {
114
114
  potentialClientRoots.push(node);
115
115
  }
116
116
  }
117
+ function getExportedName(specifier) {
118
+ if (specifier?.exported?.type === "Identifier") return specifier.exported.name;
119
+ if (typeof specifier?.exported?.value === "string") return specifier.exported.value;
120
+ if (specifier?.local?.type === "Identifier") return specifier.local.name;
121
+ if (typeof specifier?.local?.value === "string") return specifier.local.value;
122
+ return void 0;
123
+ }
124
+ function getReexportedImportName(specifier) {
125
+ if (specifier?.local?.type === "Identifier") return specifier.local.name;
126
+ if (typeof specifier?.local?.value === "string") return specifier.local.value;
127
+ if (specifier?.imported?.type === "Identifier") return specifier.imported.name;
128
+ if (typeof specifier?.imported?.value === "string") return specifier.imported.value;
129
+ return getExportedName(specifier);
130
+ }
131
+ function getLocalExportName(specifier) {
132
+ if (specifier?.local?.type === "Identifier") return specifier.local.name;
133
+ if (typeof specifier?.local?.value === "string") return specifier.local.value;
134
+ return void 0;
135
+ }
136
+ function isExplicitlyRequestedExport(name) {
137
+ if (explicitlyRequestedExports === "*") return true;
138
+ return explicitlyRequestedExports?.has(name) ?? false;
139
+ }
117
140
  let isFallbackRoots = false;
118
141
  if (potentialClientRoots.length === 0) {
119
- isFallbackRoots = true;
120
- for (const node of resolvedProgram.body) {
121
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") {
122
- potentialClientRoots.push(node);
142
+ if (explicitlyRequestedExports) {
143
+ for (const node of resolvedProgram.body) {
144
+ if (node.type === "ExportNamedDeclaration") {
145
+ const exportNode = node;
146
+ if (exportNode.source && exportNode.specifiers?.length) {
147
+ const hasRequestedReexport = exportNode.specifiers.some((specifier) => {
148
+ const exportedName = getExportedName(specifier);
149
+ return exportedName ? isExplicitlyRequestedExport(exportedName) : false;
150
+ });
151
+ if (hasRequestedReexport) {
152
+ potentialClientRoots.push(node);
153
+ }
154
+ continue;
155
+ }
156
+ if (exportNode.declaration?.type === "FunctionDeclaration" || exportNode.declaration?.type === "ClassDeclaration") {
157
+ const declarationName = exportNode.declaration.id?.name;
158
+ if (declarationName && isExplicitlyRequestedExport(declarationName)) {
159
+ potentialClientRoots.push(node);
160
+ }
161
+ continue;
162
+ }
163
+ if (exportNode.declaration?.type === "VariableDeclaration") {
164
+ const hasRequestedDeclaration = exportNode.declaration.declarations.some(
165
+ (declaration) => declaration.id?.type === "Identifier" && isExplicitlyRequestedExport(declaration.id.name)
166
+ );
167
+ if (hasRequestedDeclaration) {
168
+ potentialClientRoots.push(node);
169
+ }
170
+ continue;
171
+ }
172
+ if (exportNode.specifiers?.length) {
173
+ const hasRequestedSpecifier = exportNode.specifiers.some((specifier) => {
174
+ const exportedName = getExportedName(specifier);
175
+ return exportedName ? isExplicitlyRequestedExport(exportedName) : false;
176
+ });
177
+ if (hasRequestedSpecifier) {
178
+ potentialClientRoots.push(node);
179
+ }
180
+ }
181
+ } else if (node.type === "ExportDefaultDeclaration") {
182
+ if (isExplicitlyRequestedExport("default")) {
183
+ potentialClientRoots.push(node);
184
+ }
185
+ } else if (node.type === "ExportAllDeclaration") {
186
+ if (explicitlyRequestedExports === "*") {
187
+ potentialClientRoots.push(node);
188
+ }
189
+ }
190
+ }
191
+ } else {
192
+ isFallbackRoots = true;
193
+ for (const node of resolvedProgram.body) {
194
+ if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") {
195
+ potentialClientRoots.push(node);
196
+ }
123
197
  }
124
198
  }
125
199
  }
@@ -167,6 +241,28 @@ function analyzeReachability(source, filename, program) {
167
241
  markImportReachable(node.source.value, "*");
168
242
  return;
169
243
  }
244
+ if (node.type === "ExportNamedDeclaration" && typeof node.source?.value === "string") {
245
+ for (const specifier of node.specifiers ?? []) {
246
+ const importedName = getReexportedImportName(specifier);
247
+ if (importedName) {
248
+ markImportReachable(node.source.value, importedName);
249
+ }
250
+ }
251
+ return;
252
+ }
253
+ if (node.type === "ExportNamedDeclaration" && !node.source && explicitlyRequestedExports && node.specifiers?.length) {
254
+ for (const specifier of node.specifiers) {
255
+ const exportedName = getExportedName(specifier);
256
+ if (!exportedName || !isExplicitlyRequestedExport(exportedName)) {
257
+ continue;
258
+ }
259
+ const localName = getLocalExportName(specifier);
260
+ if (localName && !currentScope.has(localName)) {
261
+ checkIdentifier(localName);
262
+ }
263
+ }
264
+ return;
265
+ }
170
266
  if (node.type === "Identifier" || node.type === "JSXIdentifier" && /^[A-Z]/.test(node.name)) {
171
267
  if (!currentScope.has(node.name)) {
172
268
  checkIdentifier(node.name);
@@ -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
+ };
@@ -1,11 +1,18 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { compile } from "@mdx-js/mdx";
4
- import { SourceMapGenerator } from "source-map";
4
+ import sourceMap from "source-map";
5
5
  import { VFile } from "vfile";
6
+ function resolveCompileFormat(filePath, compilerOptions) {
7
+ const configuredFormat = compilerOptions?.format;
8
+ if (configuredFormat && configuredFormat !== "detect") {
9
+ return configuredFormat;
10
+ }
11
+ return path.extname(filePath).toLowerCase() === ".md" ? "mdx" : configuredFormat;
12
+ }
6
13
  function createReactMdxLoaderPlugin(compilerOptions) {
7
14
  const mdxExtensions = compilerOptions?.mdxExtensions ?? [".mdx"];
8
- const mdExtensions = compilerOptions?.mdExtensions ?? [".md"];
15
+ const mdExtensions = compilerOptions?.mdExtensions ?? [];
9
16
  const allExtensions = [...mdxExtensions, ...mdExtensions];
10
17
  const escapedExts = allExtensions.map((ext) => ext.replace(".", "\\."));
11
18
  const filter = new RegExp(`(${escapedExts.join("|")})(\\?.*)?$`);
@@ -18,13 +25,14 @@ function createReactMdxLoaderPlugin(compilerOptions) {
18
25
  const file = new VFile({ path: filePath, value: source });
19
26
  const compiled = await compile(file, {
20
27
  ...compilerOptions,
21
- SourceMapGenerator
28
+ format: resolveCompileFormat(filePath, compilerOptions),
29
+ SourceMapGenerator: sourceMap.SourceMapGenerator
22
30
  });
23
- const sourceMap = compiled.map ? `
31
+ const inlineSourceMap = compiled.map ? `
24
32
  //# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(compiled.map)).toString("base64")}
25
33
  ` : "";
26
34
  return {
27
- contents: `${String(compiled.value)}${sourceMap}`,
35
+ contents: `${String(compiled.value)}${inlineSourceMap}`,
28
36
  loader: compilerOptions?.jsx ? "jsx" : "js",
29
37
  resolveDir: path.dirname(args.path)
30
38
  };
@@ -0,0 +1,6 @@
1
+ import type { ReactRouterAdapter } from '../router-adapter.js';
2
+ import type { ReactRuntimeImports } from '../services/react-runtime-bundle.service.js';
3
+ 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 getReactRuntimeExternalSpecifiers(): string[];
6
+ export declare function getReactClientGraphAllowSpecifiers(runtimeSpecifiers: Iterable<string>, routerAdapter?: ReactRouterAdapter): string[];
@@ -0,0 +1,37 @@
1
+ const REACT_RUNTIME_SPECIFIERS = [
2
+ "react",
3
+ "react-dom",
4
+ "react/jsx-runtime",
5
+ "react/jsx-dev-runtime",
6
+ "react-dom/client"
7
+ ];
8
+ function buildReactRuntimeSpecifierMap(runtimeImports, routerAdapter) {
9
+ const map = {
10
+ react: runtimeImports.react,
11
+ "react/jsx-runtime": runtimeImports.reactJsxRuntime,
12
+ "react/jsx-dev-runtime": runtimeImports.reactJsxDevRuntime,
13
+ "react-dom": runtimeImports.reactDom,
14
+ "react-dom/client": runtimeImports.reactDomClient
15
+ };
16
+ if (routerAdapter && runtimeImports.router) {
17
+ map[routerAdapter.importMapKey] = runtimeImports.router;
18
+ }
19
+ return map;
20
+ }
21
+ function getReactRuntimeExternalSpecifiers() {
22
+ return [...REACT_RUNTIME_SPECIFIERS];
23
+ }
24
+ function getReactClientGraphAllowSpecifiers(runtimeSpecifiers, routerAdapter) {
25
+ return [
26
+ "@ecopages/core",
27
+ ...REACT_RUNTIME_SPECIFIERS,
28
+ ...routerAdapter ? [routerAdapter.importMapKey] : [],
29
+ ...Array.from(runtimeSpecifiers)
30
+ ];
31
+ }
32
+ export {
33
+ REACT_RUNTIME_SPECIFIERS,
34
+ buildReactRuntimeSpecifierMap,
35
+ getReactClientGraphAllowSpecifiers,
36
+ getReactRuntimeExternalSpecifiers
37
+ };
@@ -0,0 +1,5 @@
1
+ import type { EcoBuildPlugin } from '@ecopages/core/build/build-types';
2
+ export declare function createUseSyncExternalStoreShimPlugin(options?: {
3
+ name?: string;
4
+ namespace?: string;
5
+ }): EcoBuildPlugin;
@@ -0,0 +1,41 @@
1
+ function createUseSyncExternalStoreShimPlugin(options) {
2
+ const namespace = options?.namespace ?? "ecopages-react-use-sync-external-store-shim";
3
+ return {
4
+ name: options?.name ?? "react-use-sync-external-store-shim",
5
+ setup(build) {
6
+ build.onResolve({ filter: /^use-sync-external-store\/shim(?:\/index\.js)?$/ }, () => ({
7
+ path: "use-sync-external-store/shim",
8
+ namespace
9
+ }));
10
+ build.onLoad({ filter: /^use-sync-external-store\/shim$/, namespace }, () => ({
11
+ contents: "export { useSyncExternalStore } from 'react';",
12
+ loader: "js"
13
+ }));
14
+ build.onLoad({ filter: /[\\/]use-sync-external-store[\\/]shim[\\/]index\.js$/ }, () => ({
15
+ contents: "export { useSyncExternalStore } from 'react';",
16
+ loader: "js"
17
+ }));
18
+ build.onLoad(
19
+ {
20
+ filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.development\.js$/
21
+ },
22
+ () => ({
23
+ contents: "export { useSyncExternalStore } from 'react';",
24
+ loader: "js"
25
+ })
26
+ );
27
+ build.onLoad(
28
+ {
29
+ filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.production\.js$/
30
+ },
31
+ () => ({
32
+ contents: "export { useSyncExternalStore } from 'react';",
33
+ loader: "js"
34
+ })
35
+ );
36
+ }
37
+ };
38
+ }
39
+ export {
40
+ createUseSyncExternalStoreShimPlugin
41
+ };