dinou 6.1.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -0
- package/README.md +9352 -1081
- package/cli.js +314 -91
- package/dinou/adapters/bun.js +62 -0
- package/dinou/adapters/cloudflare.js +65 -0
- package/dinou/adapters/deno.js +48 -0
- package/dinou/adapters/netlify.js +85 -0
- package/dinou/adapters/node.mjs +31 -0
- package/dinou/bun/build.mjs +1077 -0
- package/dinou/cloudflare/build.mjs +1121 -0
- package/dinou/cloudflare/shims/child_process.js +14 -0
- package/dinou/cloudflare/shims/empty.js +2 -0
- package/dinou/cloudflare/shims/fs.js +99 -0
- package/dinou/cloudflare/shims/os.js +29 -0
- package/dinou/config.js +1 -0
- package/dinou/config.mjs +2 -0
- package/dinou/constants.js +29 -4
- package/dinou/core/babel-esm-loader.js +66 -37
- package/dinou/core/bailout-proxy.js +61 -0
- package/dinou/core/build-static-pages.js +322 -165
- package/dinou/core/cache-revalidate.js +471 -144
- package/dinou/core/cache-slot.js +387 -0
- package/dinou/core/client-redirect.jsx +12 -7
- package/dinou/core/client-webpack.jsx +457 -82
- package/dinou/core/client.jsx +452 -82
- package/dinou/core/config.js +11 -0
- package/dinou/core/dinou-paths.js +52 -0
- package/dinou/core/edge-ssr.js +122 -0
- package/dinou/core/export-static.mjs +64 -0
- package/dinou/core/generate-route-types.js +252 -0
- package/dinou/core/generate-static-page.js +55 -94
- package/dinou/core/generate-static-rsc.js +47 -15
- package/dinou/core/generate-static.js +212 -10
- package/dinou/core/generating-isg.js +5 -0
- package/dinou/core/get-asset-from-manifest.js +9 -3
- package/dinou/core/get-error-jsx.js +221 -41
- package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
- package/dinou/core/get-jsx.js +175 -96
- package/dinou/core/handler.js +2303 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +183 -0
- package/dinou/core/link.jsx +18 -6
- package/dinou/core/load-dinou-config.js +60 -0
- package/dinou/core/manifest-provider.js +261 -0
- package/dinou/core/navigation-utils.js +4 -0
- package/dinou/core/navigation.js +50 -39
- package/dinou/core/parse-exports.js +252 -2
- package/dinou/core/ppr-context.js +105 -0
- package/dinou/core/ppr-runtime.js +263 -0
- package/dinou/core/redirect.jsx +20 -19
- package/dinou/core/render-jsx-to-client-jsx.js +91 -87
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +34 -0
- package/dinou/core/revalidating.js +28 -9
- package/dinou/core/route-generator.js +107 -0
- package/dinou/core/rsc-renderer.js +90 -0
- package/dinou/core/run-ssg.js +19 -0
- package/dinou/core/scan-dependency-components.js +218 -0
- package/dinou/core/server-function-proxy-webpack.js +3 -0
- package/dinou/core/server-function-proxy.js +3 -0
- package/dinou/core/slot.js +302 -0
- package/dinou/core/status-manifest.js +41 -1
- package/dinou/core/storage-adapter.js +522 -0
- package/dinou/core/url-resolver.js +16 -16
- package/dinou/core/vfs.js +139 -68
- package/dinou/deno/build.mjs +1112 -0
- package/dinou/deno/seed-kv.js +115 -0
- package/dinou/esbuild/build.mjs +51 -1
- package/dinou/esbuild/dev.mjs +290 -224
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
- package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
- package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
- package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
- package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
- package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
- package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
- package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
- package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
- package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
- package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
- package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
- package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
- package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
- package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
- package/dinou/index.d.ts +315 -11
- package/dinou/index.js +12 -6
- package/dinou/index.mjs +9 -0
- package/dinou/node/build.mjs +195 -0
- package/dinou/node/bundle-dual-engine.mjs +1012 -0
- package/dinou/node/dev.mjs +2589 -0
- package/dinou/node/port-selector.mjs +164 -0
- package/dinou/node/terminal-status.mjs +345 -0
- package/dinou/package.json +16 -14
- package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
- package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
- package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
- package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
- package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
- package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
- package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
- package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
- package/dinou/rollup/rollup.config.js +155 -43
- package/dinou/server.d.ts +114 -12
- package/dinou/server.js +35 -10
- package/dinou/server.mjs +30 -9
- package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
- package/dinou/webpack/loaders/server-functions-loader.js +22 -1
- package/dinou/webpack/loaders/swc-loader.js +76 -0
- package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
- package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
- package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
- package/dinou/webpack/webpack.config.js +227 -29
- package/eject.js +113 -34
- package/package.json +18 -16
- package/dinou/core/asset-require-hook.js +0 -58
- package/dinou/core/client-error-webpack.jsx +0 -432
- package/dinou/core/client-error.jsx +0 -432
- package/dinou/core/concurrency-manager.js +0 -48
- package/dinou/core/context-proxy.js +0 -68
- package/dinou/core/generate-static-pages.js +0 -130
- package/dinou/core/generate-static-rscs.js +0 -151
- package/dinou/core/get-ssg-metadata.js +0 -31
- package/dinou/core/register-loader.mjs +0 -11
- package/dinou/core/register-paths.js +0 -27
- package/dinou/core/render-app-to-html.js +0 -430
- package/dinou/core/render-html.js +0 -407
- package/dinou/core/server.js +0 -1542
- package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
use,
|
|
3
|
-
useState,
|
|
4
|
-
useEffect,
|
|
5
|
-
useTransition,
|
|
6
|
-
useLayoutEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
Component,
|
|
9
|
-
} from "react";
|
|
10
|
-
import { createFromFetch } from "@roggc/react-server-dom-esm/client";
|
|
11
|
-
import { hydrateRoot } from "react-dom/client";
|
|
12
|
-
import { RouterContext } from "./navigation.js";
|
|
13
|
-
import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
|
|
14
|
-
import { createServerFunctionProxy } from "./server-function-proxy.js";
|
|
15
|
-
|
|
16
|
-
// ====================================================================
|
|
17
|
-
// 1. GLOBAL STATE (Outside the component)
|
|
18
|
-
// ====================================================================
|
|
19
|
-
const cache = new Map();
|
|
20
|
-
const scrollCache = new Map();
|
|
21
|
-
|
|
22
|
-
const getCurrentRoute = () => window.location.pathname + window.location.search;
|
|
23
|
-
|
|
24
|
-
// ====================================================================
|
|
25
|
-
// 2. PURE HELPERS
|
|
26
|
-
// ====================================================================
|
|
27
|
-
|
|
28
|
-
// Helper to detect if we only change the hash on the same page
|
|
29
|
-
const isHashChangeOnly = (finalPath) => {
|
|
30
|
-
const targetUrl = new URL(finalPath, window.location.origin);
|
|
31
|
-
const normalize = (p) =>
|
|
32
|
-
p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
33
|
-
|
|
34
|
-
const targetPath = normalize(targetUrl.pathname);
|
|
35
|
-
const currentPath = normalize(window.location.pathname);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
targetPath + targetUrl.search === currentPath + window.location.search &&
|
|
39
|
-
targetUrl.hash !== ""
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
let isInitialErrorLoad = true;
|
|
44
|
-
|
|
45
|
-
const getRSCPayload = (rscKey, isPrefetch = false) => {
|
|
46
|
-
const url = rscKey.split("::")[0];
|
|
47
|
-
// 1. Check Idempotence (Avoids the infinite loop of React)
|
|
48
|
-
if (cache.has(url)) return cache.get(url);
|
|
49
|
-
|
|
50
|
-
let promise;
|
|
51
|
-
if (isInitialErrorLoad && url === getCurrentRoute()) {
|
|
52
|
-
isInitialErrorLoad = false;
|
|
53
|
-
const payloadUrl = "/____rsc_payload_error____" + url;
|
|
54
|
-
promise = createFromFetch(
|
|
55
|
-
fetch(payloadUrl, {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: {
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify({
|
|
61
|
-
error: {
|
|
62
|
-
message: window.__DINOU_ERROR_MESSAGE__ || "Unknown Error",
|
|
63
|
-
stack: window.__DINOU_ERROR_STACK__,
|
|
64
|
-
name: window.__DINOU_ERROR_NAME__,
|
|
65
|
-
},
|
|
66
|
-
}),
|
|
67
|
-
}).then((res) => {
|
|
68
|
-
if (res.headers.has("x-rsc-redirect")) {
|
|
69
|
-
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
70
|
-
cache.delete(url);
|
|
71
|
-
if (!isPrefetch) {
|
|
72
|
-
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
73
|
-
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
74
|
-
} else {
|
|
75
|
-
window.location.href = redirectUrl;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return new Promise(() => {});
|
|
79
|
-
}
|
|
80
|
-
return res;
|
|
81
|
-
}),
|
|
82
|
-
{
|
|
83
|
-
callServer: async (id, args) => {
|
|
84
|
-
const proxy = createServerFunctionProxy(id);
|
|
85
|
-
return proxy(...args);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
);
|
|
89
|
-
} else {
|
|
90
|
-
// Normal client.jsx load logic!
|
|
91
|
-
let payloadUrl = "/____rsc_payload____" + url;
|
|
92
|
-
const buildId = window.__DINOU_BUILD_ID__;
|
|
93
|
-
if (buildId) {
|
|
94
|
-
payloadUrl += (payloadUrl.includes("?") ? "&" : "?") + "buildId=" + buildId;
|
|
95
|
-
window.__DINOU_BUILD_ID__ = undefined;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
promise = createFromFetch(
|
|
99
|
-
fetch(payloadUrl).then((res) => {
|
|
100
|
-
if (res.headers.has("x-rsc-redirect")) {
|
|
101
|
-
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
102
|
-
cache.delete(url);
|
|
103
|
-
if (!isPrefetch) {
|
|
104
|
-
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
105
|
-
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
106
|
-
} else {
|
|
107
|
-
window.location.href = redirectUrl;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return new Promise(() => {});
|
|
111
|
-
}
|
|
112
|
-
return res;
|
|
113
|
-
}),
|
|
114
|
-
{
|
|
115
|
-
callServer: async (id, args) => {
|
|
116
|
-
const proxy = createServerFunctionProxy(id);
|
|
117
|
-
return proxy(...args);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
cache.set(url, promise);
|
|
124
|
-
return promise;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const getErrorRSCPayload = (route, error) => {
|
|
128
|
-
const url = route.split("::")[0];
|
|
129
|
-
const cacheKey = `error::${url}::${error.message || String(error)}`;
|
|
130
|
-
if (cache.has(cacheKey)) {
|
|
131
|
-
return cache.get(cacheKey);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const payloadUrl = "/____rsc_payload_error____" + url;
|
|
135
|
-
const promise = createFromFetch(
|
|
136
|
-
fetch(payloadUrl, {
|
|
137
|
-
method: "POST",
|
|
138
|
-
headers: {
|
|
139
|
-
"Content-Type": "application/json",
|
|
140
|
-
},
|
|
141
|
-
body: JSON.stringify({
|
|
142
|
-
error: {
|
|
143
|
-
message: error.message || "Unknown Error",
|
|
144
|
-
name: error.name,
|
|
145
|
-
stack: error.stack,
|
|
146
|
-
},
|
|
147
|
-
}),
|
|
148
|
-
}).then((res) => {
|
|
149
|
-
if (res.headers.has("x-rsc-redirect")) {
|
|
150
|
-
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
151
|
-
cache.delete(cacheKey);
|
|
152
|
-
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
153
|
-
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
154
|
-
} else {
|
|
155
|
-
window.location.href = redirectUrl;
|
|
156
|
-
}
|
|
157
|
-
return new Promise(() => {});
|
|
158
|
-
}
|
|
159
|
-
return res;
|
|
160
|
-
}),
|
|
161
|
-
{
|
|
162
|
-
callServer: async (id, args) => {
|
|
163
|
-
const proxy = createServerFunctionProxy(id);
|
|
164
|
-
return proxy(...args);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
);
|
|
168
|
-
cache.set(cacheKey, promise);
|
|
169
|
-
return promise;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
class ErrorBoundary extends Component {
|
|
173
|
-
constructor(props) {
|
|
174
|
-
super(props);
|
|
175
|
-
this.state = { hasError: false, error: null };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
static getDerivedStateFromError(error) {
|
|
179
|
-
return { hasError: true, error };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
componentDidCatch(error, errorInfo) {
|
|
183
|
-
console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
|
|
184
|
-
if (this.props.onError) {
|
|
185
|
-
this.props.onError(error);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
render() {
|
|
190
|
-
if (this.state.hasError) {
|
|
191
|
-
if (this.props.fallback) {
|
|
192
|
-
return this.props.fallback(this.state.error);
|
|
193
|
-
}
|
|
194
|
-
const isDev = process.env.NODE_ENV !== "production";
|
|
195
|
-
return (
|
|
196
|
-
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
|
|
197
|
-
<h2>Application Error</h2>
|
|
198
|
-
<p>An unexpected error occurred on the client.</p>
|
|
199
|
-
<pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
|
|
200
|
-
{isDev ? (
|
|
201
|
-
<>
|
|
202
|
-
<div style={{ fontWeight: "bold", marginBottom: "10px" }}>
|
|
203
|
-
{this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
|
|
204
|
-
</div>
|
|
205
|
-
<div>{this.state.error?.stack}</div>
|
|
206
|
-
</>
|
|
207
|
-
) : (
|
|
208
|
-
this.state.error?.message || String(this.state.error)
|
|
209
|
-
)}
|
|
210
|
-
</pre>
|
|
211
|
-
</div>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
return this.props.children;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// ====================================================================
|
|
220
|
-
// 3. ROUTER COMPONENT
|
|
221
|
-
// ====================================================================
|
|
222
|
-
|
|
223
|
-
function Router() {
|
|
224
|
-
const [route, setRoute] = useState(getCurrentRoute());
|
|
225
|
-
const [isPopState, setIsPopState] = useState(false);
|
|
226
|
-
const [isPending, startTransition] = useTransition();
|
|
227
|
-
const [version, setVersion] = useState(0);
|
|
228
|
-
const [navError, setNavError] = useState(null);
|
|
229
|
-
|
|
230
|
-
// 🧭 NAVIGATE FUNCTION (Core Logic)
|
|
231
|
-
const navigate = (href, options = {}) => {
|
|
232
|
-
const finalPath = resolveUrl(href, window.location.pathname);
|
|
233
|
-
|
|
234
|
-
// 🛡️ NAVIGATE PROTECTION: Hash Detection
|
|
235
|
-
if (isHashChangeOnly(finalPath)) {
|
|
236
|
-
if (options.replace) {
|
|
237
|
-
window.history.replaceState(null, "", finalPath);
|
|
238
|
-
} else {
|
|
239
|
-
window.history.pushState(null, "", finalPath);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Manual scroll
|
|
243
|
-
const hash = new URL(finalPath, window.location.origin).hash;
|
|
244
|
-
const id = hash.replace("#", "");
|
|
245
|
-
const element = document.getElementById(id);
|
|
246
|
-
if (element) {
|
|
247
|
-
element.scrollIntoView({ behavior: "auto" });
|
|
248
|
-
}
|
|
249
|
-
return; // CRITICAL STOP
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (options.fresh) {
|
|
253
|
-
// console.log(`[Router] Force refreshing: ${finalPath}`);
|
|
254
|
-
cache.delete(finalPath);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Normal RSC Navigation
|
|
258
|
-
scrollCache.set(
|
|
259
|
-
window.location.pathname + window.location.search,
|
|
260
|
-
window.scrollY,
|
|
261
|
-
);
|
|
262
|
-
// cache.delete(finalPath);
|
|
263
|
-
if (options.replace) {
|
|
264
|
-
window.history.replaceState(null, "", finalPath);
|
|
265
|
-
} else {
|
|
266
|
-
window.history.pushState(null, "", finalPath);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
startTransition(() => {
|
|
270
|
-
setIsPopState(false);
|
|
271
|
-
setRoute(finalPath);
|
|
272
|
-
setNavError(null);
|
|
273
|
-
});
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
// 🔌 EFFECT 1: Expose Global Prefetch & Navigation
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
window.__DINOU_PREFETCH__ = (url) => {
|
|
279
|
-
// 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
|
|
280
|
-
if (isHashChangeOnly(url)) return;
|
|
281
|
-
getRSCPayload(url, true);
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
window.__DINOU_ROUTER_NAVIGATE__ = navigate;
|
|
285
|
-
|
|
286
|
-
// Hydration
|
|
287
|
-
document.body.setAttribute("data-hydrated", "true");
|
|
288
|
-
|
|
289
|
-
return () => {
|
|
290
|
-
if (window.__DINOU_ROUTER_NAVIGATE__ === navigate) {
|
|
291
|
-
window.__DINOU_ROUTER_NAVIGATE__ = undefined;
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
}, [navigate]);
|
|
295
|
-
|
|
296
|
-
const back = () => window.history.back();
|
|
297
|
-
const forward = () => window.history.forward();
|
|
298
|
-
const refresh = () => {
|
|
299
|
-
const currentPath = window.location.pathname + window.location.search;
|
|
300
|
-
// console.log(`[Router] Soft Refreshing: ${currentPath}`);
|
|
301
|
-
|
|
302
|
-
// 1. Delete cache to ensure fresh data
|
|
303
|
-
cache.delete(currentPath);
|
|
304
|
-
|
|
305
|
-
// 2. Start transition (to show isPending if you want)
|
|
306
|
-
startTransition(() => {
|
|
307
|
-
// 3. Increment version to force re-execution of useMemo
|
|
308
|
-
setVersion((v) => v + 1);
|
|
309
|
-
setNavError(null);
|
|
310
|
-
});
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// 🔌 EFFECT 2: Global Listeners (Click and PopState)
|
|
314
|
-
useEffect(() => {
|
|
315
|
-
if ("scrollRestoration" in window.history) {
|
|
316
|
-
window.history.scrollRestoration = "manual";
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const onNavigate = (e) => {
|
|
320
|
-
// 🛡️ FIX: If the event was already processed (preventDefault called by Link), we ignore it.
|
|
321
|
-
if (e.defaultPrevented) return;
|
|
322
|
-
const anchor = e.target.closest("a");
|
|
323
|
-
if (
|
|
324
|
-
!anchor ||
|
|
325
|
-
anchor.target ||
|
|
326
|
-
e.metaKey ||
|
|
327
|
-
e.ctrlKey ||
|
|
328
|
-
e.shiftKey ||
|
|
329
|
-
e.altKey
|
|
330
|
-
) {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const href = anchor.getAttribute("href");
|
|
335
|
-
if (!href || href.startsWith("mailto:") || href.startsWith("tel:") || isExternalUrl(href))
|
|
336
|
-
return;
|
|
337
|
-
|
|
338
|
-
// We use the unified helper
|
|
339
|
-
const finalPath = resolveUrl(href, window.location.pathname);
|
|
340
|
-
|
|
341
|
-
// We use the same hash detection helper for consistency
|
|
342
|
-
if (isHashChangeOnly(finalPath)) {
|
|
343
|
-
return; // The browser handles it natively or navigate would handle it
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
e.preventDefault();
|
|
347
|
-
navigate(href);
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const onPopState = () => {
|
|
351
|
-
const target = getCurrentRoute();
|
|
352
|
-
// Optional: cache.delete(target); // Uncomment if you want refresh on going back
|
|
353
|
-
startTransition(() => {
|
|
354
|
-
setIsPopState(true);
|
|
355
|
-
setRoute(target);
|
|
356
|
-
setNavError(null);
|
|
357
|
-
});
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
window.addEventListener("click", onNavigate);
|
|
361
|
-
window.addEventListener("popstate", onPopState);
|
|
362
|
-
|
|
363
|
-
return () => {
|
|
364
|
-
window.removeEventListener("click", onNavigate);
|
|
365
|
-
window.removeEventListener("popstate", onPopState);
|
|
366
|
-
};
|
|
367
|
-
}, []);
|
|
368
|
-
|
|
369
|
-
// 🔌 EFFECT 3: Scroll Management (Restoration)
|
|
370
|
-
useLayoutEffect(() => {
|
|
371
|
-
requestAnimationFrame(() => {
|
|
372
|
-
if (window.location.hash) return;
|
|
373
|
-
|
|
374
|
-
if (isPopState) {
|
|
375
|
-
const key = route;
|
|
376
|
-
const savedY = scrollCache.get(key);
|
|
377
|
-
if (savedY !== undefined) {
|
|
378
|
-
window.scrollTo(0, savedY);
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
window.scrollTo(0, 0);
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
}, [route, isPopState]);
|
|
385
|
-
|
|
386
|
-
// 🔌 EFFECT 4: Scroll Management (Hash on new page)
|
|
387
|
-
useEffect(() => {
|
|
388
|
-
const hash = window.location.hash;
|
|
389
|
-
if (!hash) return;
|
|
390
|
-
|
|
391
|
-
requestAnimationFrame(() => {
|
|
392
|
-
const id = hash.replace("#", "");
|
|
393
|
-
const element = document.getElementById(id);
|
|
394
|
-
if (element) {
|
|
395
|
-
element.scrollIntoView({ behavior: "auto" });
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
}, [route]);
|
|
399
|
-
|
|
400
|
-
// RSC Logic
|
|
401
|
-
const rscKey = route + "::" + version;
|
|
402
|
-
const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
|
|
403
|
-
|
|
404
|
-
const contextValue = useMemo(
|
|
405
|
-
() => ({
|
|
406
|
-
url: route,
|
|
407
|
-
navigate,
|
|
408
|
-
back,
|
|
409
|
-
forward,
|
|
410
|
-
refresh,
|
|
411
|
-
isPending,
|
|
412
|
-
}),
|
|
413
|
-
[route, isPending],
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
return (
|
|
417
|
-
<RouterContext.Provider value={contextValue}>
|
|
418
|
-
<ErrorBoundary
|
|
419
|
-
key={navError ? "error" : "normal"}
|
|
420
|
-
onError={setNavError}
|
|
421
|
-
>
|
|
422
|
-
{use(content)}
|
|
423
|
-
</ErrorBoundary>
|
|
424
|
-
</RouterContext.Provider>
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
hydrateRoot(document, <Router />);
|
|
429
|
-
|
|
430
|
-
if (import.meta.hot) {
|
|
431
|
-
import.meta.hot.accept();
|
|
432
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
class ConcurrencyManager {
|
|
2
|
-
constructor(maxConcurrent) {
|
|
3
|
-
this.maxConcurrent = maxConcurrent;
|
|
4
|
-
this.activeCount = 0;
|
|
5
|
-
this.queue = [];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Executes an asynchronous task respecting the concurrency limit.
|
|
10
|
-
* @param {Function} task - Function that returns a promise (e.g., your render/fork logic)
|
|
11
|
-
*/
|
|
12
|
-
async run(task) {
|
|
13
|
-
// If we are already at maximum, wait in the queue
|
|
14
|
-
if (this.activeCount >= this.maxConcurrent) {
|
|
15
|
-
await new Promise((resolve) => this.queue.push(resolve));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
this.activeCount++;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
return await task();
|
|
22
|
-
} finally {
|
|
23
|
-
this.activeCount--;
|
|
24
|
-
|
|
25
|
-
// If there is someone waiting, let them proceed
|
|
26
|
-
if (this.queue.length > 0) {
|
|
27
|
-
const nextResolve = this.queue.shift();
|
|
28
|
-
nextResolve();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Optional: For metrics
|
|
34
|
-
getStatus() {
|
|
35
|
-
return { active: this.activeCount, queued: this.queue.length };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Global instance (adjust according to your server's RAM)
|
|
40
|
-
// A conservative general rule: CPUs * 2 or a fixed number like 10-20.
|
|
41
|
-
const os = require("os");
|
|
42
|
-
const MAX_PROCESSES =
|
|
43
|
-
process.env.MAX_CONCURRENT_RENDERS || Math.max(1, os.cpus().length * 2);
|
|
44
|
-
// const MAX_PROCESSES = process.env.MAX_CONCURRENT_RENDERS || 10;
|
|
45
|
-
|
|
46
|
-
const processLimiter = new ConcurrencyManager(MAX_PROCESSES);
|
|
47
|
-
|
|
48
|
-
module.exports = processLimiter;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
// core/context-proxy.js
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a proxy object that intercepts response method calls
|
|
5
|
-
* and sends them to the parent process (Express Handler) through IPC.
|
|
6
|
-
* @returns {object} The proxy object that simulates the Express response.
|
|
7
|
-
*/
|
|
8
|
-
function createResponseProxy() {
|
|
9
|
-
// const proxyId = Math.random().toString(36).substring(7);
|
|
10
|
-
// console.log(`[Proxy] Created new proxy with ID: ${proxyId}`);
|
|
11
|
-
// Central function to send commands to the parent process
|
|
12
|
-
function sendCommand(command, args) {
|
|
13
|
-
// console.log(`[Dinou] Sending context command to parent: ${command}`, args);
|
|
14
|
-
if (typeof process.send === "function") {
|
|
15
|
-
process.send({
|
|
16
|
-
type: "DINOU_CONTEXT_COMMAND",
|
|
17
|
-
command,
|
|
18
|
-
args,
|
|
19
|
-
});
|
|
20
|
-
} else {
|
|
21
|
-
console.warn(
|
|
22
|
-
`[Dinou] Attempted to run context command "${command}" outside of a child process.`
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
// _proxyId: proxyId,
|
|
29
|
-
// 1. Proxy to delete cookies
|
|
30
|
-
clearCookie: (name, options) => {
|
|
31
|
-
sendCommand("clearCookie", [name, options]);
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
// 2. 👇 ADDED: Proxy to set cookies
|
|
35
|
-
// This will send [name, value, options] to 'render-app-to-html.js'
|
|
36
|
-
cookie: (name, value, options) => {
|
|
37
|
-
// console.warn(`[Dinou] Proxying cookie set command for cookie: ${name}`);
|
|
38
|
-
sendCommand("cookie", [name, value, options]);
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
// 3. Proxy to set headers
|
|
42
|
-
setHeader: (name, value) => {
|
|
43
|
-
sendCommand("setHeader", [name, value]);
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
// 4. Proxy to redirect
|
|
47
|
-
// IMPROVEMENT: Pass the arguments as is to the parent so that it applies
|
|
48
|
-
// the security logic (safeRedirect) and decides the status.
|
|
49
|
-
redirect: (arg1, arg2) => {
|
|
50
|
-
// arg1 can be status or url
|
|
51
|
-
// arg2 is url (if arg1 is status) or undefined
|
|
52
|
-
if (arg2) {
|
|
53
|
-
sendCommand("redirect", [arg1, arg2]); // [status, url]
|
|
54
|
-
} else {
|
|
55
|
-
sendCommand("redirect", [arg1]); // [url]
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
// 5. Proxy for status code
|
|
60
|
-
status: (code) => {
|
|
61
|
-
sendCommand("status", [code]);
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
module.exports = {
|
|
67
|
-
createResponseProxy,
|
|
68
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
// generate-static-pages.js
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const { mkdirSync, createWriteStream } = require("fs");
|
|
4
|
-
const fs = require("fs").promises;
|
|
5
|
-
const renderAppToHtml = require("./render-app-to-html.js");
|
|
6
|
-
const { getStaticMetadata } = require("./build-static-pages.js");
|
|
7
|
-
const { processMetadata } = require("./get-ssg-metadata.js");
|
|
8
|
-
const { updateStatus } = require("./status-manifest.js");
|
|
9
|
-
|
|
10
|
-
const OUT_DIR = path.resolve(".dinou/dist2");
|
|
11
|
-
|
|
12
|
-
async function generateStaticPages(routes) {
|
|
13
|
-
for (const route of routes) {
|
|
14
|
-
const reqPath = route.endsWith("/") ? route : route + "/";
|
|
15
|
-
const htmlPath = path.join(OUT_DIR, reqPath, "index.html");
|
|
16
|
-
|
|
17
|
-
const query = {};
|
|
18
|
-
const paramsString = JSON.stringify(query);
|
|
19
|
-
const capturedStatus = {};
|
|
20
|
-
const contextForChild = {
|
|
21
|
-
req: {
|
|
22
|
-
query,
|
|
23
|
-
cookies: {},
|
|
24
|
-
headers: {
|
|
25
|
-
"user-agent": "Dinou-SSG-Builder",
|
|
26
|
-
host: "localhost",
|
|
27
|
-
"x-forwarded-proto": "http",
|
|
28
|
-
},
|
|
29
|
-
path: reqPath,
|
|
30
|
-
method: "GET",
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
36
|
-
const fileStream = createWriteStream(htmlPath);
|
|
37
|
-
let htmlStream = null;
|
|
38
|
-
|
|
39
|
-
const mockRes = {
|
|
40
|
-
headersSent: true,
|
|
41
|
-
_cookies: [],
|
|
42
|
-
cookie(name, value, options) {
|
|
43
|
-
this._cookies.push({ name, value, options });
|
|
44
|
-
},
|
|
45
|
-
write: (chunk) => {
|
|
46
|
-
if (!fileStream.writableEnded) fileStream.write(chunk);
|
|
47
|
-
},
|
|
48
|
-
end: (chunk) => {
|
|
49
|
-
if (chunk && !fileStream.writableEnded) fileStream.write(chunk);
|
|
50
|
-
if (htmlStream) {
|
|
51
|
-
htmlStream.unpipe(fileStream);
|
|
52
|
-
}
|
|
53
|
-
if (!fileStream.writableEnded) fileStream.end();
|
|
54
|
-
},
|
|
55
|
-
status: (code) => {
|
|
56
|
-
if (code !== 200)
|
|
57
|
-
console.warn(`[SSG] Status ${code} ignored for ${reqPath}`);
|
|
58
|
-
capturedStatus.value = code;
|
|
59
|
-
},
|
|
60
|
-
setHeader: () => { },
|
|
61
|
-
clearCookie: () => { },
|
|
62
|
-
redirect: () => { },
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
htmlStream = renderAppToHtml(
|
|
66
|
-
reqPath,
|
|
67
|
-
paramsString,
|
|
68
|
-
contextForChild,
|
|
69
|
-
mockRes,
|
|
70
|
-
capturedStatus
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const metadata = getStaticMetadata(reqPath);
|
|
74
|
-
let sideEffectScripts = "";
|
|
75
|
-
if (metadata && metadata.effects) {
|
|
76
|
-
sideEffectScripts = processMetadata(metadata.effects);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
await new Promise((resolve, reject) => {
|
|
80
|
-
if (sideEffectScripts) {
|
|
81
|
-
fileStream.write(sideEffectScripts);
|
|
82
|
-
}
|
|
83
|
-
htmlStream.pipe(fileStream, { end: false });
|
|
84
|
-
|
|
85
|
-
htmlStream.on("end", () => {
|
|
86
|
-
if (!fileStream.writableEnded) fileStream.end();
|
|
87
|
-
updateStatus(reqPath, capturedStatus.value);
|
|
88
|
-
resolve();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
htmlStream.on("error", (err) => {
|
|
92
|
-
if (err.code === "ERR_STREAM_WRITE_AFTER_END") {
|
|
93
|
-
resolve();
|
|
94
|
-
} else {
|
|
95
|
-
reject(err);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
fileStream.on("error", reject);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (metadata) {
|
|
103
|
-
const metadataPath = path.join(OUT_DIR, reqPath, "metadata.json");
|
|
104
|
-
await fs.writeFile(
|
|
105
|
-
metadataPath,
|
|
106
|
-
JSON.stringify({
|
|
107
|
-
revalidate: metadata.revalidate,
|
|
108
|
-
generatedAt: Date.now(),
|
|
109
|
-
effects: metadata.effects,
|
|
110
|
-
tags: metadata.tags || [],
|
|
111
|
-
}, null, 2),
|
|
112
|
-
"utf8"
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.log("✅ Generated HTML:", reqPath);
|
|
117
|
-
} catch (error) {
|
|
118
|
-
if (error.code === "ERR_STREAM_WRITE_AFTER_END") {
|
|
119
|
-
console.log("⚠️ Ignored write-after-end race condition for:", reqPath);
|
|
120
|
-
} else {
|
|
121
|
-
console.error("❌ Error rendering:", reqPath);
|
|
122
|
-
console.error(error.message);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
console.log("🟢 Static page generation complete.");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
module.exports = generateStaticPages;
|