dinou 4.0.16 → 5.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 +10 -0
- package/README.md +150 -57
- package/dinou/core/babel-esm-loader.js +87 -5
- package/dinou/core/build-static-pages.js +17 -97
- package/dinou/core/generate-static-page.js +21 -3
- package/dinou/core/generate-static-pages.js +30 -41
- package/dinou/core/generate-static-rsc.js +11 -5
- package/dinou/core/generate-static-rscs.js +11 -5
- package/dinou/core/generate-static.js +2 -3
- package/dinou/core/get-jsx.js +2 -1
- package/dinou/core/get-ssg-metadata.js +5 -13
- package/dinou/core/import-module.js +25 -6
- package/dinou/core/redirect.jsx +18 -11
- package/dinou/core/register-loader.mjs +1 -0
- package/dinou/core/render-app-to-html.js +198 -31
- package/dinou/core/render-html.js +144 -26
- package/dinou/core/render-jsx-to-client-jsx.js +6 -7
- package/dinou/core/request-context.js +11 -6
- package/dinou/core/revalidating.js +29 -25
- package/dinou/core/server.js +270 -85
- package/dinou/esbuild/dev.mjs +31 -2
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +0 -40
- package/dinou/index.mjs +31 -10
- package/dinou/package.json +1 -1
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +88 -49
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +7 -9
- package/dinou/rollup/rollup.config.js +1 -2
- package/dinou/webpack/helpers/get-webpack-entries.js +0 -40
- package/package.json +3 -3
- package/dinou/core/babel-plugin-register-imports.js +0 -50
- package/dinou/core/get-ssg-jsx-or-jsx.js +0 -14
- package/dinou/core/get-ssg-jsx.js +0 -83
- package/dinou/core/jsx-json.js +0 -55
- package/dinou/core/register-hooks.js +0 -18
package/dinou/core/server.js
CHANGED
|
@@ -1,17 +1,53 @@
|
|
|
1
1
|
require("dotenv/config");
|
|
2
|
+
const Module = require("module");
|
|
3
|
+
const originalResolveFilename = Module._resolveFilename;
|
|
4
|
+
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
5
|
+
globalThis.__dinou_require__ = require;
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
let reactServerPath, reactDomServerPath, reactJsxRuntimePath, reactJsxDevRuntimePath;
|
|
9
|
+
|
|
10
|
+
if (!isWebpack) {
|
|
11
|
+
const reactPkgJson = require.resolve("react/package.json");
|
|
12
|
+
reactServerPath = path.join(path.dirname(reactPkgJson), "react.react-server.js");
|
|
13
|
+
reactJsxRuntimePath = path.join(path.dirname(reactPkgJson), "jsx-runtime.react-server.js");
|
|
14
|
+
reactJsxDevRuntimePath = path.join(path.dirname(reactPkgJson), "jsx-dev-runtime.react-server.js");
|
|
15
|
+
|
|
16
|
+
const reactDomPkgJson = require.resolve("react-dom/package.json");
|
|
17
|
+
reactDomServerPath = path.join(path.dirname(reactDomPkgJson), "react-dom.react-server.js");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Module._resolveFilename = function (request, parent, isMain, options) {
|
|
21
|
+
if (!isWebpack) {
|
|
22
|
+
if (request === "react") {
|
|
23
|
+
return reactServerPath;
|
|
24
|
+
} else if (request === "react-dom") {
|
|
25
|
+
return reactDomServerPath;
|
|
26
|
+
} else if (request === "react/jsx-runtime") {
|
|
27
|
+
return reactJsxRuntimePath;
|
|
28
|
+
} else if (request === "react/jsx-dev-runtime") {
|
|
29
|
+
return reactJsxDevRuntimePath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return originalResolveFilename.call(this, request, parent, isMain, options);
|
|
33
|
+
};
|
|
34
|
+
|
|
2
35
|
require("./register-paths");
|
|
3
|
-
require("./register-hooks.js");
|
|
4
36
|
const webpackRegister = require("react-server-dom-webpack/node-register");
|
|
5
|
-
const path = require("path");
|
|
6
37
|
const { readFileSync, existsSync, createReadStream } = require("fs");
|
|
7
|
-
const
|
|
38
|
+
// const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
39
|
+
const { renderToPipeableStream } = isWebpack
|
|
40
|
+
? require("react-server-dom-webpack/server")
|
|
41
|
+
: require("@roggc/react-server-dom-esm/server");
|
|
8
42
|
const express = require("express");
|
|
9
43
|
const getJSX = require("./get-jsx.js");
|
|
44
|
+
const { getFilePathAndDynamicParams } = require("./get-file-path-and-dynamic-params.js");
|
|
10
45
|
const { getErrorJSX } = require("./get-error-jsx.js");
|
|
11
46
|
const addHook = require("./asset-require-hook.js");
|
|
12
47
|
const { extensions } = require("./asset-extensions.js");
|
|
13
|
-
|
|
14
|
-
|
|
48
|
+
if (isWebpack) {
|
|
49
|
+
webpackRegister();
|
|
50
|
+
}
|
|
15
51
|
const babelRegister = require("@babel/register");
|
|
16
52
|
babelRegister({
|
|
17
53
|
ignore: [/node_modules[\\/](?!dinou)/],
|
|
@@ -19,7 +55,7 @@ babelRegister({
|
|
|
19
55
|
["@babel/preset-react", { runtime: "automatic" }],
|
|
20
56
|
"@babel/preset-typescript",
|
|
21
57
|
],
|
|
22
|
-
plugins: [
|
|
58
|
+
plugins: ["@babel/transform-modules-commonjs"],
|
|
23
59
|
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
|
24
60
|
});
|
|
25
61
|
const createScopedName = require("./createScopedName");
|
|
@@ -39,8 +75,7 @@ const { revalidating, regenerating } = require("./revalidating.js");
|
|
|
39
75
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
40
76
|
const outputFolder = isDevelopment ? "public" : "dist3";
|
|
41
77
|
const chokidar = require("chokidar");
|
|
42
|
-
const { fileURLToPath } = require("url");
|
|
43
|
-
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
78
|
+
const { fileURLToPath, pathToFileURL } = require("url");
|
|
44
79
|
const parseExports = require("./parse-exports.js");
|
|
45
80
|
const { requestStorage } = require("./request-context.js");
|
|
46
81
|
const { useServerRegex } = require("../constants.js");
|
|
@@ -248,6 +283,35 @@ const appUseCookieParser = cookieParser();
|
|
|
248
283
|
const app = express();
|
|
249
284
|
app.use(appUseCookieParser);
|
|
250
285
|
app.use(express.json());
|
|
286
|
+
|
|
287
|
+
// ============================================================
|
|
288
|
+
// 🛡️ ANTI-BOT SHIELD (Prevent ISG from collapsing)
|
|
289
|
+
// ============================================================
|
|
290
|
+
const botGarbagePatterns = [
|
|
291
|
+
/\.php$/i, // Any PHP file
|
|
292
|
+
/\.env$/i, // Attempts to steal environment variables
|
|
293
|
+
/\.git\b/i, // Attempts to access Git repository
|
|
294
|
+
/wp-admin/i, // WordPress admin
|
|
295
|
+
/wp-content/i, // WordPress content
|
|
296
|
+
/wp-includes/i, // WordPress core files
|
|
297
|
+
/xmlrpc\.php/i, // WordPress DDoS attacks
|
|
298
|
+
/\.sql$/i, // Attempts to download database dumps
|
|
299
|
+
/\.asp$/i, // Legacy ASP pages
|
|
300
|
+
/\.jsp$/i, // JSP pages
|
|
301
|
+
/\.cgi$/i, // Old CGI scripts
|
|
302
|
+
/\.bak$/i, // Backup files
|
|
303
|
+
/\.log$/i, // Log files
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
app.use((req, res, next) => {
|
|
307
|
+
const isGarbage = botGarbagePatterns.some((pattern) => pattern.test(req.path));
|
|
308
|
+
if (isGarbage) {
|
|
309
|
+
// Return 404 immediately to prevent the ISG engine from spawning processes
|
|
310
|
+
return res.status(404).send("Not Found");
|
|
311
|
+
}
|
|
312
|
+
next();
|
|
313
|
+
});
|
|
314
|
+
|
|
251
315
|
const { resolveRelativeUrl } = require("./url-resolver");
|
|
252
316
|
|
|
253
317
|
function getContext(req, res) {
|
|
@@ -372,7 +436,7 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
372
436
|
if (referer) {
|
|
373
437
|
try {
|
|
374
438
|
refererPath = new URL(referer).pathname;
|
|
375
|
-
} catch (e) {}
|
|
439
|
+
} catch (e) { }
|
|
376
440
|
}
|
|
377
441
|
const resolvedUrl = resolveRelativeUrl(rawUrl, refererPath);
|
|
378
442
|
let finalUrl = "/";
|
|
@@ -474,12 +538,27 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
474
538
|
|
|
475
539
|
app.use(express.static(path.resolve(process.cwd(), outputFolder)));
|
|
476
540
|
|
|
541
|
+
const clientManifestResolvedPath = path.resolve(
|
|
542
|
+
process.cwd(),
|
|
543
|
+
isWebpack
|
|
544
|
+
? `${outputFolder}/react-client-manifest.json`
|
|
545
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
function isManifestReady() {
|
|
549
|
+
try {
|
|
550
|
+
return existsSync(clientManifestResolvedPath) && readFileSync(clientManifestResolvedPath, "utf8").trim().length > 2;
|
|
551
|
+
} catch (e) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
477
556
|
let isReady = isDevelopment; // In dev we are always ready (or almost)
|
|
478
557
|
|
|
479
558
|
app.get("/__DINOU_STATUS_PLAYWRIGHT__", (req, res) => {
|
|
480
559
|
res.json({
|
|
481
560
|
status: "ok",
|
|
482
|
-
isReady,
|
|
561
|
+
isReady: isDevelopment ? isManifestReady() : isReady,
|
|
483
562
|
mode: isDevelopment ? "development" : "production",
|
|
484
563
|
});
|
|
485
564
|
});
|
|
@@ -554,16 +633,17 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
554
633
|
}
|
|
555
634
|
if (existsSync(payloadPath)) {
|
|
556
635
|
res.setHeader("Content-Type", "application/octet-stream");
|
|
557
|
-
|
|
558
|
-
|
|
636
|
+
try {
|
|
637
|
+
const buffer = readFileSync(payloadPath);
|
|
638
|
+
return res.send(buffer);
|
|
639
|
+
} catch (err) {
|
|
559
640
|
console.error("Error reading RSC file:", err);
|
|
560
|
-
res.status(500).end();
|
|
561
|
-
}
|
|
562
|
-
return readStream.pipe(res);
|
|
641
|
+
return res.status(500).end();
|
|
642
|
+
}
|
|
563
643
|
}
|
|
564
644
|
}
|
|
565
645
|
const context = getContext(req, res);
|
|
566
|
-
const isNotFound =
|
|
646
|
+
const isNotFound = {};
|
|
567
647
|
await requestStorage.run(context, async () => {
|
|
568
648
|
const jsx = await getJSX(
|
|
569
649
|
reqPath,
|
|
@@ -572,6 +652,10 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
572
652
|
isDevelopment,
|
|
573
653
|
);
|
|
574
654
|
|
|
655
|
+
if (isNotFound.value) {
|
|
656
|
+
res.status(404);
|
|
657
|
+
}
|
|
658
|
+
|
|
575
659
|
if (res.headersSent) {
|
|
576
660
|
return;
|
|
577
661
|
}
|
|
@@ -590,7 +674,9 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
|
|
|
590
674
|
)
|
|
591
675
|
: cachedClientManifest;
|
|
592
676
|
|
|
593
|
-
const { pipe } =
|
|
677
|
+
const { pipe } = isWebpack
|
|
678
|
+
? renderToPipeableStream(jsx, manifest)
|
|
679
|
+
: renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
|
|
594
680
|
pipe(res);
|
|
595
681
|
});
|
|
596
682
|
} catch (error) {
|
|
@@ -639,7 +725,9 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
|
639
725
|
),
|
|
640
726
|
)
|
|
641
727
|
: cachedClientManifest;
|
|
642
|
-
const { pipe } =
|
|
728
|
+
const { pipe } = isWebpack
|
|
729
|
+
? renderToPipeableStream(jsx, manifest)
|
|
730
|
+
: renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
|
|
643
731
|
pipe(res);
|
|
644
732
|
} catch (error) {
|
|
645
733
|
console.error("Error rendering RSC:", error);
|
|
@@ -647,9 +735,116 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
|
647
735
|
}
|
|
648
736
|
});
|
|
649
737
|
|
|
650
|
-
|
|
738
|
+
const pageFunctionsConfigCache = new Map();
|
|
739
|
+
|
|
740
|
+
app.get(/^\/.*\/?$/, async (req, res) => {
|
|
651
741
|
try {
|
|
742
|
+
const reqSegments = req.path.split("/").filter(Boolean);
|
|
743
|
+
const srcFolder = path.resolve(process.cwd(), "src");
|
|
744
|
+
const [pagePath, dynamicParams] = getFilePathAndDynamicParams(
|
|
745
|
+
reqSegments,
|
|
746
|
+
req.query,
|
|
747
|
+
srcFolder,
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
if (!pagePath) {
|
|
751
|
+
if (path.extname(req.path)) {
|
|
752
|
+
return res.status(404).send("Not Found");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
let isPathBlocked = false;
|
|
757
|
+
|
|
758
|
+
if (pagePath) {
|
|
759
|
+
let cachedConfig = pageFunctionsConfigCache.get(pagePath);
|
|
760
|
+
if (!cachedConfig) {
|
|
761
|
+
const pageFolder = path.dirname(pagePath);
|
|
762
|
+
const [pageFunctionsPath] = getFilePathAndDynamicParams(
|
|
763
|
+
reqSegments,
|
|
764
|
+
req.query,
|
|
765
|
+
pageFolder,
|
|
766
|
+
"page_functions",
|
|
767
|
+
true,
|
|
768
|
+
true,
|
|
769
|
+
undefined,
|
|
770
|
+
reqSegments.length,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
if (pageFunctionsPath) {
|
|
774
|
+
const pageFunctionsModule = await importModule(pageFunctionsPath);
|
|
775
|
+
const allowISGValue = pageFunctionsModule.allowISG
|
|
776
|
+
? await pageFunctionsModule.allowISG()
|
|
777
|
+
: true;
|
|
778
|
+
|
|
779
|
+
let staticPathsSet = null;
|
|
780
|
+
if (pageFunctionsModule.getStaticPaths) {
|
|
781
|
+
const paths = await pageFunctionsModule.getStaticPaths();
|
|
782
|
+
staticPathsSet = new Set(
|
|
783
|
+
(paths || []).map((pathObj) => {
|
|
784
|
+
const sortedEntries = Object.entries(pathObj).sort((a, b) =>
|
|
785
|
+
a[0].localeCompare(b[0])
|
|
786
|
+
);
|
|
787
|
+
return JSON.stringify(sortedEntries);
|
|
788
|
+
})
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
cachedConfig = {
|
|
793
|
+
allowISG: allowISGValue,
|
|
794
|
+
staticPathsSet,
|
|
795
|
+
validateParams: pageFunctionsModule.validateParams || null,
|
|
796
|
+
};
|
|
797
|
+
} else {
|
|
798
|
+
cachedConfig = {
|
|
799
|
+
allowISG: true,
|
|
800
|
+
staticPathsSet: null,
|
|
801
|
+
validateParams: null,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (!isDevelopment) {
|
|
806
|
+
pageFunctionsConfigCache.set(pagePath, cachedConfig);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const { allowISG: allowISGValue, staticPathsSet, validateParams: validateParamsFn } = cachedConfig;
|
|
811
|
+
const hasParams = Object.keys(dynamicParams || {}).length > 0;
|
|
812
|
+
if (hasParams) {
|
|
813
|
+
if (validateParamsFn) {
|
|
814
|
+
const isValid = await validateParamsFn(dynamicParams);
|
|
815
|
+
if (!isValid) {
|
|
816
|
+
isPathBlocked = true;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (!isPathBlocked && allowISGValue === false) {
|
|
821
|
+
let isPathAllowed = false;
|
|
822
|
+
if (staticPathsSet) {
|
|
823
|
+
const sortedQueryEntries = Object.entries(dynamicParams)
|
|
824
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
825
|
+
.map(([k, v]) => {
|
|
826
|
+
if (Array.isArray(v)) return [k, v.join(",")];
|
|
827
|
+
return [k, String(v)];
|
|
828
|
+
});
|
|
829
|
+
const serializedQuery = JSON.stringify(sortedQueryEntries);
|
|
830
|
+
isPathAllowed = staticPathsSet.has(serializedQuery);
|
|
831
|
+
}
|
|
832
|
+
if (!isPathAllowed) {
|
|
833
|
+
if (isDevelopment) {
|
|
834
|
+
isPathBlocked = true;
|
|
835
|
+
} else {
|
|
836
|
+
const htmlPath = path.join("dist2", req.path, "index.html");
|
|
837
|
+
if (!existsSync(htmlPath)) {
|
|
838
|
+
isPathBlocked = true;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
652
846
|
const reqPath = req.path.endsWith("/") ? req.path : req.path + "/";
|
|
847
|
+
|
|
653
848
|
// 1. Correct Map initialization
|
|
654
849
|
if (!isDynamic.has(reqPath)) {
|
|
655
850
|
// Initialize with a mutable object.
|
|
@@ -660,7 +855,7 @@ app.get(/^\/.*\/?$/, (req, res) => {
|
|
|
660
855
|
// Get reference to the mutable object
|
|
661
856
|
const dynamicState = isDynamic.get(reqPath);
|
|
662
857
|
// console.log("dynamicState.value", dynamicState.value);
|
|
663
|
-
if (!isDevelopment && !dynamicState.value) {
|
|
858
|
+
if (!isDevelopment && !dynamicState.value && pagePath && !isPathBlocked) {
|
|
664
859
|
revalidating(reqPath, dynamicState);
|
|
665
860
|
let htmlPathOld;
|
|
666
861
|
if (regenerating.has(reqPath)) {
|
|
@@ -680,29 +875,19 @@ app.get(/^\/.*\/?$/, (req, res) => {
|
|
|
680
875
|
} else {
|
|
681
876
|
res.statusCode = 200; // Default
|
|
682
877
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
console.error("Error reading HTML file:", err);
|
|
687
|
-
if (!res.headersSent) res.status(500).send("Server Error");
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
// 1. IMPORTANT: We use { end: false } so pipe doesn't close the response
|
|
691
|
-
rStream.pipe(res, { end: false });
|
|
692
|
-
|
|
693
|
-
// 2. When the file finishes sending...
|
|
694
|
-
rStream.on("end", () => {
|
|
695
|
-
// 3. Write our additional content
|
|
878
|
+
try {
|
|
879
|
+
const htmlContent = readFileSync(fileToRead);
|
|
880
|
+
res.write(htmlContent);
|
|
696
881
|
if (htmlPathOld) {
|
|
697
882
|
res.write("<script>window.__DINOU_USE_OLD_RSC__=true;</script>");
|
|
698
883
|
}
|
|
699
884
|
res.write("<script>window.__DINOU_USE_STATIC__=true;</script>");
|
|
700
|
-
|
|
701
|
-
// 4. Manually close the response (now we do)
|
|
702
885
|
res.end();
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
|
|
886
|
+
} catch (err) {
|
|
887
|
+
console.error("Error reading HTML file:", err);
|
|
888
|
+
if (!res.headersSent) res.status(500).send("Server Error");
|
|
889
|
+
}
|
|
890
|
+
return;
|
|
706
891
|
}
|
|
707
892
|
}
|
|
708
893
|
|
|
@@ -739,6 +924,7 @@ app.get(/^\/.*\/?$/, (req, res) => {
|
|
|
739
924
|
res,
|
|
740
925
|
capturedStatus,
|
|
741
926
|
isDynamic,
|
|
927
|
+
isPathBlocked,
|
|
742
928
|
);
|
|
743
929
|
|
|
744
930
|
res.setHeader("Content-Type", "text/html");
|
|
@@ -968,57 +1154,56 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
968
1154
|
return res.status(400).json({ error: "Export is not a function" });
|
|
969
1155
|
}
|
|
970
1156
|
|
|
971
|
-
|
|
972
|
-
const context = getContextForServerFunctionEndpoint(req, res);
|
|
1157
|
+
const context = getContextForServerFunctionEndpoint(req, res);
|
|
973
1158
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1159
|
+
let result;
|
|
1160
|
+
try {
|
|
1161
|
+
result = await requestStorage.run(context, async () => {
|
|
1162
|
+
return await fn(...args);
|
|
1163
|
+
});
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
// 💡 WE INTERCEPT THE REDIRECT
|
|
1166
|
+
if (err && err.$$type === "dinou-internal-redirect") {
|
|
1167
|
+
const safeUrl = JSON.stringify(err.url);
|
|
1168
|
+
|
|
1169
|
+
if (!res.headersSent) {
|
|
1170
|
+
// SCENARIO A: Clean (Content-Type application/json)
|
|
1171
|
+
res.setHeader("Content-Type", "application/json");
|
|
1172
|
+
res.setHeader("X-Dinou-Redirect", err.url);
|
|
1173
|
+
return res.status(200).json({ redirect: err.url });
|
|
1174
|
+
} else {
|
|
1175
|
+
// SCENARIO B: Dirty/Active Stream
|
|
1176
|
+
// Write a custom line-based stream command
|
|
1177
|
+
res.write(`D:{"type":"redirect","url":${safeUrl}}\n`);
|
|
1178
|
+
|
|
1179
|
+
// ⚠️ IMPORTANT:
|
|
1180
|
+
// 1. We close the response, since we redirected and there will be no RSC payload.
|
|
1181
|
+
// 2. WE STOP execution so it doesn't continue to res.json() below.
|
|
1182
|
+
res.end();
|
|
1183
|
+
return;
|
|
1001
1184
|
}
|
|
1002
|
-
throw err; // If it's another error, throw it to the outer catch
|
|
1003
1185
|
}
|
|
1186
|
+
throw err; // If it's another error, throw it to the outer catch
|
|
1187
|
+
}
|
|
1004
1188
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1189
|
+
if (!res.headersSent) res.setHeader("Content-Type", "text/x-component");
|
|
1190
|
+
const manifestPath = path.resolve(
|
|
1191
|
+
process.cwd(),
|
|
1192
|
+
isWebpack
|
|
1193
|
+
? `${outputFolder}/react-client-manifest.json`
|
|
1194
|
+
: `react_client_manifest/react-client-manifest.json`,
|
|
1195
|
+
);
|
|
1196
|
+
// Verify that the manifest exists to avoid errors
|
|
1197
|
+
if (!existsSync(manifestPath)) {
|
|
1198
|
+
return res.status(500).json({ error: "Manifest not found" });
|
|
1199
|
+
}
|
|
1200
|
+
const manifest = isDevelopment
|
|
1201
|
+
? JSON.parse(readFileSync(manifestPath, "utf8"))
|
|
1202
|
+
: cachedClientManifest;
|
|
1203
|
+
const { pipe } = isWebpack
|
|
1204
|
+
? renderToPipeableStream(result, manifest)
|
|
1205
|
+
: renderToPipeableStream(result, pathToFileURL(process.cwd()).href + "/");
|
|
1206
|
+
pipe(res);
|
|
1022
1207
|
} catch (err) {
|
|
1023
1208
|
console.error(`Server function error [${req.body?.id}]:`, err);
|
|
1024
1209
|
// In production, do not send full err.message to avoid leaks
|
package/dinou/esbuild/dev.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { regex as assetRegex } from "../core/asset-extensions.js";
|
|
|
8
8
|
import normalizePath from "./helpers-esbuild/normalize-path.mjs";
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
10
10
|
import { updateManifestForModule } from "./helpers-esbuild/update-manifest-for-module.mjs";
|
|
11
|
+
import { useServerRegex } from "../constants.js";
|
|
11
12
|
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -146,6 +147,16 @@ const debounceRecreateAndReload = () => {
|
|
|
146
147
|
}, 300);
|
|
147
148
|
};
|
|
148
149
|
|
|
150
|
+
let reloadTimer = null;
|
|
151
|
+
const debounceReload = () => {
|
|
152
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
153
|
+
reloadTimer = setTimeout(() => {
|
|
154
|
+
if (hmrEngine.value) {
|
|
155
|
+
hmrEngine.value.broadcastMessage({ type: "reload" });
|
|
156
|
+
}
|
|
157
|
+
}, 100);
|
|
158
|
+
};
|
|
159
|
+
|
|
149
160
|
watcher.on("add", async (file) => {
|
|
150
161
|
const ext = path.extname(file);
|
|
151
162
|
if (codeCssRegex.test(ext) || assetRegex.test(ext)) {
|
|
@@ -198,7 +209,13 @@ function existsInManifest(resolvedFile, manifest) {
|
|
|
198
209
|
watcher.on("change", async (file) => {
|
|
199
210
|
const resolvedFile = normalizePath(path.resolve(file));
|
|
200
211
|
const oldManifest = { ...manifest };
|
|
212
|
+
const oldEntryKeys = JSON.stringify(Object.keys(entryPoints).sort());
|
|
213
|
+
|
|
201
214
|
await updateEntriesAndComponents();
|
|
215
|
+
|
|
216
|
+
const newEntryKeys = JSON.stringify(Object.keys(entryPoints).sort());
|
|
217
|
+
const entryPointsChanged = oldEntryKeys !== newEntryKeys;
|
|
218
|
+
|
|
202
219
|
// Check if changed file is a client component
|
|
203
220
|
const isClientModule = clientComponentsPaths.includes(resolvedFile);
|
|
204
221
|
const isServerModule = currentServerFiles.has(resolvedFile);
|
|
@@ -215,6 +232,18 @@ watcher.on("change", async (file) => {
|
|
|
215
232
|
changedIds.add(resolvedFile);
|
|
216
233
|
return;
|
|
217
234
|
}
|
|
218
|
-
|
|
219
|
-
|
|
235
|
+
|
|
236
|
+
// If it's a Server Action module ("use server"), do not reload the browser
|
|
237
|
+
const fileContent = await fs.readFile(resolvedFile, "utf8").catch(() => "");
|
|
238
|
+
const isServerAction = useServerRegex.test(fileContent.trim());
|
|
239
|
+
if (isServerAction) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Only recreate context and reload if entry points actually changed or it's a CSS file
|
|
244
|
+
if (entryPointsChanged || file.endsWith(".css") || file.endsWith(".scss")) {
|
|
245
|
+
debounceRecreateAndReload();
|
|
246
|
+
} else {
|
|
247
|
+
debounceReload();
|
|
248
|
+
}
|
|
220
249
|
});
|
|
@@ -150,41 +150,6 @@ export default async function getEsbuildEntries({
|
|
|
150
150
|
return fileName.startsWith("page.") || fileName.startsWith("layout.");
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function isAsyncDefaultExport(code) {
|
|
154
|
-
const ast = parser.parse(code, {
|
|
155
|
-
sourceType: "module",
|
|
156
|
-
plugins: ["jsx", "typescript"],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
let isAsync = false;
|
|
160
|
-
|
|
161
|
-
traverse.default(ast, {
|
|
162
|
-
ExportDefaultDeclaration(path) {
|
|
163
|
-
let decl = path.node.declaration;
|
|
164
|
-
|
|
165
|
-
if (decl.type === "Identifier") {
|
|
166
|
-
const binding = path.scope.getBinding(decl.name);
|
|
167
|
-
if (binding && binding.path) {
|
|
168
|
-
decl = binding.path.node;
|
|
169
|
-
if (decl.type === "VariableDeclarator") {
|
|
170
|
-
decl = decl.init;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
decl &&
|
|
177
|
-
(decl.type === "FunctionDeclaration" ||
|
|
178
|
-
decl.type === "ArrowFunctionExpression" ||
|
|
179
|
-
decl.type === "FunctionExpression")
|
|
180
|
-
) {
|
|
181
|
-
isAsync = decl.async;
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
return isAsync;
|
|
187
|
-
}
|
|
188
153
|
|
|
189
154
|
const files = await glob(["**/*.{js,jsx,ts,tsx}"], {
|
|
190
155
|
cwd: srcDir,
|
|
@@ -222,11 +187,6 @@ export default async function getEsbuildEntries({
|
|
|
222
187
|
}
|
|
223
188
|
});
|
|
224
189
|
} else if (isPageOrLayout(absPath)) {
|
|
225
|
-
if (!isAsyncDefaultExport(code)) {
|
|
226
|
-
console.warn(
|
|
227
|
-
`[react-client-manifest] The file ${normalizedPath} is a page or layout without "use client" directive, but its default export is not an async function.`,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
190
|
serverModules.add(normalizedPath);
|
|
231
191
|
try {
|
|
232
192
|
const { imports, assets, csss } = await getImportsAndAssetsAndCsss(
|
package/dinou/index.mjs
CHANGED
|
@@ -1,13 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import { getContext } from "./core/request-context.js";
|
|
2
|
+
import {
|
|
3
|
+
usePathname,
|
|
4
|
+
useSearchParams,
|
|
5
|
+
useRouter,
|
|
6
|
+
useNavigationLoading,
|
|
7
|
+
} from "./core/navigation.js";
|
|
8
|
+
import { redirect } from "./core/redirect.jsx";
|
|
9
|
+
import { ClientRedirect } from "./core/client-redirect.jsx";
|
|
10
|
+
import { Link } from "./core/link.jsx";
|
|
3
11
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
const dinou = {
|
|
13
|
+
getContext,
|
|
14
|
+
usePathname,
|
|
15
|
+
useSearchParams,
|
|
16
|
+
useRouter,
|
|
17
|
+
useNavigationLoading,
|
|
18
|
+
redirect,
|
|
19
|
+
ClientRedirect,
|
|
20
|
+
Link,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
getContext,
|
|
25
|
+
usePathname,
|
|
26
|
+
useSearchParams,
|
|
27
|
+
useRouter,
|
|
28
|
+
useNavigationLoading,
|
|
29
|
+
redirect,
|
|
30
|
+
ClientRedirect,
|
|
31
|
+
Link,
|
|
32
|
+
};
|
|
12
33
|
|
|
13
34
|
export default dinou;
|