@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.
- package/CHANGELOG.md +11 -46
- 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-renderer.d.ts +130 -11
- package/src/react-renderer.js +368 -64
- package/src/react.plugin.d.ts +17 -5
- package/src/react.plugin.js +44 -13
- package/src/router-adapter.d.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-hydration-asset.service.js +3 -3
- 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-runtime-bundle.service.d.ts +12 -12
- package/src/services/react-runtime-bundle.service.js +98 -180
- package/src/utils/client-graph-boundary-plugin.js +147 -9
- package/src/utils/hydration-scripts.d.ts +18 -1
- package/src/utils/hydration-scripts.js +83 -32
- package/src/utils/reachability-analyzer.d.ts +12 -1
- package/src/utils/reachability-analyzer.js +101 -5
- 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-mdx-loader-plugin.js +13 -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/use-sync-external-store-shim-plugin.d.ts +5 -0
- package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
- package/src/react-hmr-strategy.ts +0 -444
- package/src/react-renderer.ts +0 -403
- package/src/react.plugin.ts +0 -241
- package/src/router-adapter.ts +0 -95
- package/src/services/react-bundle.service.ts +0 -212
- package/src/services/react-hmr-page-metadata-cache.ts +0 -24
- package/src/services/react-hydration-asset.service.ts +0 -260
- package/src/services/react-page-module.service.ts +0 -214
- package/src/services/react-runtime-bundle.service.ts +0 -271
- package/src/utils/client-graph-boundary-plugin.ts +0 -590
- package/src/utils/client-only.ts +0 -27
- package/src/utils/declared-modules.ts +0 -99
- package/src/utils/dynamic.ts +0 -27
- package/src/utils/hmr-scripts.ts +0 -47
- package/src/utils/html-boundary.ts +0 -66
- package/src/utils/hydration-scripts.ts +0 -338
- package/src/utils/reachability-analyzer.ts +0 -440
- 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.
|
|
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,
|
|
@@ -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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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,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
|
|
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 ?? [
|
|
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
|
-
|
|
28
|
+
format: resolveCompileFormat(filePath, compilerOptions),
|
|
29
|
+
SourceMapGenerator: sourceMap.SourceMapGenerator
|
|
22
30
|
});
|
|
23
|
-
const
|
|
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)}${
|
|
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,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
|
+
};
|