@ecopages/react 0.2.0-alpha.4 → 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.
- package/CHANGELOG.md +23 -37
- package/README.md +143 -17
- package/package.json +3 -3
- package/src/react-hmr-strategy.d.ts +22 -19
- package/src/react-hmr-strategy.js +57 -109
- package/src/react-hmr-strategy.ts +76 -134
- package/src/react-renderer.d.ts +130 -11
- package/src/react-renderer.js +368 -64
- package/src/react-renderer.ts +490 -90
- package/src/react.plugin.d.ts +17 -5
- package/src/react.plugin.js +44 -13
- package/src/react.plugin.ts +49 -14
- package/src/router-adapter.d.ts +2 -2
- package/src/router-adapter.ts +2 -2
- package/src/services/react-bundle.service.d.ts +2 -25
- package/src/services/react-bundle.service.js +21 -91
- package/src/services/react-bundle.service.ts +22 -126
- package/src/services/react-hydration-asset.service.js +3 -3
- package/src/services/react-hydration-asset.service.ts +7 -4
- package/src/services/react-page-module.service.d.ts +3 -0
- package/src/services/react-page-module.service.js +20 -16
- package/src/services/react-page-module.service.ts +27 -17
- package/src/services/react-runtime-bundle.service.d.ts +12 -12
- package/src/services/react-runtime-bundle.service.js +98 -180
- package/src/services/react-runtime-bundle.service.ts +112 -211
- package/src/utils/client-graph-boundary-plugin.js +147 -9
- package/src/utils/client-graph-boundary-plugin.ts +252 -11
- package/src/utils/hydration-scripts.d.ts +18 -1
- package/src/utils/hydration-scripts.js +83 -32
- package/src/utils/hydration-scripts.ts +159 -38
- package/src/utils/reachability-analyzer.d.ts +12 -1
- package/src/utils/reachability-analyzer.js +101 -5
- package/src/utils/reachability-analyzer.ts +161 -8
- package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
- package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
- package/src/utils/react-dom-runtime-interop-plugin.ts +33 -0
- package/src/utils/react-mdx-loader-plugin.js +13 -5
- package/src/utils/react-mdx-loader-plugin.ts +28 -5
- package/src/utils/react-runtime-specifier-map.d.ts +6 -0
- package/src/utils/react-runtime-specifier-map.js +37 -0
- package/src/utils/react-runtime-specifier-map.ts +45 -0
- package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
- package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
- 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.
|
|
25
|
-
window.
|
|
26
|
-
window.
|
|
27
|
-
|
|
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.
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
86
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
250
|
+
if (!target || !Component) {
|
|
201
251
|
return;
|
|
202
252
|
}
|
|
203
|
-
const props =
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
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
|
-
*
|
|
78
|
-
*
|
|
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.
|
|
94
|
-
window.
|
|
95
|
-
window.
|
|
96
|
-
|
|
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.
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
*
|
|
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.
|
|
161
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
439
|
+
if (!target || !Component) {
|
|
320
440
|
return;
|
|
321
441
|
}
|
|
322
|
-
const props =
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
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
|
}
|
|
@@ -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 {};
|