dinou 6.1.0 → 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 +99 -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 +17 -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 -107
- 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 "react-server-dom-webpack/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-webpack.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
|
-
}
|