dinou 5.0.2 → 5.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 +22 -0
- package/README.md +67 -4
- package/dinou/core/babel-esm-loader.js +48 -1
- package/dinou/core/build-static-pages.js +120 -4
- package/dinou/core/cache-revalidate.js +145 -0
- package/dinou/core/client-error-webpack.jsx +194 -23
- package/dinou/core/client-error.jsx +194 -23
- package/dinou/core/client-webpack.jsx +111 -2
- package/dinou/core/client.jsx +111 -2
- package/dinou/core/generate-static-page.js +2 -1
- package/dinou/core/generate-static-pages.js +1 -0
- package/dinou/core/generate-static-rsc.js +1 -2
- package/dinou/core/path-utils.js +10 -0
- package/dinou/core/server.js +101 -40
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -0
- package/dinou/package.json +6 -1
- package/dinou/server.d.ts +12 -0
- package/dinou/server.js +10 -0
- package/dinou/server.mjs +9 -0
- package/dinou/webpack/loaders/server-functions-loader.js +6 -1
- package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -1
- package/dinou/webpack/webpack.config.js +12 -1
- package/package.json +1 -1
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
useTransition,
|
|
6
6
|
useLayoutEffect,
|
|
7
7
|
useMemo,
|
|
8
|
+
Component,
|
|
8
9
|
} from "react";
|
|
9
10
|
import { createFromFetch } from "react-server-dom-webpack/client";
|
|
10
11
|
import { hydrateRoot } from "react-dom/client";
|
|
11
12
|
import { RouterContext } from "./navigation.js";
|
|
12
13
|
import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
|
|
14
|
+
import { createServerFunctionProxy } from "./server-function-proxy-webpack.js";
|
|
13
15
|
|
|
14
16
|
// ====================================================================
|
|
15
17
|
// 1. GLOBAL STATE (Outside the component)
|
|
@@ -81,11 +83,109 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
|
|
|
81
83
|
}
|
|
82
84
|
return res;
|
|
83
85
|
}),
|
|
86
|
+
{
|
|
87
|
+
callServer: async (id, args) => {
|
|
88
|
+
const proxy = createServerFunctionProxy(id);
|
|
89
|
+
return proxy(...args);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
84
92
|
);
|
|
85
93
|
cache.set(url, promise); // <--- KEY TO AVOID LOOP
|
|
86
94
|
return promise;
|
|
87
95
|
};
|
|
88
96
|
|
|
97
|
+
const getErrorRSCPayload = (route, error) => {
|
|
98
|
+
const url = route.split("::")[0];
|
|
99
|
+
const cacheKey = `error::${url}::${error.message || String(error)}`;
|
|
100
|
+
if (cache.has(cacheKey)) {
|
|
101
|
+
return cache.get(cacheKey);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const payloadUrl = "/____rsc_payload_error____" + url;
|
|
105
|
+
const promise = createFromFetch(
|
|
106
|
+
fetch(payloadUrl, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: {
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
error: {
|
|
113
|
+
message: error.message || "Unknown Error",
|
|
114
|
+
name: error.name,
|
|
115
|
+
stack: error.stack,
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
}).then((res) => {
|
|
119
|
+
if (res.headers.has("x-rsc-redirect")) {
|
|
120
|
+
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
121
|
+
cache.delete(cacheKey);
|
|
122
|
+
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
123
|
+
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
124
|
+
} else {
|
|
125
|
+
window.location.href = redirectUrl;
|
|
126
|
+
}
|
|
127
|
+
return new Promise(() => {});
|
|
128
|
+
}
|
|
129
|
+
return res;
|
|
130
|
+
}),
|
|
131
|
+
{
|
|
132
|
+
callServer: async (id, args) => {
|
|
133
|
+
const proxy = createServerFunctionProxy(id);
|
|
134
|
+
return proxy(...args);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
cache.set(cacheKey, promise);
|
|
139
|
+
return promise;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
class ErrorBoundary extends Component {
|
|
143
|
+
constructor(props) {
|
|
144
|
+
super(props);
|
|
145
|
+
this.state = { hasError: false, error: null };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static getDerivedStateFromError(error) {
|
|
149
|
+
return { hasError: true, error };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
componentDidCatch(error, errorInfo) {
|
|
153
|
+
console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
|
|
154
|
+
if (this.props.onError) {
|
|
155
|
+
this.props.onError(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
render() {
|
|
160
|
+
if (this.state.hasError) {
|
|
161
|
+
if (this.props.fallback) {
|
|
162
|
+
return this.props.fallback(this.state.error);
|
|
163
|
+
}
|
|
164
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
165
|
+
return (
|
|
166
|
+
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
|
|
167
|
+
<h2>Application Error</h2>
|
|
168
|
+
<p>An unexpected error occurred on the client.</p>
|
|
169
|
+
<pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
|
|
170
|
+
{isDev ? (
|
|
171
|
+
<>
|
|
172
|
+
<div style={{ fontWeight: "bold", marginBottom: "10px" }}>
|
|
173
|
+
{this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
|
|
174
|
+
</div>
|
|
175
|
+
<div>{this.state.error?.stack}</div>
|
|
176
|
+
</>
|
|
177
|
+
) : (
|
|
178
|
+
this.state.error?.message || String(this.state.error)
|
|
179
|
+
)}
|
|
180
|
+
</pre>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return this.props.children;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
89
189
|
// ====================================================================
|
|
90
190
|
// 3. ROUTER COMPONENT
|
|
91
191
|
// ====================================================================
|
|
@@ -95,6 +195,7 @@ function Router() {
|
|
|
95
195
|
const [isPopState, setIsPopState] = useState(false);
|
|
96
196
|
const [isPending, startTransition] = useTransition();
|
|
97
197
|
const [version, setVersion] = useState(0);
|
|
198
|
+
const [navError, setNavError] = useState(null);
|
|
98
199
|
|
|
99
200
|
// 🧭 NAVIGATE FUNCTION (Core Logic)
|
|
100
201
|
const navigate = (href, options = {}) => {
|
|
@@ -138,6 +239,7 @@ function Router() {
|
|
|
138
239
|
startTransition(() => {
|
|
139
240
|
setIsPopState(false);
|
|
140
241
|
setRoute(finalPath);
|
|
242
|
+
setNavError(null);
|
|
141
243
|
});
|
|
142
244
|
};
|
|
143
245
|
|
|
@@ -174,6 +276,7 @@ function Router() {
|
|
|
174
276
|
startTransition(() => {
|
|
175
277
|
// 3. Increment version to force re-execution of useMemo
|
|
176
278
|
setVersion((v) => v + 1);
|
|
279
|
+
setNavError(null);
|
|
177
280
|
});
|
|
178
281
|
};
|
|
179
282
|
|
|
@@ -220,6 +323,7 @@ function Router() {
|
|
|
220
323
|
startTransition(() => {
|
|
221
324
|
setIsPopState(true);
|
|
222
325
|
setRoute(target);
|
|
326
|
+
setNavError(null);
|
|
223
327
|
});
|
|
224
328
|
};
|
|
225
329
|
|
|
@@ -265,7 +369,7 @@ function Router() {
|
|
|
265
369
|
|
|
266
370
|
// RSC Logic
|
|
267
371
|
const rscKey = route + "::" + version;
|
|
268
|
-
const content = getRSCPayload(rscKey);
|
|
372
|
+
const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
|
|
269
373
|
|
|
270
374
|
const contextValue = useMemo(
|
|
271
375
|
() => ({
|
|
@@ -281,7 +385,12 @@ function Router() {
|
|
|
281
385
|
|
|
282
386
|
return (
|
|
283
387
|
<RouterContext.Provider value={contextValue}>
|
|
284
|
-
|
|
388
|
+
<ErrorBoundary
|
|
389
|
+
key={navError ? "error" : "normal"}
|
|
390
|
+
onError={setNavError}
|
|
391
|
+
>
|
|
392
|
+
{use(content)}
|
|
393
|
+
</ErrorBoundary>
|
|
285
394
|
</RouterContext.Provider>
|
|
286
395
|
);
|
|
287
396
|
}
|
package/dinou/core/client.jsx
CHANGED
|
@@ -6,11 +6,13 @@ import {
|
|
|
6
6
|
useTransition,
|
|
7
7
|
useLayoutEffect,
|
|
8
8
|
useMemo,
|
|
9
|
+
Component,
|
|
9
10
|
} from "react";
|
|
10
11
|
import { createFromFetch } from "@roggc/react-server-dom-esm/client";
|
|
11
12
|
import { hydrateRoot } from "react-dom/client";
|
|
12
13
|
import { RouterContext } from "./navigation.js";
|
|
13
14
|
import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
|
|
15
|
+
import { createServerFunctionProxy } from "./server-function-proxy.js";
|
|
14
16
|
|
|
15
17
|
// ====================================================================
|
|
16
18
|
// 1. GLOBAL STATE (Outside the component)
|
|
@@ -88,11 +90,109 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
|
|
|
88
90
|
}
|
|
89
91
|
return res;
|
|
90
92
|
}),
|
|
93
|
+
{
|
|
94
|
+
callServer: async (id, args) => {
|
|
95
|
+
const proxy = createServerFunctionProxy(id);
|
|
96
|
+
return proxy(...args);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
91
99
|
);
|
|
92
100
|
cache.set(url, promise); // <--- KEY TO AVOID LOOP
|
|
93
101
|
return promise;
|
|
94
102
|
};
|
|
95
103
|
|
|
104
|
+
const getErrorRSCPayload = (route, error) => {
|
|
105
|
+
const url = route.split("::")[0];
|
|
106
|
+
const cacheKey = `error::${url}::${error.message || String(error)}`;
|
|
107
|
+
if (cache.has(cacheKey)) {
|
|
108
|
+
return cache.get(cacheKey);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const payloadUrl = "/____rsc_payload_error____" + url;
|
|
112
|
+
const promise = createFromFetch(
|
|
113
|
+
fetch(payloadUrl, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: {
|
|
116
|
+
"Content-Type": "application/json",
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
error: {
|
|
120
|
+
message: error.message || "Unknown Error",
|
|
121
|
+
name: error.name,
|
|
122
|
+
stack: error.stack,
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
}).then((res) => {
|
|
126
|
+
if (res.headers.has("x-rsc-redirect")) {
|
|
127
|
+
const redirectUrl = res.headers.get("x-rsc-redirect");
|
|
128
|
+
cache.delete(cacheKey);
|
|
129
|
+
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
130
|
+
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
|
|
131
|
+
} else {
|
|
132
|
+
window.location.href = redirectUrl;
|
|
133
|
+
}
|
|
134
|
+
return new Promise(() => {});
|
|
135
|
+
}
|
|
136
|
+
return res;
|
|
137
|
+
}),
|
|
138
|
+
{
|
|
139
|
+
callServer: async (id, args) => {
|
|
140
|
+
const proxy = createServerFunctionProxy(id);
|
|
141
|
+
return proxy(...args);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
cache.set(cacheKey, promise);
|
|
146
|
+
return promise;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
class ErrorBoundary extends Component {
|
|
150
|
+
constructor(props) {
|
|
151
|
+
super(props);
|
|
152
|
+
this.state = { hasError: false, error: null };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static getDerivedStateFromError(error) {
|
|
156
|
+
return { hasError: true, error };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
componentDidCatch(error, errorInfo) {
|
|
160
|
+
console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
|
|
161
|
+
if (this.props.onError) {
|
|
162
|
+
this.props.onError(error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
render() {
|
|
167
|
+
if (this.state.hasError) {
|
|
168
|
+
if (this.props.fallback) {
|
|
169
|
+
return this.props.fallback(this.state.error);
|
|
170
|
+
}
|
|
171
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
172
|
+
return (
|
|
173
|
+
<div style={{ padding: "20px", fontFamily: "sans-serif" }}>
|
|
174
|
+
<h2>Application Error</h2>
|
|
175
|
+
<p>An unexpected error occurred on the client.</p>
|
|
176
|
+
<pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
|
|
177
|
+
{isDev ? (
|
|
178
|
+
<>
|
|
179
|
+
<div style={{ fontWeight: "bold", marginBottom: "10px" }}>
|
|
180
|
+
{this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
|
|
181
|
+
</div>
|
|
182
|
+
<div>{this.state.error?.stack}</div>
|
|
183
|
+
</>
|
|
184
|
+
) : (
|
|
185
|
+
this.state.error?.message || String(this.state.error)
|
|
186
|
+
)}
|
|
187
|
+
</pre>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return this.props.children;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
96
196
|
// ====================================================================
|
|
97
197
|
// 3. ROUTER COMPONENT
|
|
98
198
|
// ====================================================================
|
|
@@ -102,6 +202,7 @@ function Router() {
|
|
|
102
202
|
const [isPopState, setIsPopState] = useState(false);
|
|
103
203
|
const [isPending, startTransition] = useTransition();
|
|
104
204
|
const [version, setVersion] = useState(0);
|
|
205
|
+
const [navError, setNavError] = useState(null);
|
|
105
206
|
|
|
106
207
|
// 🧭 NAVIGATE FUNCTION (Core Logic)
|
|
107
208
|
const navigate = (href, options = {}) => {
|
|
@@ -145,6 +246,7 @@ function Router() {
|
|
|
145
246
|
startTransition(() => {
|
|
146
247
|
setIsPopState(false);
|
|
147
248
|
setRoute(finalPath);
|
|
249
|
+
setNavError(null);
|
|
148
250
|
});
|
|
149
251
|
};
|
|
150
252
|
|
|
@@ -181,6 +283,7 @@ function Router() {
|
|
|
181
283
|
startTransition(() => {
|
|
182
284
|
// 3. Increment version to force re-execution of useMemo
|
|
183
285
|
setVersion((v) => v + 1);
|
|
286
|
+
setNavError(null);
|
|
184
287
|
});
|
|
185
288
|
};
|
|
186
289
|
|
|
@@ -227,6 +330,7 @@ function Router() {
|
|
|
227
330
|
startTransition(() => {
|
|
228
331
|
setIsPopState(true);
|
|
229
332
|
setRoute(target);
|
|
333
|
+
setNavError(null);
|
|
230
334
|
});
|
|
231
335
|
};
|
|
232
336
|
|
|
@@ -272,7 +376,7 @@ function Router() {
|
|
|
272
376
|
|
|
273
377
|
// RSC Logic
|
|
274
378
|
const rscKey = route + "::" + version;
|
|
275
|
-
const content = getRSCPayload(rscKey);
|
|
379
|
+
const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
|
|
276
380
|
|
|
277
381
|
const contextValue = useMemo(
|
|
278
382
|
() => ({
|
|
@@ -288,7 +392,12 @@ function Router() {
|
|
|
288
392
|
|
|
289
393
|
return (
|
|
290
394
|
<RouterContext.Provider value={contextValue}>
|
|
291
|
-
|
|
395
|
+
<ErrorBoundary
|
|
396
|
+
key={navError ? "error" : "normal"}
|
|
397
|
+
onError={setNavError}
|
|
398
|
+
>
|
|
399
|
+
{use(content)}
|
|
400
|
+
</ErrorBoundary>
|
|
292
401
|
</RouterContext.Provider>
|
|
293
402
|
);
|
|
294
403
|
}
|
|
@@ -10,7 +10,7 @@ const OUT_DIR = path.resolve("dist2");
|
|
|
10
10
|
async function generateStaticPage(reqPath) {
|
|
11
11
|
const finalReqPath = reqPath.endsWith("/") ? reqPath : reqPath + "/";
|
|
12
12
|
const htmlPath = path.join(OUT_DIR, finalReqPath, "index.html");
|
|
13
|
-
const tempHtmlPath = path.join(OUT_DIR, finalReqPath,
|
|
13
|
+
const tempHtmlPath = path.join(OUT_DIR, finalReqPath, `index.html.${Date.now()}-${Math.random()}.tmp`);
|
|
14
14
|
|
|
15
15
|
const query = {};
|
|
16
16
|
const paramsString = JSON.stringify(query);
|
|
@@ -94,6 +94,7 @@ async function generateStaticPage(reqPath) {
|
|
|
94
94
|
revalidate: metadata.revalidate,
|
|
95
95
|
generatedAt: Date.now(),
|
|
96
96
|
effects: metadata.effects,
|
|
97
|
+
tags: metadata.tags || [],
|
|
97
98
|
}, null, 2),
|
|
98
99
|
"utf8"
|
|
99
100
|
);
|
|
@@ -15,9 +15,8 @@ const OUT_DIR = path.resolve("dist2");
|
|
|
15
15
|
async function generateStaticRSC(reqPath) {
|
|
16
16
|
const finalReqPath = reqPath.endsWith("/") ? reqPath : reqPath + "/";
|
|
17
17
|
|
|
18
|
-
// 1. PATHS: Final and Temporary
|
|
19
18
|
const payloadPath = path.join(OUT_DIR, finalReqPath, "rsc.rsc");
|
|
20
|
-
const tempPayloadPath =
|
|
19
|
+
const tempPayloadPath = path.join(OUT_DIR, finalReqPath, `rsc.rsc.${Date.now()}-${Math.random()}.tmp`);
|
|
21
20
|
|
|
22
21
|
// 👇 2. MOCK RES (Complete and Robust Version)
|
|
23
22
|
const mockRes = {
|
package/dinou/core/server.js
CHANGED
|
@@ -5,6 +5,8 @@ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
|
5
5
|
globalThis.__dinou_require__ = require;
|
|
6
6
|
const path = require("path");
|
|
7
7
|
|
|
8
|
+
const { normalizePathCase } = require("./path-utils.js");
|
|
9
|
+
|
|
8
10
|
let reactServerPath, reactDomServerPath, reactJsxRuntimePath, reactJsxDevRuntimePath;
|
|
9
11
|
|
|
10
12
|
if (!isWebpack) {
|
|
@@ -124,11 +126,10 @@ if (isDevelopment) {
|
|
|
124
126
|
while (attempts < maxRetries) {
|
|
125
127
|
try {
|
|
126
128
|
// console.log(`Attempting to load manifest (try ${attempts + 1})...`);
|
|
127
|
-
|
|
129
|
+
const text = readFileSync(manifestPath, "utf8");
|
|
130
|
+
if (!text.trim()) throw new Error("Empty JSON");
|
|
131
|
+
return JSON.parse(text);
|
|
128
132
|
} catch (err) {
|
|
129
|
-
if (err.code !== "ENOENT") {
|
|
130
|
-
throw err; // Rethrow if it's not a file not found error
|
|
131
|
-
}
|
|
132
133
|
attempts++;
|
|
133
134
|
if (attempts >= maxRetries) {
|
|
134
135
|
throw err; // Rethrow after max retries
|
|
@@ -628,7 +629,7 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
628
629
|
const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
|
|
629
630
|
currentGeneratedAt = metaObj.generatedAt || null;
|
|
630
631
|
}
|
|
631
|
-
} catch (e) {}
|
|
632
|
+
} catch (e) { }
|
|
632
633
|
|
|
633
634
|
const useOld =
|
|
634
635
|
isOld ||
|
|
@@ -797,7 +798,43 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
797
798
|
});
|
|
798
799
|
} catch (error) {
|
|
799
800
|
console.error("Error rendering RSC:", error);
|
|
800
|
-
|
|
801
|
+
try {
|
|
802
|
+
const serializedError = {
|
|
803
|
+
message: error.message || "Unknown Error",
|
|
804
|
+
name: error.name,
|
|
805
|
+
stack: isDevelopment ? error.stack : undefined,
|
|
806
|
+
};
|
|
807
|
+
const context = getContext(req, res);
|
|
808
|
+
await requestStorage.run(context, async () => {
|
|
809
|
+
const jsx = await getErrorJSX(
|
|
810
|
+
reqPath,
|
|
811
|
+
{ ...req.query },
|
|
812
|
+
serializedError,
|
|
813
|
+
isDevelopment,
|
|
814
|
+
);
|
|
815
|
+
const manifest = isDevelopment
|
|
816
|
+
? JSON.parse(
|
|
817
|
+
readFileSync(
|
|
818
|
+
path.resolve(
|
|
819
|
+
process.cwd(),
|
|
820
|
+
isWebpack
|
|
821
|
+
? `${outputFolder}/react-client-manifest.json`
|
|
822
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
823
|
+
),
|
|
824
|
+
"utf8",
|
|
825
|
+
),
|
|
826
|
+
)
|
|
827
|
+
: cachedClientManifest;
|
|
828
|
+
const { pipe } = isWebpack
|
|
829
|
+
? renderToPipeableStream(jsx, manifest)
|
|
830
|
+
: renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
|
|
831
|
+
res.status(500);
|
|
832
|
+
pipe(res);
|
|
833
|
+
});
|
|
834
|
+
} catch (innerError) {
|
|
835
|
+
console.error("Fatal error rendering fallback error RSC:", innerError);
|
|
836
|
+
res.status(500).send("Internal Server Error");
|
|
837
|
+
}
|
|
801
838
|
}
|
|
802
839
|
}
|
|
803
840
|
|
|
@@ -822,29 +859,33 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
|
822
859
|
const reqPath = (
|
|
823
860
|
req.path.endsWith("/") ? req.path : req.path + "/"
|
|
824
861
|
).replace("/____rsc_payload_error____", "");
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
862
|
+
|
|
863
|
+
const context = getContext(req, res);
|
|
864
|
+
await requestStorage.run(context, async () => {
|
|
865
|
+
const jsx = await getErrorJSX(
|
|
866
|
+
reqPath,
|
|
867
|
+
{ ...req.query },
|
|
868
|
+
req.body.error,
|
|
869
|
+
isDevelopment,
|
|
870
|
+
);
|
|
871
|
+
const manifest = isDevelopment
|
|
872
|
+
? JSON.parse(
|
|
873
|
+
readFileSync(
|
|
874
|
+
path.resolve(
|
|
875
|
+
process.cwd(),
|
|
876
|
+
isWebpack
|
|
877
|
+
? `${outputFolder}/react-client-manifest.json`
|
|
878
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
879
|
+
),
|
|
880
|
+
"utf8",
|
|
839
881
|
),
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
pipe(res);
|
|
882
|
+
)
|
|
883
|
+
: cachedClientManifest;
|
|
884
|
+
const { pipe } = isWebpack
|
|
885
|
+
? renderToPipeableStream(jsx, manifest)
|
|
886
|
+
: renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
|
|
887
|
+
pipe(res);
|
|
888
|
+
});
|
|
848
889
|
} catch (error) {
|
|
849
890
|
console.error("Error rendering RSC:", error);
|
|
850
891
|
res.status(500).send("Internal Server Error");
|
|
@@ -985,10 +1026,10 @@ app.get(/^\/.*\/?$/, async (req, res) => {
|
|
|
985
1026
|
if (existsSync(fileToRead) && !dynamicState.value) {
|
|
986
1027
|
res.setHeader("Content-Type", "text/html");
|
|
987
1028
|
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
988
|
-
const
|
|
1029
|
+
const status = getStatus(reqPath);
|
|
989
1030
|
|
|
990
|
-
if (
|
|
991
|
-
res.statusCode =
|
|
1031
|
+
if (status) {
|
|
1032
|
+
res.statusCode = status;
|
|
992
1033
|
} else {
|
|
993
1034
|
res.statusCode = 200; // Default
|
|
994
1035
|
}
|
|
@@ -1001,7 +1042,7 @@ app.get(/^\/.*\/?$/, async (req, res) => {
|
|
|
1001
1042
|
const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
|
|
1002
1043
|
buildId = metaObj.generatedAt || "";
|
|
1003
1044
|
}
|
|
1004
|
-
} catch (e) {}
|
|
1045
|
+
} catch (e) { }
|
|
1005
1046
|
|
|
1006
1047
|
let scripts = `<script>window.__DINOU_USE_STATIC__=true;</script>`;
|
|
1007
1048
|
if (htmlPathOld) {
|
|
@@ -1208,8 +1249,27 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1208
1249
|
return res.status(400).json({ error: "Invalid file URL format" });
|
|
1209
1250
|
}
|
|
1210
1251
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1252
|
+
let relativePath;
|
|
1253
|
+
|
|
1254
|
+
// Check if the URL is a relative reference (e.g. file:///src/...)
|
|
1255
|
+
// If so, extract it directly without using fileURLToPath (which throws on Windows without a drive letter)
|
|
1256
|
+
const isRelativeSrc = fileUrl.startsWith("file:///src/") || fileUrl.startsWith("file:///src\\");
|
|
1257
|
+
|
|
1258
|
+
if (isRelativeSrc) {
|
|
1259
|
+
relativePath = fileUrl.replace(/^file:\/\/\/?/, "").trim();
|
|
1260
|
+
} else {
|
|
1261
|
+
const resolvedPath = fileURLToPath(fileUrl);
|
|
1262
|
+
relativePath = resolvedPath;
|
|
1263
|
+
|
|
1264
|
+
const normalizedCwd = normalizePathCase(process.cwd());
|
|
1265
|
+
const normalizedResolved = normalizePathCase(resolvedPath);
|
|
1266
|
+
|
|
1267
|
+
if (normalizedResolved.startsWith(normalizedCwd)) {
|
|
1268
|
+
relativePath = path.relative(normalizedCwd, normalizedResolved);
|
|
1269
|
+
} else {
|
|
1270
|
+
relativePath = relativePath.replace(/^[\\/]+/, "");
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1213
1273
|
const normalizedRelative = relativePath.replace(/\\/g, "/");
|
|
1214
1274
|
if (
|
|
1215
1275
|
normalizedRelative.startsWith("/") ||
|
|
@@ -1220,7 +1280,7 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1220
1280
|
.status(400)
|
|
1221
1281
|
.json({ error: "Invalid path: no absolute, traversal, or drive letter allowed" });
|
|
1222
1282
|
}
|
|
1223
|
-
|
|
1283
|
+
|
|
1224
1284
|
// Restrict to 'src/' folder: prepend 'src/' if missing, and resolve absolutePath
|
|
1225
1285
|
if (!relativePath.startsWith("src/") && !relativePath.startsWith("src\\")) {
|
|
1226
1286
|
relativePath = path.join("src", relativePath);
|
|
@@ -1234,9 +1294,10 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1234
1294
|
.status(403)
|
|
1235
1295
|
.json({ error: "Access denied: file outside src directory" });
|
|
1236
1296
|
}
|
|
1237
|
-
|
|
1297
|
+
|
|
1238
1298
|
// Verify that the file exists
|
|
1239
1299
|
if (!existsSync(absolutePath)) {
|
|
1300
|
+
// console.error("❌ [Dinou Server Function 404] File not found! id:", id, "relativePath:", relativePath, "absolutePath:", absolutePath);
|
|
1240
1301
|
return res.status(404).json({ error: "File not found" });
|
|
1241
1302
|
}
|
|
1242
1303
|
|
|
@@ -1257,8 +1318,8 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1257
1318
|
.status(403)
|
|
1258
1319
|
.json({ error: "Not a valid server function file" });
|
|
1259
1320
|
}
|
|
1260
|
-
// Parse exports
|
|
1261
|
-
const exports = parseExports(fileContent);
|
|
1321
|
+
// Parse exports
|
|
1322
|
+
const exports = parseExports(fileContent);
|
|
1262
1323
|
allowedExports = new Set(exports);
|
|
1263
1324
|
}
|
|
1264
1325
|
|
|
@@ -1271,10 +1332,10 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1271
1332
|
return res.status(400).json({ error: "Invalid export name" });
|
|
1272
1333
|
}
|
|
1273
1334
|
|
|
1274
|
-
// Proceed with import
|
|
1335
|
+
// Proceed with import
|
|
1275
1336
|
const mod = await importModule(absolutePath);
|
|
1276
1337
|
|
|
1277
|
-
// Validate exportName
|
|
1338
|
+
// Validate exportName
|
|
1278
1339
|
if (!exportName || (exportName !== "default" && !mod[exportName])) {
|
|
1279
1340
|
return res.status(400).json({ error: "Invalid export name" });
|
|
1280
1341
|
}
|
|
@@ -56,6 +56,9 @@ export default function getConfigEsbuildProd({
|
|
|
56
56
|
metafile: true,
|
|
57
57
|
logLevel: "warning",
|
|
58
58
|
minify: true,
|
|
59
|
+
define: {
|
|
60
|
+
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
61
|
+
},
|
|
59
62
|
external: [
|
|
60
63
|
"/__SERVER_FUNCTION_PROXY__",
|
|
61
64
|
"/serverFunctionProxy.js",
|
package/dinou/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dinou-ejected",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"private": true,
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"import": "./index.mjs",
|
|
9
9
|
"require": "./index.js"
|
|
10
|
+
},
|
|
11
|
+
"./server": {
|
|
12
|
+
"types": "./server.d.ts",
|
|
13
|
+
"import": "./server.mjs",
|
|
14
|
+
"require": "./server.js"
|
|
10
15
|
}
|
|
11
16
|
},
|
|
12
17
|
"types": "./index.d.ts",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Revalidates the cache for a specific route path.
|
|
3
|
+
* * It deletes the old cached HTML and RSC files and rebuilds them synchronously.
|
|
4
|
+
* @param path The path of the route to revalidate (e.g. "/blog" or "/blog/12").
|
|
5
|
+
*/
|
|
6
|
+
export declare function revalidatePath(path: string): Promise<void>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Revalidates all routes that are associated with the specified cache tag.
|
|
10
|
+
* @param tag The tag string to revalidate (e.g. "blog-posts").
|
|
11
|
+
*/
|
|
12
|
+
export declare function revalidateTag(tag: string): Promise<void>;
|
package/dinou/server.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
revalidatePath: async function (path) {
|
|
3
|
+
const { revalidatePath: fn } = require("./core/cache-revalidate.js");
|
|
4
|
+
return fn(path);
|
|
5
|
+
},
|
|
6
|
+
revalidateTag: async function (tag) {
|
|
7
|
+
const { revalidateTag: fn } = require("./core/cache-revalidate.js");
|
|
8
|
+
return fn(tag);
|
|
9
|
+
},
|
|
10
|
+
};
|