dinou 6.1.1 → 7.0.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 +94 -0
- package/README.md +9461 -1080
- package/cli.js +392 -169
- 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 +309 -157
- 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 +172 -95
- package/dinou/core/handler.js +2296 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +181 -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 +207 -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 +133 -15
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +32 -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 +173 -94
- package/package.json +110 -108
- 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,30 +1,104 @@
|
|
|
1
|
+
// dinou/core/client-webpack.jsx
|
|
2
|
+
if (typeof window !== "undefined") {
|
|
3
|
+
if (!window.$RefreshReg$) window.$RefreshReg$ = () => { };
|
|
4
|
+
if (!window.$RefreshSig$) window.$RefreshSig$ = () => (type) => type;
|
|
5
|
+
}
|
|
1
6
|
import {
|
|
2
7
|
use,
|
|
8
|
+
useRef,
|
|
3
9
|
useState,
|
|
4
10
|
useEffect,
|
|
5
11
|
useTransition,
|
|
6
12
|
useLayoutEffect,
|
|
7
13
|
useMemo,
|
|
14
|
+
useCallback,
|
|
8
15
|
Component,
|
|
16
|
+
createElement,
|
|
17
|
+
StrictMode,
|
|
9
18
|
} from "react";
|
|
10
19
|
import { createFromFetch } from "react-server-dom-webpack/client";
|
|
11
20
|
import { hydrateRoot } from "react-dom/client";
|
|
12
21
|
import { RouterContext } from "./navigation.js";
|
|
13
22
|
import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
|
|
14
23
|
import { createServerFunctionProxy } from "./server-function-proxy-webpack.js";
|
|
24
|
+
import { DinouPageContext, DinouPageSlot, cleanErrorStack } from "./slot.js";
|
|
15
25
|
|
|
16
26
|
// ====================================================================
|
|
17
27
|
// 1. GLOBAL STATE (Outside the component)
|
|
18
28
|
// ====================================================================
|
|
19
|
-
const
|
|
29
|
+
const pageCache = new Map();
|
|
30
|
+
const layoutCache = new Map();
|
|
20
31
|
const scrollCache = new Map();
|
|
21
32
|
|
|
22
33
|
const getCurrentRoute = () => window.location.pathname + window.location.search;
|
|
23
34
|
|
|
35
|
+
const scrollToCurrentHash = (rawHash = typeof window !== "undefined" ? window.location.hash : "") => {
|
|
36
|
+
if (!rawHash) return false;
|
|
37
|
+
try {
|
|
38
|
+
const id = decodeURIComponent(rawHash.replace("#", ""));
|
|
39
|
+
const element = document.getElementById(id) || document.getElementsByName(id)[0];
|
|
40
|
+
if (element) {
|
|
41
|
+
element.scrollIntoView({ behavior: "auto" });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
const id = rawHash.replace("#", "");
|
|
46
|
+
const element = document.getElementById(id) || document.getElementsByName(id)[0];
|
|
47
|
+
if (element) {
|
|
48
|
+
element.scrollIntoView({ behavior: "auto" });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
54
|
+
|
|
24
55
|
// ====================================================================
|
|
25
56
|
// 2. PURE HELPERS
|
|
26
57
|
// ====================================================================
|
|
27
58
|
|
|
59
|
+
// Helper to determine active layout folder for a pathname
|
|
60
|
+
const getLayoutKey = (pathname) => {
|
|
61
|
+
const layouts = (typeof window !== "undefined" && window.__DINOU_LAYOUTS__) || ["", "docs"];
|
|
62
|
+
const cleanPath = pathname.split("?")[0].split("#")[0];
|
|
63
|
+
const segments = cleanPath.split("/").filter(Boolean);
|
|
64
|
+
|
|
65
|
+
for (let i = segments.length; i >= 1; i--) {
|
|
66
|
+
const candidateSegments = segments.slice(0, i);
|
|
67
|
+
const candidate = candidateSegments.join("/");
|
|
68
|
+
if (layouts.includes(candidate)) {
|
|
69
|
+
return "/" + candidate;
|
|
70
|
+
}
|
|
71
|
+
const dynamicMatch = layouts.find((l) => {
|
|
72
|
+
const lSegs = l
|
|
73
|
+
.split("/")
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.filter((seg) => !(seg.startsWith("(") && seg.endsWith(")")));
|
|
76
|
+
if (lSegs.length !== candidateSegments.length) return false;
|
|
77
|
+
return lSegs.every((seg, idx) => {
|
|
78
|
+
if (seg.startsWith("[") && seg.endsWith("]")) return true;
|
|
79
|
+
return seg === candidateSegments[idx];
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
if (dynamicMatch) {
|
|
83
|
+
return "/" + candidate;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
layouts.includes("") ||
|
|
88
|
+
layouts.includes("/") ||
|
|
89
|
+
layouts.some(
|
|
90
|
+
(l) =>
|
|
91
|
+
l
|
|
92
|
+
.split("/")
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.filter((s) => !(s.startsWith("(") && s.endsWith(")"))).length === 0
|
|
95
|
+
)
|
|
96
|
+
) {
|
|
97
|
+
return "/";
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
|
|
28
102
|
// Helper to detect if we only change the hash on the same page
|
|
29
103
|
const isHashChangeOnly = (finalPath) => {
|
|
30
104
|
const targetUrl = new URL(finalPath, window.location.origin);
|
|
@@ -40,37 +114,35 @@ const isHashChangeOnly = (finalPath) => {
|
|
|
40
114
|
);
|
|
41
115
|
};
|
|
42
116
|
|
|
43
|
-
const
|
|
44
|
-
const url =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return cache.get(url);
|
|
117
|
+
const getPagePayload = (route, isPrefetch = false) => {
|
|
118
|
+
const url = route.split("::")[0];
|
|
119
|
+
if (pageCache.has(url)) {
|
|
120
|
+
return pageCache.get(url);
|
|
48
121
|
}
|
|
49
122
|
|
|
50
|
-
// 2. Global Flags Logic (Only first time)
|
|
51
123
|
let payloadUrl;
|
|
52
124
|
if (window.__DINOU_USE_OLD_RSC__ || window.__DINOU_USE_STATIC__) {
|
|
53
125
|
payloadUrl = window.__DINOU_USE_OLD_RSC__
|
|
54
126
|
? window.__DINOU_USE_STATIC__
|
|
55
|
-
? "/
|
|
56
|
-
: "/
|
|
127
|
+
? "/____rsc_page_old_static____" + url
|
|
128
|
+
: "/____rsc_page_old____" + url
|
|
57
129
|
: window.__DINOU_USE_STATIC__
|
|
58
|
-
? "/
|
|
59
|
-
: "/
|
|
60
|
-
|
|
61
|
-
// Clean flags immediately
|
|
62
|
-
window.__DINOU_USE_OLD_RSC__ = false;
|
|
63
|
-
window.__DINOU_USE_STATIC__ = false;
|
|
130
|
+
? "/____rsc_page_static____" + url
|
|
131
|
+
: "/____rsc_page____" + url;
|
|
64
132
|
} else {
|
|
65
|
-
payloadUrl = "/
|
|
133
|
+
payloadUrl = "/____rsc_page____" + url;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const buildId = window.__DINOU_BUILD_ID__;
|
|
137
|
+
if (buildId) {
|
|
138
|
+
payloadUrl += (payloadUrl.includes("?") ? "&" : "?") + "buildId=" + buildId;
|
|
66
139
|
}
|
|
67
140
|
|
|
68
|
-
// 3. Fetch and Cache Storage
|
|
69
141
|
const promise = createFromFetch(
|
|
70
142
|
fetch(payloadUrl).then((res) => {
|
|
71
143
|
if (res.headers.has("x-rsc-redirect")) {
|
|
72
144
|
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
73
|
-
|
|
145
|
+
pageCache.delete(url);
|
|
74
146
|
if (!isPrefetch) {
|
|
75
147
|
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
76
148
|
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
@@ -78,7 +150,6 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
|
|
|
78
150
|
window.location.href = redirectUrl;
|
|
79
151
|
}
|
|
80
152
|
}
|
|
81
|
-
// Return a promise that never resolves to avoid React Server DOM throwing "Connection closed"
|
|
82
153
|
return new Promise(() => {});
|
|
83
154
|
}
|
|
84
155
|
return res;
|
|
@@ -87,18 +158,59 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
|
|
|
87
158
|
callServer: async (id, args) => {
|
|
88
159
|
const proxy = createServerFunctionProxy(id);
|
|
89
160
|
return proxy(...args);
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
promise.catch(() => {});
|
|
166
|
+
pageCache.set(url, promise);
|
|
167
|
+
return promise;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const getLayoutPayload = (layoutKey, searchPart = "", version = 0) => {
|
|
171
|
+
if (!layoutKey) return null;
|
|
172
|
+
const cacheKey = `${layoutKey}${searchPart}`;
|
|
173
|
+
if (layoutCache.has(cacheKey)) {
|
|
174
|
+
return layoutCache.get(cacheKey);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const cleanKey = layoutKey === "/" ? "" : layoutKey;
|
|
178
|
+
let layoutUrl =
|
|
179
|
+
(window.__DINOU_USE_STATIC__
|
|
180
|
+
? "/____rsc_layout_static____"
|
|
181
|
+
: "/____rsc_layout____") + cleanKey + searchPart;
|
|
182
|
+
|
|
183
|
+
if (version) {
|
|
184
|
+
layoutUrl += (layoutUrl.includes("?") ? "&" : "?") + "v=" + version;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const promise = createFromFetch(
|
|
188
|
+
fetch(layoutUrl).then((res) => {
|
|
189
|
+
if (!res.ok) {
|
|
190
|
+
console.warn(
|
|
191
|
+
`[Dinou Router] Layout ${layoutKey} fetch returned status ${res.status}`
|
|
192
|
+
);
|
|
90
193
|
}
|
|
194
|
+
return res;
|
|
195
|
+
}),
|
|
196
|
+
{
|
|
197
|
+
callServer: async (id, args) => {
|
|
198
|
+
const proxy = createServerFunctionProxy(id);
|
|
199
|
+
return proxy(...args);
|
|
200
|
+
},
|
|
91
201
|
}
|
|
92
202
|
);
|
|
93
|
-
|
|
203
|
+
|
|
204
|
+
promise.catch(() => {});
|
|
205
|
+
layoutCache.set(cacheKey, promise);
|
|
94
206
|
return promise;
|
|
95
207
|
};
|
|
96
208
|
|
|
97
209
|
const getErrorRSCPayload = (route, error) => {
|
|
98
210
|
const url = route.split("::")[0];
|
|
99
211
|
const cacheKey = `error::${url}::${error.message || String(error)}`;
|
|
100
|
-
if (
|
|
101
|
-
return
|
|
212
|
+
if (pageCache.has(cacheKey)) {
|
|
213
|
+
return pageCache.get(cacheKey);
|
|
102
214
|
}
|
|
103
215
|
|
|
104
216
|
const payloadUrl = "/____rsc_payload_error____" + url;
|
|
@@ -118,7 +230,7 @@ const getErrorRSCPayload = (route, error) => {
|
|
|
118
230
|
}).then((res) => {
|
|
119
231
|
if (res.headers.has("x-rsc-redirect")) {
|
|
120
232
|
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
121
|
-
|
|
233
|
+
pageCache.delete(cacheKey);
|
|
122
234
|
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
123
235
|
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
124
236
|
} else {
|
|
@@ -132,13 +244,46 @@ const getErrorRSCPayload = (route, error) => {
|
|
|
132
244
|
callServer: async (id, args) => {
|
|
133
245
|
const proxy = createServerFunctionProxy(id);
|
|
134
246
|
return proxy(...args);
|
|
135
|
-
}
|
|
247
|
+
},
|
|
136
248
|
}
|
|
137
249
|
);
|
|
138
|
-
|
|
250
|
+
pageCache.set(cacheKey, promise);
|
|
139
251
|
return promise;
|
|
140
252
|
};
|
|
141
253
|
|
|
254
|
+
const fetchSlotRSC = (slotId, options = {}) => {
|
|
255
|
+
const currentPath = typeof window !== "undefined" ? window.location.pathname : "/";
|
|
256
|
+
const freshQuery = options && options.fresh ? "&fresh=1" : "";
|
|
257
|
+
const slotUrl = `/____rsc_slot____/${slotId}?t=${Date.now()}${freshQuery}`;
|
|
258
|
+
return createFromFetch(
|
|
259
|
+
fetch(slotUrl, {
|
|
260
|
+
headers: {
|
|
261
|
+
"x-dinou-current-path": currentPath,
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
{
|
|
265
|
+
callServer: async (id, args) => {
|
|
266
|
+
const proxy = createServerFunctionProxy(id);
|
|
267
|
+
return proxy(...args);
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const refreshSlot = (slotId, options) => {
|
|
274
|
+
if (typeof window !== "undefined") {
|
|
275
|
+
window.dispatchEvent(
|
|
276
|
+
new CustomEvent("dinou:refresh-slot", { detail: { id: slotId, options } })
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if (typeof globalThis !== "undefined") {
|
|
282
|
+
globalThis.__DINOU_GET_ERROR_RSC_PAYLOAD__ = getErrorRSCPayload;
|
|
283
|
+
globalThis.__DINOU_FETCH_SLOT__ = fetchSlotRSC;
|
|
284
|
+
globalThis.__DINOU_REFRESH_SLOT__ = refreshSlot;
|
|
285
|
+
}
|
|
286
|
+
|
|
142
287
|
class ErrorBoundary extends Component {
|
|
143
288
|
constructor(props) {
|
|
144
289
|
super(props);
|
|
@@ -156,6 +301,12 @@ class ErrorBoundary extends Component {
|
|
|
156
301
|
}
|
|
157
302
|
}
|
|
158
303
|
|
|
304
|
+
componentDidUpdate(prevProps) {
|
|
305
|
+
if (this.state.hasError && prevProps.resetKey !== this.props.resetKey) {
|
|
306
|
+
this.setState({ hasError: false, error: null });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
159
310
|
render() {
|
|
160
311
|
if (this.state.hasError) {
|
|
161
312
|
if (this.props.fallback) {
|
|
@@ -163,29 +314,84 @@ class ErrorBoundary extends Component {
|
|
|
163
314
|
}
|
|
164
315
|
const isDev = process.env.NODE_ENV !== "production";
|
|
165
316
|
return (
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
317
|
+
<html lang="en">
|
|
318
|
+
<head>
|
|
319
|
+
<meta charSet="UTF-8" />
|
|
320
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
321
|
+
<title>"Dinou Dev Error from client.jsx ErrorBoundary"</title>
|
|
322
|
+
</head>
|
|
323
|
+
<body
|
|
324
|
+
style={{
|
|
325
|
+
margin: 0,
|
|
326
|
+
backgroundColor: "#fff1f2",
|
|
327
|
+
padding: "20px",
|
|
328
|
+
boxSizing: "border-box",
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
<div
|
|
332
|
+
style={{
|
|
333
|
+
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
334
|
+
padding: "32px",
|
|
335
|
+
maxWidth: "960px",
|
|
336
|
+
margin: "40px auto",
|
|
337
|
+
backgroundColor: "#fff1f2",
|
|
338
|
+
border: "1px solid #fecdd3",
|
|
339
|
+
borderRadius: "12px",
|
|
340
|
+
color: "#9f1239",
|
|
341
|
+
boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1)",
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
<div style={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: "12px" }}>
|
|
345
|
+
<span
|
|
346
|
+
style={{
|
|
347
|
+
background: "#e11d48",
|
|
348
|
+
color: "white",
|
|
349
|
+
padding: "2px 8px",
|
|
350
|
+
borderRadius: "9999px",
|
|
351
|
+
fontSize: "12px",
|
|
352
|
+
fontWeight: "bold",
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
{isDev ? "Dinou Dev Error" : "Application Error"}
|
|
356
|
+
</span>
|
|
357
|
+
<h2 style={{ margin: 0, fontSize: "1.25rem", fontWeight: 700, color: "#881337" }}>
|
|
358
|
+
{this.state.error?.message || "Unhandled Application Error"}
|
|
359
|
+
</h2>
|
|
360
|
+
</div>
|
|
361
|
+
{isDev && (
|
|
362
|
+
<p style={{ margin: "0 0 16px 0", fontSize: "0.875rem", color: "#9f1239", lineHeight: 1.5 }}>
|
|
363
|
+
An unhandled error occurred during rendering. You can provide a custom error UI by creating an{" "}
|
|
364
|
+
<code style={{ background: "#ffe4e6", padding: "2px 6px", borderRadius: "4px", fontWeight: 600 }}>
|
|
365
|
+
error.tsx
|
|
366
|
+
</code>{" "}
|
|
367
|
+
file in your route folder.
|
|
368
|
+
</p>
|
|
369
|
+
)}
|
|
370
|
+
{isDev && this.state.error?.stack && (
|
|
371
|
+
<pre
|
|
372
|
+
style={{
|
|
373
|
+
background: "#0f172a",
|
|
374
|
+
color: "#f8fafc",
|
|
375
|
+
padding: "16px",
|
|
376
|
+
borderRadius: "8px",
|
|
377
|
+
overflowX: "auto",
|
|
378
|
+
fontSize: "0.8125rem",
|
|
379
|
+
lineHeight: 1.6,
|
|
380
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
381
|
+
}}
|
|
382
|
+
>
|
|
383
|
+
{cleanErrorStack(this.state.error.stack)}
|
|
384
|
+
</pre>
|
|
385
|
+
)}
|
|
386
|
+
</div>
|
|
387
|
+
</body>
|
|
388
|
+
</html>
|
|
182
389
|
);
|
|
183
390
|
}
|
|
184
391
|
return this.props.children;
|
|
185
392
|
}
|
|
186
393
|
}
|
|
187
394
|
|
|
188
|
-
|
|
189
395
|
// ====================================================================
|
|
190
396
|
// 3. ROUTER COMPONENT
|
|
191
397
|
// ====================================================================
|
|
@@ -195,10 +401,32 @@ function Router() {
|
|
|
195
401
|
const [isPopState, setIsPopState] = useState(false);
|
|
196
402
|
const [isPending, startTransition] = useTransition();
|
|
197
403
|
const [version, setVersion] = useState(0);
|
|
404
|
+
const [layoutVersion, setLayoutVersion] = useState(0);
|
|
198
405
|
const [navError, setNavError] = useState(null);
|
|
406
|
+
const [navCount, setNavCount] = useState(0);
|
|
407
|
+
|
|
408
|
+
// If the initial SSR render had a page-level error wrapped inside a working layout,
|
|
409
|
+
// we hydrate the layout normally so the navbar/sidebar are fully interactive,
|
|
410
|
+
// while supplying the server error directly to DinouPageSlot to avoid refetching.
|
|
411
|
+
const initialPageErrorRef = useRef(
|
|
412
|
+
typeof window !== "undefined" &&
|
|
413
|
+
window.__DINOU_ERROR_MESSAGE__ &&
|
|
414
|
+
window.__DINOU_ERROR_SEGMENT__ === "page"
|
|
415
|
+
? (() => {
|
|
416
|
+
const err = new Error(window.__DINOU_ERROR_MESSAGE__);
|
|
417
|
+
err.name = window.__DINOU_ERROR_NAME__ || "Error";
|
|
418
|
+
if (window.__DINOU_ERROR_STACK__) err.stack = window.__DINOU_ERROR_STACK__;
|
|
419
|
+
return getErrorRSCPayload(getCurrentRoute(), err);
|
|
420
|
+
})()
|
|
421
|
+
: null
|
|
422
|
+
);
|
|
199
423
|
|
|
200
424
|
// 🧭 NAVIGATE FUNCTION (Core Logic)
|
|
201
425
|
const navigate = (href, options = {}) => {
|
|
426
|
+
initialPageErrorRef.current = null;
|
|
427
|
+
if (typeof window !== "undefined" && window.__DINOU_ACTIVE_LAYOUT__) {
|
|
428
|
+
window.__DINOU_ACTIVE_LAYOUT__ = null;
|
|
429
|
+
}
|
|
202
430
|
const finalPath = resolveUrl(href, window.location.pathname);
|
|
203
431
|
|
|
204
432
|
// 🛡️ NAVIGATE PROTECTION: Hash Detection
|
|
@@ -220,8 +448,7 @@ function Router() {
|
|
|
220
448
|
}
|
|
221
449
|
|
|
222
450
|
if (options.fresh) {
|
|
223
|
-
|
|
224
|
-
cache.delete(finalPath);
|
|
451
|
+
pageCache.delete(finalPath);
|
|
225
452
|
}
|
|
226
453
|
|
|
227
454
|
// Normal RSC Navigation
|
|
@@ -229,7 +456,6 @@ function Router() {
|
|
|
229
456
|
window.location.pathname + window.location.search,
|
|
230
457
|
window.scrollY,
|
|
231
458
|
);
|
|
232
|
-
// cache.delete(finalPath);
|
|
233
459
|
if (options.replace) {
|
|
234
460
|
window.history.replaceState(null, "", finalPath);
|
|
235
461
|
} else {
|
|
@@ -240,18 +466,52 @@ function Router() {
|
|
|
240
466
|
setIsPopState(false);
|
|
241
467
|
setRoute(finalPath);
|
|
242
468
|
setNavError(null);
|
|
469
|
+
setNavCount((c) => c + 1);
|
|
243
470
|
});
|
|
244
471
|
};
|
|
245
472
|
|
|
473
|
+
const back = () => window.history.back();
|
|
474
|
+
const forward = () => window.history.forward();
|
|
475
|
+
const refresh = useCallback((options = {}) => {
|
|
476
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
477
|
+
pageCache.delete(currentPath);
|
|
478
|
+
|
|
479
|
+
const shouldRefreshLayout = Boolean(options && options.layout);
|
|
480
|
+
if (shouldRefreshLayout) {
|
|
481
|
+
const currentLayoutKey = getLayoutKey(window.location.pathname);
|
|
482
|
+
if (currentLayoutKey) {
|
|
483
|
+
for (const k of layoutCache.keys()) {
|
|
484
|
+
if (k === currentLayoutKey || k.startsWith(currentLayoutKey + "?")) {
|
|
485
|
+
layoutCache.delete(k);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
startTransition(() => {
|
|
492
|
+
setVersion((v) => v + 1);
|
|
493
|
+
if (shouldRefreshLayout) {
|
|
494
|
+
setLayoutVersion((v) => v + 1);
|
|
495
|
+
}
|
|
496
|
+
setNavError(null);
|
|
497
|
+
setNavCount((c) => c + 1);
|
|
498
|
+
});
|
|
499
|
+
}, [startTransition]);
|
|
500
|
+
|
|
246
501
|
// 🔌 EFFECT 1: Expose Global Prefetch & Navigation
|
|
247
502
|
useEffect(() => {
|
|
248
503
|
window.__DINOU_PREFETCH__ = (url) => {
|
|
249
504
|
// 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
|
|
250
505
|
if (isHashChangeOnly(url)) return;
|
|
251
|
-
|
|
506
|
+
const lKey = getLayoutKey(url);
|
|
507
|
+
if (lKey && !layoutCache.has(lKey)) {
|
|
508
|
+
getLayoutPayload(lKey);
|
|
509
|
+
}
|
|
510
|
+
getPagePayload(url, true);
|
|
252
511
|
};
|
|
253
512
|
|
|
254
513
|
window.__DINOU_ROUTER_NAVIGATE__ = navigate;
|
|
514
|
+
window.__DINOU_ROUTER_REFRESH__ = refresh;
|
|
255
515
|
|
|
256
516
|
// Hydration
|
|
257
517
|
document.body.setAttribute("data-hydrated", "true");
|
|
@@ -260,25 +520,11 @@ function Router() {
|
|
|
260
520
|
if (window.__DINOU_ROUTER_NAVIGATE__ === navigate) {
|
|
261
521
|
window.__DINOU_ROUTER_NAVIGATE__ = undefined;
|
|
262
522
|
}
|
|
523
|
+
if (window.__DINOU_ROUTER_REFRESH__ === refresh) {
|
|
524
|
+
window.__DINOU_ROUTER_REFRESH__ = undefined;
|
|
525
|
+
}
|
|
263
526
|
};
|
|
264
|
-
}, [navigate]);
|
|
265
|
-
|
|
266
|
-
const back = () => window.history.back();
|
|
267
|
-
const forward = () => window.history.forward();
|
|
268
|
-
const refresh = () => {
|
|
269
|
-
const currentPath = window.location.pathname + window.location.search;
|
|
270
|
-
// console.log(`[Router] Soft Refreshing: ${currentPath}`);
|
|
271
|
-
|
|
272
|
-
// 1. Delete cache to ensure fresh data
|
|
273
|
-
cache.delete(currentPath);
|
|
274
|
-
|
|
275
|
-
// 2. Start transition (to show isPending if you want)
|
|
276
|
-
startTransition(() => {
|
|
277
|
-
// 3. Increment version to force re-execution of useMemo
|
|
278
|
-
setVersion((v) => v + 1);
|
|
279
|
-
setNavError(null);
|
|
280
|
-
});
|
|
281
|
-
};
|
|
527
|
+
}, [navigate, refresh]);
|
|
282
528
|
|
|
283
529
|
// 🔌 EFFECT 2: Global Listeners (Click and PopState)
|
|
284
530
|
useEffect(() => {
|
|
@@ -318,23 +564,68 @@ function Router() {
|
|
|
318
564
|
};
|
|
319
565
|
|
|
320
566
|
const onPopState = () => {
|
|
567
|
+
initialPageErrorRef.current = null;
|
|
568
|
+
if (typeof window !== "undefined" && window.__DINOU_ACTIVE_LAYOUT__) {
|
|
569
|
+
window.__DINOU_ACTIVE_LAYOUT__ = null;
|
|
570
|
+
}
|
|
321
571
|
const target = getCurrentRoute();
|
|
322
|
-
// Optional: cache.delete(target); // Uncomment if you want refresh on going back
|
|
323
572
|
startTransition(() => {
|
|
324
573
|
setIsPopState(true);
|
|
325
574
|
setRoute(target);
|
|
326
575
|
setNavError(null);
|
|
576
|
+
setNavCount((c) => c + 1);
|
|
327
577
|
});
|
|
578
|
+
|
|
579
|
+
if (window.location.hash) {
|
|
580
|
+
requestAnimationFrame(() => {
|
|
581
|
+
if (!scrollToCurrentHash()) {
|
|
582
|
+
let frames = 0;
|
|
583
|
+
const interval = setInterval(() => {
|
|
584
|
+
frames++;
|
|
585
|
+
if (scrollToCurrentHash() || frames > 20) {
|
|
586
|
+
clearInterval(interval);
|
|
587
|
+
}
|
|
588
|
+
}, 50);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
} else if (target === route) {
|
|
592
|
+
requestAnimationFrame(() => {
|
|
593
|
+
const savedY = scrollCache.get(target);
|
|
594
|
+
if (savedY !== undefined) {
|
|
595
|
+
window.scrollTo(0, savedY);
|
|
596
|
+
} else {
|
|
597
|
+
window.scrollTo(0, 0);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const onHashChange = () => {
|
|
604
|
+
if (window.location.hash) {
|
|
605
|
+
requestAnimationFrame(() => {
|
|
606
|
+
if (!scrollToCurrentHash()) {
|
|
607
|
+
let frames = 0;
|
|
608
|
+
const interval = setInterval(() => {
|
|
609
|
+
frames++;
|
|
610
|
+
if (scrollToCurrentHash() || frames > 20) {
|
|
611
|
+
clearInterval(interval);
|
|
612
|
+
}
|
|
613
|
+
}, 50);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
328
617
|
};
|
|
329
618
|
|
|
330
619
|
window.addEventListener("click", onNavigate);
|
|
331
620
|
window.addEventListener("popstate", onPopState);
|
|
621
|
+
window.addEventListener("hashchange", onHashChange);
|
|
332
622
|
|
|
333
623
|
return () => {
|
|
334
624
|
window.removeEventListener("click", onNavigate);
|
|
335
625
|
window.removeEventListener("popstate", onPopState);
|
|
626
|
+
window.removeEventListener("hashchange", onHashChange);
|
|
336
627
|
};
|
|
337
|
-
}, []);
|
|
628
|
+
}, [route]);
|
|
338
629
|
|
|
339
630
|
// 🔌 EFFECT 3: Scroll Management (Restoration)
|
|
340
631
|
useLayoutEffect(() => {
|
|
@@ -355,21 +646,47 @@ function Router() {
|
|
|
355
646
|
|
|
356
647
|
// 🔌 EFFECT 4: Scroll Management (Hash on new page)
|
|
357
648
|
useEffect(() => {
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (
|
|
365
|
-
|
|
649
|
+
if (!window.location.hash) return;
|
|
650
|
+
if (scrollToCurrentHash()) return;
|
|
651
|
+
|
|
652
|
+
let frames = 0;
|
|
653
|
+
const interval = setInterval(() => {
|
|
654
|
+
frames++;
|
|
655
|
+
if (scrollToCurrentHash() || frames > 20) {
|
|
656
|
+
clearInterval(interval);
|
|
366
657
|
}
|
|
367
|
-
});
|
|
658
|
+
}, 50);
|
|
659
|
+
|
|
660
|
+
return () => clearInterval(interval);
|
|
368
661
|
}, [route]);
|
|
369
662
|
|
|
370
|
-
// RSC Logic
|
|
371
|
-
const
|
|
372
|
-
const
|
|
663
|
+
// RSC Segmented Navigation Logic
|
|
664
|
+
const cleanRoutePath = route.split("?")[0].split("#")[0];
|
|
665
|
+
const searchPart = route.includes("?") ? "?" + route.split("?")[1].split("#")[0] : "";
|
|
666
|
+
const layoutKey = useMemo(() => {
|
|
667
|
+
if (
|
|
668
|
+
typeof window !== "undefined" &&
|
|
669
|
+
window.__DINOU_ACTIVE_LAYOUT__ &&
|
|
670
|
+
route === getCurrentRoute()
|
|
671
|
+
) {
|
|
672
|
+
return window.__DINOU_ACTIVE_LAYOUT__;
|
|
673
|
+
}
|
|
674
|
+
return getLayoutKey(cleanRoutePath);
|
|
675
|
+
}, [cleanRoutePath, route]);
|
|
676
|
+
|
|
677
|
+
const pagePromise = useMemo(() => {
|
|
678
|
+
if (initialPageErrorRef.current && route === getCurrentRoute()) {
|
|
679
|
+
return initialPageErrorRef.current;
|
|
680
|
+
}
|
|
681
|
+
if (navError) return getErrorRSCPayload(route, navError);
|
|
682
|
+
return getPagePayload(route + (version ? `?v=${version}` : ""));
|
|
683
|
+
}, [route, version, navError]);
|
|
684
|
+
|
|
685
|
+
const layoutPromise = useMemo(() => {
|
|
686
|
+
return layoutKey !== null
|
|
687
|
+
? getLayoutPayload(layoutKey, searchPart, layoutVersion)
|
|
688
|
+
: null;
|
|
689
|
+
}, [layoutKey, searchPart, layoutVersion]);
|
|
373
690
|
|
|
374
691
|
const contextValue = useMemo(
|
|
375
692
|
() => ({
|
|
@@ -380,22 +697,80 @@ function Router() {
|
|
|
380
697
|
refresh,
|
|
381
698
|
isPending,
|
|
382
699
|
}),
|
|
383
|
-
[route, isPending],
|
|
700
|
+
[route, isPending, refresh],
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const slotContextValue = useMemo(
|
|
704
|
+
() => ({
|
|
705
|
+
pagePromise,
|
|
706
|
+
resetKey: `${route}::${navCount}`,
|
|
707
|
+
}),
|
|
708
|
+
[pagePromise, route, navCount]
|
|
384
709
|
);
|
|
385
710
|
|
|
386
711
|
return (
|
|
387
712
|
<RouterContext.Provider value={contextValue}>
|
|
388
713
|
<ErrorBoundary
|
|
389
|
-
|
|
714
|
+
resetKey={`${route}::${navCount}`}
|
|
390
715
|
onError={setNavError}
|
|
391
716
|
>
|
|
392
|
-
{
|
|
717
|
+
<DinouPageContext.Provider value={slotContextValue}>
|
|
718
|
+
{navError
|
|
719
|
+
? use(pagePromise)
|
|
720
|
+
: layoutPromise
|
|
721
|
+
? use(layoutPromise)
|
|
722
|
+
: use(pagePromise)}
|
|
723
|
+
</DinouPageContext.Provider>
|
|
393
724
|
</ErrorBoundary>
|
|
394
725
|
</RouterContext.Provider>
|
|
395
726
|
);
|
|
396
727
|
}
|
|
397
728
|
|
|
398
|
-
|
|
729
|
+
const routerElement = createElement(Router);
|
|
730
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
731
|
+
const useStrictMode = isDev && (typeof window === "undefined" || window.__DINOU_STRICT_MODE__ !== false);
|
|
732
|
+
|
|
733
|
+
const app = useStrictMode
|
|
734
|
+
? createElement(StrictMode, null, routerElement)
|
|
735
|
+
: routerElement;
|
|
736
|
+
|
|
737
|
+
const onRecoverableError = (error) => {
|
|
738
|
+
if (
|
|
739
|
+
error?.name === "InvalidStateError" &&
|
|
740
|
+
(error?.message?.includes("Document hidden") || (typeof document !== "undefined" && document.hidden))
|
|
741
|
+
) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (error?.name === "AbortError" && error?.message?.includes("transition")) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (typeof reportError === "function") {
|
|
749
|
+
reportError(error);
|
|
750
|
+
} else {
|
|
751
|
+
console.error(error);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// If the server rendered a fatal layout error during SSR (meaning there is no layout DOM to hydrate),
|
|
756
|
+
// do NOT attempt to hydrate the application over it.
|
|
757
|
+
const hasInitialServerError =
|
|
758
|
+
typeof window !== "undefined" && Boolean(window.__DINOU_ERROR_MESSAGE__);
|
|
759
|
+
const isLayoutError =
|
|
760
|
+
hasInitialServerError && window.__DINOU_ERROR_SEGMENT__ !== "page";
|
|
761
|
+
|
|
762
|
+
if (!isLayoutError) {
|
|
763
|
+
hydrateRoot(document, app, { onRecoverableError });
|
|
764
|
+
} else {
|
|
765
|
+
console.warn(
|
|
766
|
+
"[Dinou] Initial layout server error detected:",
|
|
767
|
+
window.__DINOU_ERROR_MESSAGE__,
|
|
768
|
+
"- skipping client hydration to preserve the server-rendered error screen."
|
|
769
|
+
);
|
|
770
|
+
if (typeof document !== "undefined" && document.body) {
|
|
771
|
+
document.body.setAttribute("data-hydrated", "true");
|
|
772
|
+
}
|
|
773
|
+
}
|
|
399
774
|
|
|
400
775
|
if (import.meta.hot) {
|
|
401
776
|
import.meta.hot.accept();
|