dinou 4.0.2 → 4.0.4
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 +19 -0
- package/README.md +112 -2
- package/dinou/core/build-static-pages.js +260 -261
- package/dinou/core/client-error-webpack.jsx +7 -5
- package/dinou/core/client-error.jsx +7 -5
- package/dinou/core/client-webpack.jsx +8 -6
- package/dinou/core/client.jsx +8 -6
- package/dinou/core/server-function-proxy-webpack.js +23 -7
- package/dinou/core/server-function-proxy.js +22 -6
- package/dinou/core/server.js +97 -29
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +2 -0
- package/dinou/esbuild/plugins-esbuild/babel-react-compiler-plugin.mjs +64 -0
- package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +42 -48
- package/dinou/package.json +5 -2
- package/dinou/rollup/rollup.config.js +6 -1
- package/dinou/webpack/helpers/get-webpack-entries.js +37 -8
- package/dinou/webpack/loaders/server-functions-loader.js +5 -10
- package/dinou/webpack/webpack.config.js +45 -36
- package/package.json +4 -1
|
@@ -38,7 +38,8 @@ const isHashChangeOnly = (finalPath) => {
|
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const getRSCPayload = (
|
|
41
|
+
const getRSCPayload = (rscKey) => {
|
|
42
|
+
const url = rscKey.split("::")[0];
|
|
42
43
|
// Important: url must already be normalized here
|
|
43
44
|
if (cache.has(url)) return cache.get(url);
|
|
44
45
|
|
|
@@ -55,7 +56,7 @@ const getRSCPayload = (url) => {
|
|
|
55
56
|
name: window.__DINOU_ERROR_NAME__,
|
|
56
57
|
},
|
|
57
58
|
}),
|
|
58
|
-
})
|
|
59
|
+
}),
|
|
59
60
|
);
|
|
60
61
|
cache.set(url, content);
|
|
61
62
|
return content;
|
|
@@ -113,7 +114,7 @@ function Router() {
|
|
|
113
114
|
// Normal RSC Navigation
|
|
114
115
|
scrollCache.set(
|
|
115
116
|
window.location.pathname + window.location.search,
|
|
116
|
-
window.scrollY
|
|
117
|
+
window.scrollY,
|
|
117
118
|
);
|
|
118
119
|
// cache.delete(finalPath);
|
|
119
120
|
if (options.replace) {
|
|
@@ -231,7 +232,8 @@ function Router() {
|
|
|
231
232
|
}, [route]);
|
|
232
233
|
|
|
233
234
|
// RSC Logic
|
|
234
|
-
const
|
|
235
|
+
const rscKey = route + "::" + version;
|
|
236
|
+
const content = getRSCPayload(rscKey);
|
|
235
237
|
|
|
236
238
|
const contextValue = useMemo(
|
|
237
239
|
() => ({
|
|
@@ -242,7 +244,7 @@ function Router() {
|
|
|
242
244
|
refresh,
|
|
243
245
|
isPending,
|
|
244
246
|
}),
|
|
245
|
-
[route, isPending]
|
|
247
|
+
[route, isPending],
|
|
246
248
|
);
|
|
247
249
|
|
|
248
250
|
return (
|
|
@@ -38,7 +38,8 @@ const isHashChangeOnly = (finalPath) => {
|
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const getRSCPayload = (
|
|
41
|
+
const getRSCPayload = (rscKey) => {
|
|
42
|
+
const url = rscKey.split("::")[0];
|
|
42
43
|
// Important: url must already be normalized here
|
|
43
44
|
if (cache.has(url)) return cache.get(url);
|
|
44
45
|
|
|
@@ -55,7 +56,7 @@ const getRSCPayload = (url) => {
|
|
|
55
56
|
name: window.__DINOU_ERROR_NAME__,
|
|
56
57
|
},
|
|
57
58
|
}),
|
|
58
|
-
})
|
|
59
|
+
}),
|
|
59
60
|
);
|
|
60
61
|
cache.set(url, content);
|
|
61
62
|
return content;
|
|
@@ -113,7 +114,7 @@ function Router() {
|
|
|
113
114
|
// Normal RSC Navigation
|
|
114
115
|
scrollCache.set(
|
|
115
116
|
window.location.pathname + window.location.search,
|
|
116
|
-
window.scrollY
|
|
117
|
+
window.scrollY,
|
|
117
118
|
);
|
|
118
119
|
// cache.delete(finalPath);
|
|
119
120
|
if (options.replace) {
|
|
@@ -231,7 +232,8 @@ function Router() {
|
|
|
231
232
|
}, [route]);
|
|
232
233
|
|
|
233
234
|
// RSC Logic
|
|
234
|
-
const
|
|
235
|
+
const rscKey = route + "::" + version;
|
|
236
|
+
const content = getRSCPayload(rscKey);
|
|
235
237
|
|
|
236
238
|
const contextValue = useMemo(
|
|
237
239
|
() => ({
|
|
@@ -242,7 +244,7 @@ function Router() {
|
|
|
242
244
|
refresh,
|
|
243
245
|
isPending,
|
|
244
246
|
}),
|
|
245
|
-
[route, isPending]
|
|
247
|
+
[route, isPending],
|
|
246
248
|
);
|
|
247
249
|
|
|
248
250
|
return (
|
|
@@ -38,7 +38,8 @@ const isHashChangeOnly = (finalPath) => {
|
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const getRSCPayload = (
|
|
41
|
+
const getRSCPayload = (rscKey) => {
|
|
42
|
+
const url = rscKey.split("::")[0];
|
|
42
43
|
// 1. Check Idempotence (Avoids the infinite loop of React)
|
|
43
44
|
if (cache.has(url)) {
|
|
44
45
|
return cache.get(url);
|
|
@@ -52,8 +53,8 @@ const getRSCPayload = (url) => {
|
|
|
52
53
|
? "/____rsc_payload_old_static____" + url
|
|
53
54
|
: "/____rsc_payload_old____" + url
|
|
54
55
|
: window.__DINOU_USE_STATIC__
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
? "/____rsc_payload_static____" + url
|
|
57
|
+
: "/____rsc_payload____" + url;
|
|
57
58
|
|
|
58
59
|
// Clean flags immediately
|
|
59
60
|
window.__DINOU_USE_OLD_RSC__ = false;
|
|
@@ -120,7 +121,7 @@ function Router() {
|
|
|
120
121
|
// Normal RSC Navigation
|
|
121
122
|
scrollCache.set(
|
|
122
123
|
window.location.pathname + window.location.search,
|
|
123
|
-
window.scrollY
|
|
124
|
+
window.scrollY,
|
|
124
125
|
);
|
|
125
126
|
// cache.delete(finalPath);
|
|
126
127
|
if (options.replace) {
|
|
@@ -238,7 +239,8 @@ function Router() {
|
|
|
238
239
|
}, [route]);
|
|
239
240
|
|
|
240
241
|
// RSC Logic
|
|
241
|
-
const
|
|
242
|
+
const rscKey = route + "::" + version;
|
|
243
|
+
const content = getRSCPayload(rscKey);
|
|
242
244
|
|
|
243
245
|
const contextValue = useMemo(
|
|
244
246
|
() => ({
|
|
@@ -249,7 +251,7 @@ function Router() {
|
|
|
249
251
|
refresh,
|
|
250
252
|
isPending,
|
|
251
253
|
}),
|
|
252
|
-
[route, isPending]
|
|
254
|
+
[route, isPending],
|
|
253
255
|
);
|
|
254
256
|
|
|
255
257
|
return (
|
package/dinou/core/client.jsx
CHANGED
|
@@ -39,7 +39,8 @@ const isHashChangeOnly = (finalPath) => {
|
|
|
39
39
|
);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const getRSCPayload = (
|
|
42
|
+
const getRSCPayload = (rscKey) => {
|
|
43
|
+
const url = rscKey.split("::")[0];
|
|
43
44
|
// 1. Check Idempotence (Avoids the infinite loop of React)
|
|
44
45
|
if (cache.has(url)) {
|
|
45
46
|
return cache.get(url);
|
|
@@ -53,8 +54,8 @@ const getRSCPayload = (url) => {
|
|
|
53
54
|
? "/____rsc_payload_old_static____" + url
|
|
54
55
|
: "/____rsc_payload_old____" + url
|
|
55
56
|
: window.__DINOU_USE_STATIC__
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
? "/____rsc_payload_static____" + url
|
|
58
|
+
: "/____rsc_payload____" + url;
|
|
58
59
|
|
|
59
60
|
// Clean flags immediately
|
|
60
61
|
window.__DINOU_USE_OLD_RSC__ = false;
|
|
@@ -121,7 +122,7 @@ function Router() {
|
|
|
121
122
|
// Normal RSC Navigation
|
|
122
123
|
scrollCache.set(
|
|
123
124
|
window.location.pathname + window.location.search,
|
|
124
|
-
window.scrollY
|
|
125
|
+
window.scrollY,
|
|
125
126
|
);
|
|
126
127
|
// cache.delete(finalPath);
|
|
127
128
|
if (options.replace) {
|
|
@@ -239,7 +240,8 @@ function Router() {
|
|
|
239
240
|
}, [route]);
|
|
240
241
|
|
|
241
242
|
// RSC Logic
|
|
242
|
-
const
|
|
243
|
+
const rscKey = route + "::" + version;
|
|
244
|
+
const content = getRSCPayload(rscKey);
|
|
243
245
|
|
|
244
246
|
const contextValue = useMemo(
|
|
245
247
|
() => ({
|
|
@@ -250,7 +252,7 @@ function Router() {
|
|
|
250
252
|
refresh,
|
|
251
253
|
isPending,
|
|
252
254
|
}),
|
|
253
|
-
[route, isPending]
|
|
255
|
+
[route, isPending],
|
|
254
256
|
);
|
|
255
257
|
|
|
256
258
|
return (
|
|
@@ -4,13 +4,29 @@ import { createFromFetch } from "react-server-dom-webpack/client";
|
|
|
4
4
|
function createServerFunctionProxy(id) {
|
|
5
5
|
return new Proxy(() => {}, {
|
|
6
6
|
apply: async (_target, _thisArg, args) => {
|
|
7
|
+
let body;
|
|
8
|
+
const headers = {
|
|
9
|
+
"x-server-function-call": "1",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (args[0] instanceof FormData) {
|
|
13
|
+
const formData = args[0];
|
|
14
|
+
|
|
15
|
+
formData.append("__dinou_func_id", id);
|
|
16
|
+
|
|
17
|
+
if (args.length > 1) {
|
|
18
|
+
formData.append("__dinou_args", JSON.stringify(args.slice(1)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body = formData;
|
|
22
|
+
} else {
|
|
23
|
+
headers["Content-Type"] = "application/json";
|
|
24
|
+
body = JSON.stringify({ id, args });
|
|
25
|
+
}
|
|
7
26
|
const res = await fetch("/____server_function____", {
|
|
8
27
|
method: "POST",
|
|
9
|
-
headers
|
|
10
|
-
|
|
11
|
-
"x-server-function-call": "1",
|
|
12
|
-
},
|
|
13
|
-
body: JSON.stringify({ id, args }),
|
|
28
|
+
headers,
|
|
29
|
+
body,
|
|
14
30
|
});
|
|
15
31
|
|
|
16
32
|
if (!res.ok) throw new Error("Server function failed");
|
|
@@ -48,7 +64,7 @@ function createServerFunctionProxy(id) {
|
|
|
48
64
|
// CHECK: We search for the tag start, whether it has the closing > or not
|
|
49
65
|
if (buffer.includes("<script")) {
|
|
50
66
|
console.warn(
|
|
51
|
-
"[Dinou] Stream ended with incomplete script. Discarding tail."
|
|
67
|
+
"[Dinou] Stream ended with incomplete script. Discarding tail.",
|
|
52
68
|
);
|
|
53
69
|
// We do not enqueue.
|
|
54
70
|
} else {
|
|
@@ -86,7 +102,7 @@ function createServerFunctionProxy(id) {
|
|
|
86
102
|
// Once executed, we remove them so they don't go to React
|
|
87
103
|
buffer = buffer.replace(scriptRegex, "");
|
|
88
104
|
|
|
89
|
-
// 4. CALCULATE WHAT IS SAFE TO SEND (
|
|
105
|
+
// 4. CALCULATE WHAT IS SAFE TO SEND (The anti-cut logic)
|
|
90
106
|
// We need to know if the buffer ends with something that LOOKS like the start of a script
|
|
91
107
|
// Dangerous patterns at the end: <, <s, <sc, <scr, <scri, <scrip, <script
|
|
92
108
|
|
|
@@ -4,13 +4,29 @@ import { createFromFetch } from "@roggc/react-server-dom-esm/client";
|
|
|
4
4
|
export function createServerFunctionProxy(id) {
|
|
5
5
|
return new Proxy(() => {}, {
|
|
6
6
|
apply: async (_target, _thisArg, args) => {
|
|
7
|
+
let body;
|
|
8
|
+
const headers = {
|
|
9
|
+
"x-server-function-call": "1",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (args[0] instanceof FormData) {
|
|
13
|
+
const formData = args[0];
|
|
14
|
+
|
|
15
|
+
formData.append("__dinou_func_id", id);
|
|
16
|
+
|
|
17
|
+
if (args.length > 1) {
|
|
18
|
+
formData.append("__dinou_args", JSON.stringify(args.slice(1)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body = formData;
|
|
22
|
+
} else {
|
|
23
|
+
headers["Content-Type"] = "application/json";
|
|
24
|
+
body = JSON.stringify({ id, args });
|
|
25
|
+
}
|
|
7
26
|
const res = await fetch("/____server_function____", {
|
|
8
27
|
method: "POST",
|
|
9
|
-
headers
|
|
10
|
-
|
|
11
|
-
"x-server-function-call": "1",
|
|
12
|
-
},
|
|
13
|
-
body: JSON.stringify({ id, args }),
|
|
28
|
+
headers,
|
|
29
|
+
body,
|
|
14
30
|
});
|
|
15
31
|
|
|
16
32
|
if (!res.ok) throw new Error("Server function failed");
|
|
@@ -48,7 +64,7 @@ export function createServerFunctionProxy(id) {
|
|
|
48
64
|
// CHECK: We search for the tag start, whether it has the closing > or not
|
|
49
65
|
if (buffer.includes("<script")) {
|
|
50
66
|
console.warn(
|
|
51
|
-
"[Dinou] Stream ended with incomplete script. Discarding tail."
|
|
67
|
+
"[Dinou] Stream ended with incomplete script. Discarding tail.",
|
|
52
68
|
);
|
|
53
69
|
// We do not enqueue.
|
|
54
70
|
} else {
|
package/dinou/core/server.js
CHANGED
|
@@ -54,11 +54,11 @@ if (isDevelopment) {
|
|
|
54
54
|
process.cwd(),
|
|
55
55
|
isWebpack
|
|
56
56
|
? `${outputFolder}/react-client-manifest.json`
|
|
57
|
-
: `react_client_manifest/react-client-manifest.json
|
|
57
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
58
58
|
);
|
|
59
59
|
const manifestFolderPath = path.resolve(
|
|
60
60
|
process.cwd(),
|
|
61
|
-
isWebpack ? outputFolder : "react_client_manifest"
|
|
61
|
+
isWebpack ? outputFolder : "react_client_manifest",
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
let manifestWatcher = null;
|
|
@@ -139,7 +139,7 @@ if (isDevelopment) {
|
|
|
139
139
|
}
|
|
140
140
|
} catch (err) {
|
|
141
141
|
console.warn(
|
|
142
|
-
`[Server HMR] Could not resolve or clear ${modulePath}: ${err.message}
|
|
142
|
+
`[Server HMR] Could not resolve or clear ${modulePath}: ${err.message}`,
|
|
143
143
|
);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -234,7 +234,7 @@ if (!isDevelopment) {
|
|
|
234
234
|
process.cwd(),
|
|
235
235
|
isWebpack
|
|
236
236
|
? `${outputFolder}/server-functions-manifest.json`
|
|
237
|
-
: `server_functions_manifest/server-functions-manifest.json
|
|
237
|
+
: `server_functions_manifest/server-functions-manifest.json`,
|
|
238
238
|
); // Adjust 'dist/' to your outdir
|
|
239
239
|
if (existsSync(manifestPath)) {
|
|
240
240
|
serverFunctionsManifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
@@ -258,7 +258,7 @@ function getContext(req, res) {
|
|
|
258
258
|
const safeResCall = (methodName, ...args) => {
|
|
259
259
|
if (res.headersSent) {
|
|
260
260
|
console.log(
|
|
261
|
-
`[Dinou] res.${methodName} called but headers already sent. Ignoring
|
|
261
|
+
`[Dinou] res.${methodName} called but headers already sent. Ignoring.`,
|
|
262
262
|
);
|
|
263
263
|
// console.warn(
|
|
264
264
|
// `[Dinou Warning] RSC Stream active. Ignoring res.${methodName}() to avoid crash.`
|
|
@@ -301,6 +301,12 @@ function getContext(req, res) {
|
|
|
301
301
|
cookie: req.headers["cookie"],
|
|
302
302
|
referer: req.headers["referer"],
|
|
303
303
|
host: req.headers["host"],
|
|
304
|
+
authorization: req.headers["authorization"],
|
|
305
|
+
"accept-language": req.headers["accept-language"],
|
|
306
|
+
"x-forwarded-for": req.headers["x-forwarded-for"],
|
|
307
|
+
forwarded: req.headers["forwarded"],
|
|
308
|
+
"content-type": req.headers["content-type"],
|
|
309
|
+
origin: req.headers["origin"],
|
|
304
310
|
},
|
|
305
311
|
query: { ...req.query },
|
|
306
312
|
path: req.path,
|
|
@@ -344,6 +350,12 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
344
350
|
cookie: req.headers["cookie"],
|
|
345
351
|
referer: req.headers["referer"],
|
|
346
352
|
host: req.headers["host"],
|
|
353
|
+
authorization: req.headers["authorization"],
|
|
354
|
+
"accept-language": req.headers["accept-language"],
|
|
355
|
+
"x-forwarded-for": req.headers["x-forwarded-for"],
|
|
356
|
+
forwarded: req.headers["forwarded"],
|
|
357
|
+
"content-type": req.headers["content-type"],
|
|
358
|
+
origin: req.headers["origin"],
|
|
347
359
|
},
|
|
348
360
|
query: { ...req.query },
|
|
349
361
|
path: req.path,
|
|
@@ -385,7 +397,7 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
385
397
|
// 🛑 Security: JS cannot write HttpOnly cookies
|
|
386
398
|
if (options && options.httpOnly) {
|
|
387
399
|
console.error(
|
|
388
|
-
`[Dinou Error] Cannot set HttpOnly cookie '${name}' in Server Function endpoint because streaming has started
|
|
400
|
+
`[Dinou Error] Cannot set HttpOnly cookie '${name}' in Server Function endpoint because streaming has started.`,
|
|
389
401
|
);
|
|
390
402
|
return;
|
|
391
403
|
}
|
|
@@ -426,7 +438,7 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
426
438
|
const safePath = JSON.stringify(path);
|
|
427
439
|
|
|
428
440
|
res.write(
|
|
429
|
-
`<script>document.cookie = ${safeName} + "=; Max-Age=0; path=" + ${safePath} + ";";</script
|
|
441
|
+
`<script>document.cookie = ${safeName} + "=; Max-Age=0; path=" + ${safePath} + ";";</script>`,
|
|
430
442
|
);
|
|
431
443
|
},
|
|
432
444
|
},
|
|
@@ -465,10 +477,10 @@ if (!isDevelopment) {
|
|
|
465
477
|
process.cwd(),
|
|
466
478
|
isWebpack
|
|
467
479
|
? `${outputFolder}/react-client-manifest.json`
|
|
468
|
-
: `react_client_manifest/react-client-manifest.json
|
|
480
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
469
481
|
),
|
|
470
|
-
"utf8"
|
|
471
|
-
)
|
|
482
|
+
"utf8",
|
|
483
|
+
),
|
|
472
484
|
);
|
|
473
485
|
}
|
|
474
486
|
|
|
@@ -484,9 +496,9 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
484
496
|
? "/____rsc_payload_old_static____"
|
|
485
497
|
: "/____rsc_payload_old____"
|
|
486
498
|
: isStatic
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
""
|
|
499
|
+
? "/____rsc_payload_static____"
|
|
500
|
+
: "/____rsc_payload____",
|
|
501
|
+
"",
|
|
490
502
|
);
|
|
491
503
|
// 1. Correct Map initialization
|
|
492
504
|
if (!isDynamic.has(reqPath)) {
|
|
@@ -512,7 +524,7 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
512
524
|
const payloadPath = path.resolve(
|
|
513
525
|
"dist2",
|
|
514
526
|
reqPath.replace(/^\//, ""),
|
|
515
|
-
isOld || regenerating.has(reqPath) ? "rsc._old.rsc" : "rsc.rsc"
|
|
527
|
+
isOld || regenerating.has(reqPath) ? "rsc._old.rsc" : "rsc.rsc",
|
|
516
528
|
);
|
|
517
529
|
const distDir = path.resolve("dist2");
|
|
518
530
|
|
|
@@ -546,7 +558,7 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
546
558
|
reqPath,
|
|
547
559
|
{ ...req.query },
|
|
548
560
|
isNotFound,
|
|
549
|
-
isDevelopment
|
|
561
|
+
isDevelopment,
|
|
550
562
|
);
|
|
551
563
|
const manifest = isDevelopment
|
|
552
564
|
? JSON.parse(
|
|
@@ -555,10 +567,10 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
555
567
|
process.cwd(),
|
|
556
568
|
isWebpack
|
|
557
569
|
? `${outputFolder}/react-client-manifest.json`
|
|
558
|
-
: `react_client_manifest/react-client-manifest.json
|
|
570
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
559
571
|
),
|
|
560
|
-
"utf8"
|
|
561
|
-
)
|
|
572
|
+
"utf8",
|
|
573
|
+
),
|
|
562
574
|
)
|
|
563
575
|
: cachedClientManifest;
|
|
564
576
|
|
|
@@ -596,7 +608,7 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
|
596
608
|
reqPath,
|
|
597
609
|
{ ...req.query },
|
|
598
610
|
req.body.error,
|
|
599
|
-
isDevelopment
|
|
611
|
+
isDevelopment,
|
|
600
612
|
);
|
|
601
613
|
const manifest = isDevelopment
|
|
602
614
|
? JSON.parse(
|
|
@@ -605,10 +617,10 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
|
605
617
|
process.cwd(),
|
|
606
618
|
isWebpack
|
|
607
619
|
? `${outputFolder}/react-client-manifest.json`
|
|
608
|
-
: `react_client_manifest/react-client-manifest.json
|
|
620
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
609
621
|
),
|
|
610
|
-
"utf8"
|
|
611
|
-
)
|
|
622
|
+
"utf8",
|
|
623
|
+
),
|
|
612
624
|
)
|
|
613
625
|
: cachedClientManifest;
|
|
614
626
|
const { pipe } = renderToPipeableStream(jsx, manifest);
|
|
@@ -702,6 +714,12 @@ app.get(/^\/.*\/?$/, (req, res) => {
|
|
|
702
714
|
cookie: req.headers["cookie"],
|
|
703
715
|
referer: req.headers["referer"],
|
|
704
716
|
host: req.headers["host"],
|
|
717
|
+
authorization: req.headers["authorization"],
|
|
718
|
+
"accept-language": req.headers["accept-language"],
|
|
719
|
+
"x-forwarded-for": req.headers["x-forwarded-for"],
|
|
720
|
+
forwarded: req.headers["forwarded"],
|
|
721
|
+
"content-type": req.headers["content-type"],
|
|
722
|
+
origin: req.headers["origin"],
|
|
705
723
|
},
|
|
706
724
|
path: req.path,
|
|
707
725
|
method: req.method,
|
|
@@ -718,7 +736,7 @@ app.get(/^\/.*\/?$/, (req, res) => {
|
|
|
718
736
|
contextForChild,
|
|
719
737
|
res,
|
|
720
738
|
capturedStatus,
|
|
721
|
-
isDynamic
|
|
739
|
+
isDynamic,
|
|
722
740
|
);
|
|
723
741
|
|
|
724
742
|
res.setHeader("Content-Type", "text/html");
|
|
@@ -785,8 +803,25 @@ function isOriginAllowed(req) {
|
|
|
785
803
|
}
|
|
786
804
|
}
|
|
787
805
|
|
|
806
|
+
const multer = require("multer");
|
|
807
|
+
|
|
808
|
+
const upload = multer().any();
|
|
809
|
+
|
|
810
|
+
const runMiddleware = (req, res, fn) => {
|
|
811
|
+
return new Promise((resolve, reject) => {
|
|
812
|
+
fn(req, res, (result) => {
|
|
813
|
+
if (result instanceof Error) return reject(result);
|
|
814
|
+
return resolve(result);
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
};
|
|
818
|
+
|
|
788
819
|
app.post("/____server_function____", async (req, res) => {
|
|
789
820
|
try {
|
|
821
|
+
if (req.headers["content-type"]?.includes("multipart/form-data")) {
|
|
822
|
+
await runMiddleware(req, res, upload);
|
|
823
|
+
}
|
|
824
|
+
|
|
790
825
|
// 1. Check Origin (Prevent calls from other domains)
|
|
791
826
|
const origin = req.headers.origin;
|
|
792
827
|
const host = req.headers.host;
|
|
@@ -805,11 +840,44 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
805
840
|
// 2. Origin Check (NEW)
|
|
806
841
|
if (!isDevelopment && !isOriginAllowed(req)) {
|
|
807
842
|
console.error(
|
|
808
|
-
`[Security] Blocked request from origin: ${req.headers.origin}
|
|
843
|
+
`[Security] Blocked request from origin: ${req.headers.origin}`,
|
|
809
844
|
);
|
|
810
845
|
return res.status(403).json({ error: "Origin not allowed" });
|
|
811
846
|
}
|
|
812
|
-
|
|
847
|
+
|
|
848
|
+
let id, args;
|
|
849
|
+
|
|
850
|
+
if (req.headers["content-type"]?.includes("multipart/form-data")) {
|
|
851
|
+
id = req.body.__dinou_func_id;
|
|
852
|
+
|
|
853
|
+
const formData = new FormData();
|
|
854
|
+
|
|
855
|
+
for (const key in req.body) {
|
|
856
|
+
if (key === "__dinou_func_id" || key === "__dinou_args") continue;
|
|
857
|
+
formData.append(key, req.body[key]);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (req.files && Array.isArray(req.files)) {
|
|
861
|
+
for (const file of req.files) {
|
|
862
|
+
const blob = new Blob([file.buffer], { type: file.mimetype });
|
|
863
|
+
formData.append(file.fieldname, blob, file.originalname);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
args = [formData];
|
|
868
|
+
|
|
869
|
+
if (req.body.__dinou_args) {
|
|
870
|
+
try {
|
|
871
|
+
const extraArgs = JSON.parse(req.body.__dinou_args);
|
|
872
|
+
args.push(...extraArgs);
|
|
873
|
+
} catch (e) {
|
|
874
|
+
console.error("Error parsing extra args in multipart request");
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} else {
|
|
878
|
+
id = req.body.id;
|
|
879
|
+
args = req.body.args;
|
|
880
|
+
}
|
|
813
881
|
|
|
814
882
|
// Basic input validation: id must be string, args an array
|
|
815
883
|
if (typeof id !== "string" || !Array.isArray(args)) {
|
|
@@ -932,7 +1000,7 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
932
1000
|
process.cwd(),
|
|
933
1001
|
isWebpack
|
|
934
1002
|
? `${outputFolder}/react-client-manifest.json`
|
|
935
|
-
: `react_client_manifest/react-client-manifest.json
|
|
1003
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
936
1004
|
);
|
|
937
1005
|
// Verify that the manifest exists to avoid errors
|
|
938
1006
|
if (!existsSync(manifestPath)) {
|
|
@@ -976,10 +1044,10 @@ const http = require("http");
|
|
|
976
1044
|
await new Promise((resolve) => {
|
|
977
1045
|
server.listen(port, () => {
|
|
978
1046
|
console.log(
|
|
979
|
-
`\n🚀 Dinou Server is ready and listening on http://localhost:${port}
|
|
1047
|
+
`\n🚀 Dinou Server is ready and listening on http://localhost:${port}`,
|
|
980
1048
|
);
|
|
981
1049
|
console.log(
|
|
982
|
-
` Environment: ${isDevelopment ? "Development" : "Production"}
|
|
1050
|
+
` Environment: ${isDevelopment ? "Development" : "Production"}`,
|
|
983
1051
|
);
|
|
984
1052
|
resolve();
|
|
985
1053
|
});
|
|
@@ -996,7 +1064,7 @@ const http = require("http");
|
|
|
996
1064
|
.catch((err) => {
|
|
997
1065
|
console.error(
|
|
998
1066
|
"❌ [Background] Static generation failed (App continues in Dynamic Mode):",
|
|
999
|
-
err
|
|
1067
|
+
err,
|
|
1000
1068
|
);
|
|
1001
1069
|
isReady = true;
|
|
1002
1070
|
});
|
|
@@ -6,6 +6,7 @@ import assetsPlugin from "../plugins-esbuild/assets-plugin.mjs";
|
|
|
6
6
|
import copyStaticFiles from "esbuild-copy-static-files";
|
|
7
7
|
import manifestGeneratorPlugin from "../plugins-esbuild/manifest-generator-plugin.mjs";
|
|
8
8
|
import writePlugin from "../plugins-esbuild/write-plugin.mjs";
|
|
9
|
+
import babelReactCompilerPlugin from "../plugins-esbuild/babel-react-compiler-plugin.mjs";
|
|
9
10
|
import { existsSync } from "node:fs";
|
|
10
11
|
|
|
11
12
|
const manifestData = {};
|
|
@@ -16,6 +17,7 @@ export default function getConfigEsbuildProd({
|
|
|
16
17
|
manifest = {},
|
|
17
18
|
}) {
|
|
18
19
|
let plugins = [
|
|
20
|
+
babelReactCompilerPlugin(),
|
|
19
21
|
TsconfigPathsPlugin({}),
|
|
20
22
|
cssProcessorPlugin({ outdir }),
|
|
21
23
|
reactClientManifestPlugin({
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import babel from "@babel/core";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const norm = (p) => path.resolve(p).replace(/\\/g, "/");
|
|
5
|
+
export default function babelReactCompilerPlugin() {
|
|
6
|
+
return {
|
|
7
|
+
name: "babel-react-compiler-bridge",
|
|
8
|
+
setup(build) {
|
|
9
|
+
const entryPoints = build.initialOptions.entryPoints;
|
|
10
|
+
// Interceptamos archivos JS/TS/JSX/TSX
|
|
11
|
+
// OJO: Filtramos node_modules para no ralentizar procesando librerías externas
|
|
12
|
+
build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
|
|
13
|
+
// Doble chequeo de seguridad para evitar node_modules
|
|
14
|
+
if (args.path.includes("node_modules")) return;
|
|
15
|
+
const abs = path.resolve(args.path);
|
|
16
|
+
|
|
17
|
+
const absNorm = norm(abs);
|
|
18
|
+
const isAnEntryPoint = Object.values(entryPoints).some(
|
|
19
|
+
(val) => norm(path.resolve(val)) === absNorm,
|
|
20
|
+
);
|
|
21
|
+
if (!isAnEntryPoint) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const source = await fs.readFile(args.path, "utf8");
|
|
26
|
+
const filename = args.path;
|
|
27
|
+
|
|
28
|
+
// Transformamos el código con Babel + React Compiler
|
|
29
|
+
const result = await babel.transformAsync(source, {
|
|
30
|
+
filename,
|
|
31
|
+
presets: [
|
|
32
|
+
// Necesitamos decirle a Babel que entienda React y TS antes de compilar
|
|
33
|
+
["@babel/preset-react", { runtime: "automatic" }],
|
|
34
|
+
"@babel/preset-typescript",
|
|
35
|
+
],
|
|
36
|
+
plugins: [
|
|
37
|
+
"babel-plugin-react-compiler", // 👈 LA JOYA DE LA CORONA
|
|
38
|
+
],
|
|
39
|
+
sourceMaps: true, // Vital para que los sourcemaps de esbuild funcionen
|
|
40
|
+
configFile: false, // Ignoramos babel.config.js globales para ir al grano
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Si por alguna razón Babel no devuelve código, dejamos a esbuild actuar
|
|
44
|
+
if (!result || !result.code) return;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
contents: result.code,
|
|
48
|
+
loader: "js", // Babel devuelve JS estándar
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Si falla Babel, mostramos error bonito para no romper el build silenciosamente
|
|
52
|
+
return {
|
|
53
|
+
errors: [
|
|
54
|
+
{
|
|
55
|
+
text: error.message,
|
|
56
|
+
detail: error,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|