dinou 4.0.16 → 5.0.1
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 +15 -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/navigation.js +42 -27
- 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 +2 -2
- 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
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const { fork } = require("child_process");
|
|
2
|
+
const { fork } = require("child_process");
|
|
3
3
|
const url = require("url");
|
|
4
|
-
const
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const getJSX = require("./get-jsx.js");
|
|
6
|
+
const { requestStorage } = require("./request-context.js");
|
|
7
|
+
|
|
8
|
+
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
9
|
+
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
10
|
+
|
|
11
|
+
const { renderToPipeableStream } = isWebpack
|
|
12
|
+
? require("react-server-dom-webpack/server")
|
|
13
|
+
: require("@roggc/react-server-dom-esm/server");
|
|
14
|
+
|
|
15
|
+
const manifestPath = path.resolve(
|
|
16
|
+
process.cwd(),
|
|
17
|
+
isWebpack
|
|
18
|
+
? (isDevelopment ? "public/react-client-manifest.json" : "dist3/react-client-manifest.json")
|
|
19
|
+
: "react_client_manifest/react-client-manifest.json"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
let cachedManifest = null;
|
|
23
|
+
function getManifest() {
|
|
24
|
+
if (!isDevelopment && cachedManifest) return cachedManifest;
|
|
25
|
+
try {
|
|
26
|
+
const content = fs.readFileSync(manifestPath, "utf8");
|
|
27
|
+
const parsed = JSON.parse(content);
|
|
28
|
+
if (parsed && Object.keys(parsed).length > 0) {
|
|
29
|
+
cachedManifest = parsed;
|
|
30
|
+
}
|
|
31
|
+
return cachedManifest || parsed;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (cachedManifest) {
|
|
34
|
+
console.warn("Using cached client manifest due to read error:", e.message);
|
|
35
|
+
return cachedManifest;
|
|
36
|
+
}
|
|
37
|
+
console.error("Error reading client manifest:", e);
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
5
41
|
|
|
6
42
|
function toFileUrl(p) {
|
|
7
|
-
// Converts to file://, cross-platform
|
|
8
43
|
return url.pathToFileURL(p).href;
|
|
9
44
|
}
|
|
10
45
|
|
|
@@ -13,22 +48,123 @@ const registerLoaderPath = toFileUrl(
|
|
|
13
48
|
);
|
|
14
49
|
const renderHtmlPath = path.resolve(__dirname, "render-html.js");
|
|
15
50
|
|
|
16
|
-
// ----------------------------------------------------
|
|
17
|
-
// 💡 WHITELIST STRATEGY
|
|
18
|
-
// ----------------------------------------------------
|
|
19
|
-
|
|
20
|
-
// 1. Define ESSENTIAL Node.js flags that the child must have.
|
|
21
|
-
// (We leave the list empty to avoid inheriting problematic flags)
|
|
22
51
|
const ESSENTIAL_NODE_ARGS = [];
|
|
23
|
-
|
|
24
|
-
// 2. Add the loader --import, which is the only confirmed necessity.
|
|
25
52
|
const loaderArg = `--import=${registerLoaderPath}`;
|
|
26
53
|
const childExecArgv = ESSENTIAL_NODE_ARGS.concat(loaderArg);
|
|
27
54
|
|
|
28
|
-
// ----------------------------------------------------
|
|
29
|
-
|
|
30
55
|
const { resolveRelativeUrl } = require("./url-resolver");
|
|
31
56
|
|
|
57
|
+
function createParentResponseWrapper(reqPath, res, child) {
|
|
58
|
+
let hasRedirected = false;
|
|
59
|
+
|
|
60
|
+
const safeRedirect = (targetUrl) => {
|
|
61
|
+
if (hasRedirected) return;
|
|
62
|
+
hasRedirected = true;
|
|
63
|
+
|
|
64
|
+
const resolvedUrl = resolveRelativeUrl(targetUrl, reqPath);
|
|
65
|
+
let finalUrl = "/";
|
|
66
|
+
if (
|
|
67
|
+
typeof resolvedUrl === "string" &&
|
|
68
|
+
resolvedUrl.startsWith("/") &&
|
|
69
|
+
!resolvedUrl.startsWith("//")
|
|
70
|
+
) {
|
|
71
|
+
finalUrl = resolvedUrl;
|
|
72
|
+
} else {
|
|
73
|
+
console.warn(
|
|
74
|
+
`[Dinou Security] Blocked unsafe redirect to: ${targetUrl}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (res.headersSent) {
|
|
79
|
+
console.log(
|
|
80
|
+
`[Dinou] Streaming active. Redirecting via JavaScript to: ${finalUrl}`,
|
|
81
|
+
);
|
|
82
|
+
const safeUrl = JSON.stringify(finalUrl);
|
|
83
|
+
res.write(`<script>window.location.href = ${safeUrl};</script>`);
|
|
84
|
+
res.end();
|
|
85
|
+
child.stdout.unpipe(res);
|
|
86
|
+
child.kill();
|
|
87
|
+
} else {
|
|
88
|
+
res.redirect(302, finalUrl);
|
|
89
|
+
child.stdout.unpipe(res);
|
|
90
|
+
child.kill();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
setHeader: (name, value) => {
|
|
96
|
+
if (res.headersSent) {
|
|
97
|
+
console.warn(
|
|
98
|
+
`[Dinou Warning] Cannot set header '${name}' because streaming started.`,
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
res.setHeader(name, value);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
cookie: (name, value, options) => {
|
|
105
|
+
if (res.headersSent) {
|
|
106
|
+
if (options && options.httpOnly) {
|
|
107
|
+
console.error(
|
|
108
|
+
`[Dinou Error] Cannot set HttpOnly cookie '${name}' because streaming has already started.`,
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(
|
|
113
|
+
`[Dinou] Streaming active. Setting cookie '${name}' via JS.`,
|
|
114
|
+
);
|
|
115
|
+
let cookieStr = `${name}=${encodeURIComponent(value)}`;
|
|
116
|
+
if (options) {
|
|
117
|
+
if (options.path) cookieStr += `; path=${options.path}`;
|
|
118
|
+
if (options.domain) cookieStr += `; domain=${options.domain}`;
|
|
119
|
+
if (options.maxAge) cookieStr += `; max-age=${options.maxAge}`;
|
|
120
|
+
if (options.expires)
|
|
121
|
+
cookieStr += `; expires=${new Date(options.expires).toUTCString()}`;
|
|
122
|
+
if (options.secure) cookieStr += `; secure`;
|
|
123
|
+
if (options.sameSite)
|
|
124
|
+
cookieStr += `; samesite=${options.sameSite}`;
|
|
125
|
+
}
|
|
126
|
+
const safeCookieStr = JSON.stringify(cookieStr);
|
|
127
|
+
res.write(`<script>document.cookie = ${safeCookieStr};</script>`);
|
|
128
|
+
} else {
|
|
129
|
+
res.cookie(name, value, options);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
clearCookie: (name, options) => {
|
|
133
|
+
if (res.headersSent) {
|
|
134
|
+
console.log(
|
|
135
|
+
`[Dinou] Streaming active. Clearing cookie '${name}' via JS.`,
|
|
136
|
+
);
|
|
137
|
+
let cookieStr = `${name}=; Max-Age=0`;
|
|
138
|
+
const path = options?.path || "/";
|
|
139
|
+
cookieStr += `; path=${path}`;
|
|
140
|
+
if (options) {
|
|
141
|
+
if (options.domain) cookieStr += `; domain=${options.domain}`;
|
|
142
|
+
if (options.secure) cookieStr += `; secure`;
|
|
143
|
+
if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
|
|
144
|
+
}
|
|
145
|
+
cookieStr += ";";
|
|
146
|
+
const safeCookieStr = JSON.stringify(cookieStr);
|
|
147
|
+
res.write(`<script>document.cookie = ${safeCookieStr};</script>`);
|
|
148
|
+
} else {
|
|
149
|
+
res.clearCookie(name, options);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
redirect: (arg1, arg2) => {
|
|
153
|
+
const url = arg2 || arg1;
|
|
154
|
+
safeRedirect(url);
|
|
155
|
+
},
|
|
156
|
+
status: (code) => {
|
|
157
|
+
if (res.headersSent) {
|
|
158
|
+
console.warn(
|
|
159
|
+
`[Dinou Warning] HTTP status '${code}' ignored because streaming started.`,
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
res.status(code);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
32
168
|
function renderAppToHtml(
|
|
33
169
|
reqPath,
|
|
34
170
|
paramsString,
|
|
@@ -36,31 +172,62 @@ function renderAppToHtml(
|
|
|
36
172
|
res,
|
|
37
173
|
capturedStatus = null,
|
|
38
174
|
isDynamic = false,
|
|
175
|
+
forceNotFound = false,
|
|
39
176
|
) {
|
|
40
|
-
const jsxJson = getJSXJSON(reqPath);
|
|
41
|
-
const hasJsxJson = hasJSXJSON(reqPath);
|
|
42
|
-
// We replicate the array of positional arguments passed to the script
|
|
43
|
-
// [renderHtmlPath, reqPath, paramsString, cookiesString]
|
|
44
|
-
const scriptArgs = [
|
|
45
|
-
reqPath,
|
|
46
|
-
paramsString,
|
|
47
|
-
contextForChild ? JSON.stringify(contextForChild) : JSON.stringify({}),
|
|
48
|
-
isDynamic ? "true" : "false",
|
|
49
|
-
hasJsxJson ? "true" : "false",
|
|
50
|
-
JSON.stringify(hasJsxJson ? jsxJson : {}),
|
|
51
|
-
];
|
|
52
|
-
|
|
53
177
|
const child = fork(
|
|
54
|
-
renderHtmlPath,
|
|
55
|
-
|
|
178
|
+
renderHtmlPath,
|
|
179
|
+
[
|
|
180
|
+
reqPath,
|
|
181
|
+
paramsString,
|
|
182
|
+
contextForChild ? JSON.stringify(contextForChild) : JSON.stringify({}),
|
|
183
|
+
isDynamic ? "true" : "false",
|
|
184
|
+
],
|
|
56
185
|
{
|
|
57
|
-
// ⬅️ CHANGE 3: Apply Whitelist to execArgv, resetting inherited options
|
|
58
186
|
execArgv: childExecArgv,
|
|
59
|
-
|
|
60
|
-
stdio: ["ignore", "pipe", "pipe", "ipc"], // stdin, stdout, stderr, ipc
|
|
187
|
+
stdio: ["ignore", "pipe", "pipe", "ipc", "pipe"], // fd 4 is the RSC stream pipe
|
|
61
188
|
},
|
|
62
189
|
);
|
|
63
190
|
|
|
191
|
+
const query = JSON.parse(paramsString || "{}");
|
|
192
|
+
const rscPath = path.resolve(process.cwd(), "dist2", reqPath.replace(/^\//, ""), "rsc.rsc");
|
|
193
|
+
const hasStaticRsc = !isDynamic && fs.existsSync(rscPath);
|
|
194
|
+
|
|
195
|
+
if (hasStaticRsc) {
|
|
196
|
+
try {
|
|
197
|
+
const rscBuffer = fs.readFileSync(rscPath);
|
|
198
|
+
child.stdio[4].write(rscBuffer);
|
|
199
|
+
child.stdio[4].end();
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error(`[Dinou] Failed to read static RSC from ${rscPath}:`, err.message);
|
|
202
|
+
if (child.stdio[4]) child.stdio[4].destroy();
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
// Dynamic SSR render: render RSC in parent and pipe to fd 4
|
|
206
|
+
const isNotFound = {};
|
|
207
|
+
const parentRes = createParentResponseWrapper(reqPath, res, child);
|
|
208
|
+
const context = {
|
|
209
|
+
req: contextForChild ? contextForChild.req : {},
|
|
210
|
+
res: parentRes,
|
|
211
|
+
};
|
|
212
|
+
requestStorage.run(context, () => {
|
|
213
|
+
getJSX(reqPath, query, isNotFound, isDevelopment, forceNotFound)
|
|
214
|
+
.then((jsx) => {
|
|
215
|
+
if (isNotFound.value) {
|
|
216
|
+
parentRes.status(404);
|
|
217
|
+
}
|
|
218
|
+
const manifest = getManifest();
|
|
219
|
+
const { pipe } = isWebpack
|
|
220
|
+
? renderToPipeableStream(jsx, manifest)
|
|
221
|
+
: renderToPipeableStream(jsx, url.pathToFileURL(process.cwd()).href + "/");
|
|
222
|
+
pipe(child.stdio[4]);
|
|
223
|
+
})
|
|
224
|
+
.catch((err) => {
|
|
225
|
+
console.error("Error rendering JSX in parent renderAppToHtml:", err);
|
|
226
|
+
if (child.stdio[4]) child.stdio[4].destroy();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
64
231
|
// 💡 on('message') Implementation (IPC Channel)
|
|
65
232
|
// ----------------------------------------------------
|
|
66
233
|
child.on("message", (message) => {
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
global.__webpack_require__ = function (id) {
|
|
3
|
+
if (global.__webpack_require_map__ && global.__webpack_require_map__[id]) {
|
|
4
|
+
return require(global.__webpack_require_map__[id]);
|
|
5
|
+
}
|
|
6
|
+
if (typeof id === "string" && id.startsWith("./")) {
|
|
7
|
+
id = path.resolve(process.cwd(), id);
|
|
8
|
+
}
|
|
9
|
+
return require(id);
|
|
10
|
+
};
|
|
11
|
+
global.__webpack_chunk_load__ = function (chunkId) {
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
};
|
|
14
|
+
|
|
1
15
|
require("./register-paths");
|
|
2
|
-
require("./register-hooks.js");
|
|
3
|
-
const babelPluginRegisterImports = require("./babel-plugin-register-imports.js");
|
|
4
16
|
const babelRegister = require("@babel/register");
|
|
5
17
|
babelRegister({
|
|
6
18
|
ignore: [/node_modules[\\/](?!dinou)/],
|
|
@@ -8,7 +20,7 @@ babelRegister({
|
|
|
8
20
|
["@babel/preset-react", { runtime: "automatic" }],
|
|
9
21
|
"@babel/preset-typescript",
|
|
10
22
|
],
|
|
11
|
-
plugins: [
|
|
23
|
+
plugins: ["@babel/transform-modules-commonjs"],
|
|
12
24
|
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
|
13
25
|
});
|
|
14
26
|
const addHook = require("./asset-require-hook.js");
|
|
@@ -26,9 +38,7 @@ addHook({
|
|
|
26
38
|
const getAssetFromManifest = require("./get-asset-from-manifest.js");
|
|
27
39
|
const { renderToPipeableStream } = require("react-dom/server");
|
|
28
40
|
const getJSX = require("./get-jsx");
|
|
29
|
-
const getSSGJSX = require("./get-ssg-jsx.js");
|
|
30
41
|
const { getErrorJSX } = require("./get-error-jsx");
|
|
31
|
-
const { renderJSXToClientJSX } = require("./render-jsx-to-client-jsx");
|
|
32
42
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
33
43
|
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
34
44
|
const { requestStorage } = require("./request-context.js");
|
|
@@ -124,13 +134,121 @@ function writeErrorOutput(error, isProd) {
|
|
|
124
134
|
);
|
|
125
135
|
}
|
|
126
136
|
|
|
137
|
+
function getImportMapHtml() {
|
|
138
|
+
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
139
|
+
if (isWebpack) return "";
|
|
140
|
+
|
|
141
|
+
const fs = require("fs");
|
|
142
|
+
const path = require("path");
|
|
143
|
+
const { pathToFileURL } = require("url");
|
|
144
|
+
|
|
145
|
+
const manifestPath = path.resolve(
|
|
146
|
+
process.cwd(),
|
|
147
|
+
"react_client_manifest/react-client-manifest.json"
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(manifestPath)) {
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
156
|
+
const imports = {};
|
|
157
|
+
const moduleBasePath = pathToFileURL(process.cwd()).href + "/";
|
|
158
|
+
|
|
159
|
+
for (const [key, val] of Object.entries(manifest)) {
|
|
160
|
+
let specifier = key;
|
|
161
|
+
if (specifier.startsWith(moduleBasePath)) {
|
|
162
|
+
specifier = specifier.slice(moduleBasePath.length);
|
|
163
|
+
}
|
|
164
|
+
const hashIdx = specifier.indexOf("#");
|
|
165
|
+
if (hashIdx !== -1) {
|
|
166
|
+
specifier = specifier.slice(0, hashIdx);
|
|
167
|
+
}
|
|
168
|
+
imports[specifier] = val.id;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return `<script type="importmap">{"imports":${JSON.stringify(imports)}}</script><script>(function(){const map=document.querySelector('script[type="importmap"]');if(map)map.remove();if(document.currentScript)document.currentScript.remove();})();</script>`;
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error("Error generating importmap:", err);
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let cachedSsrManifest = null;
|
|
179
|
+
let cachedRequireMap = null;
|
|
180
|
+
function getSsrManifest() {
|
|
181
|
+
if (!isDevelopment && cachedSsrManifest) return cachedSsrManifest;
|
|
182
|
+
try {
|
|
183
|
+
const fs = require("fs");
|
|
184
|
+
const path = require("path");
|
|
185
|
+
const ssrManifestPath = path.resolve(
|
|
186
|
+
process.cwd(),
|
|
187
|
+
isDevelopment ? "public/react-ssr-manifest.json" : "dist3/react-ssr-manifest.json"
|
|
188
|
+
);
|
|
189
|
+
const clientManifestPath = path.resolve(
|
|
190
|
+
process.cwd(),
|
|
191
|
+
isDevelopment ? "public/react-client-manifest.json" : "dist3/react-client-manifest.json"
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const ssrContent = fs.readFileSync(ssrManifestPath, "utf8");
|
|
195
|
+
const ssrManifest = JSON.parse(ssrContent);
|
|
196
|
+
|
|
197
|
+
if (fs.existsSync(clientManifestPath)) {
|
|
198
|
+
const clientContent = fs.readFileSync(clientManifestPath, "utf8");
|
|
199
|
+
const clientManifest = JSON.parse(clientContent);
|
|
200
|
+
|
|
201
|
+
const { fileURLToPath } = require("url");
|
|
202
|
+
const requireMap = {};
|
|
203
|
+
for (const [fileUrl, entry] of Object.entries(clientManifest)) {
|
|
204
|
+
if (entry && entry.id !== undefined) {
|
|
205
|
+
try {
|
|
206
|
+
requireMap[entry.id] = fileURLToPath(fileUrl);
|
|
207
|
+
} catch {}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
global.__webpack_require_map__ = requireMap;
|
|
211
|
+
cachedRequireMap = requireMap;
|
|
212
|
+
|
|
213
|
+
if (ssrManifest.moduleMap) {
|
|
214
|
+
for (const [modId, exports] of Object.entries(ssrManifest.moduleMap)) {
|
|
215
|
+
for (const [expName, expData] of Object.entries(exports)) {
|
|
216
|
+
if (expData && expData.specifier) {
|
|
217
|
+
const clientEntry = clientManifest[expData.specifier];
|
|
218
|
+
if (clientEntry) {
|
|
219
|
+
expData.id = clientEntry.id;
|
|
220
|
+
expData.chunks = clientEntry.chunks;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
cachedSsrManifest = ssrManifest;
|
|
229
|
+
return cachedSsrManifest;
|
|
230
|
+
} catch (e) {
|
|
231
|
+
if (cachedSsrManifest) {
|
|
232
|
+
console.warn("Using cached SSR manifest due to read error:", e.message);
|
|
233
|
+
if (cachedRequireMap) {
|
|
234
|
+
global.__webpack_require_map__ = cachedRequireMap;
|
|
235
|
+
}
|
|
236
|
+
return cachedSsrManifest;
|
|
237
|
+
}
|
|
238
|
+
console.error("Error reading SSR manifest:", e);
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { createFromNodeStream } = isWebpack
|
|
244
|
+
? require("react-server-dom-webpack/client")
|
|
245
|
+
: require("@roggc/react-server-dom-esm/client");
|
|
246
|
+
|
|
127
247
|
async function renderToStream(
|
|
128
248
|
reqPath,
|
|
129
249
|
query,
|
|
130
250
|
serializedBox,
|
|
131
251
|
isDynamic,
|
|
132
|
-
hasJsxJson,
|
|
133
|
-
jsxJson,
|
|
134
252
|
) {
|
|
135
253
|
const context = {
|
|
136
254
|
req: serializedBox.req,
|
|
@@ -138,21 +256,15 @@ async function renderToStream(
|
|
|
138
256
|
};
|
|
139
257
|
await requestStorage.run(context, async () => {
|
|
140
258
|
try {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
renderJSXToClientJSX(
|
|
151
|
-
await getJSX(reqPath, query, isNotFound, isDevelopment),
|
|
152
|
-
));
|
|
153
|
-
if (isNotFound.value) {
|
|
154
|
-
context.res.status(404);
|
|
155
|
-
}
|
|
259
|
+
const { createReadStream } = require("fs");
|
|
260
|
+
const rscStream = createReadStream(null, { fd: 4 });
|
|
261
|
+
const { pathToFileURL } = require("url");
|
|
262
|
+
const baseUrl = pathToFileURL(process.cwd()).href + "/";
|
|
263
|
+
|
|
264
|
+
const jsx = isWebpack
|
|
265
|
+
? await createFromNodeStream(rscStream, getSsrManifest())
|
|
266
|
+
: await createFromNodeStream(rscStream, baseUrl, baseUrl);
|
|
267
|
+
|
|
156
268
|
const stream = renderToPipeableStream(jsx, {
|
|
157
269
|
onError(error) {
|
|
158
270
|
process.nextTick(async () => {
|
|
@@ -181,6 +293,11 @@ async function renderToStream(
|
|
|
181
293
|
|
|
182
294
|
const errorStream = renderToPipeableStream(errorJSX, {
|
|
183
295
|
onShellReady() {
|
|
296
|
+
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
297
|
+
if (!isWebpack) {
|
|
298
|
+
const importMapHtml = getImportMapHtml();
|
|
299
|
+
process.stdout.write(importMapHtml);
|
|
300
|
+
}
|
|
184
301
|
errorStream.pipe(process.stdout);
|
|
185
302
|
},
|
|
186
303
|
onError(err) {
|
|
@@ -216,6 +333,11 @@ async function renderToStream(
|
|
|
216
333
|
});
|
|
217
334
|
},
|
|
218
335
|
onShellReady() {
|
|
336
|
+
const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
|
|
337
|
+
if (!isWebpack) {
|
|
338
|
+
const importMapHtml = getImportMapHtml();
|
|
339
|
+
process.stdout.write(importMapHtml);
|
|
340
|
+
}
|
|
219
341
|
stream.pipe(process.stdout);
|
|
220
342
|
},
|
|
221
343
|
bootstrapModules: isDevelopment
|
|
@@ -250,8 +372,6 @@ const reqPath = process.argv[2] || "/";
|
|
|
250
372
|
const query = JSON.parse(process.argv[3] || "{}");
|
|
251
373
|
const serializedBox = JSON.parse(process.argv[4] || "{}");
|
|
252
374
|
const isDynamic = process.argv[5] === "true";
|
|
253
|
-
const hasJsxJson = process.argv[6] === "true";
|
|
254
|
-
const jsxJson = JSON.parse(process.argv[7] || "{}");
|
|
255
375
|
|
|
256
376
|
process.on("uncaughtException", (error) => {
|
|
257
377
|
process.stdout.write(formatErrorHtml(error));
|
|
@@ -281,8 +401,6 @@ renderToStream(
|
|
|
281
401
|
query,
|
|
282
402
|
serializedBox,
|
|
283
403
|
isDynamic,
|
|
284
|
-
hasJsxJson,
|
|
285
|
-
jsxJson,
|
|
286
404
|
).catch((err) => {
|
|
287
405
|
console.error("❌ Fatal error starting render stream:", err);
|
|
288
406
|
process.exit(1);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// Function to check if a component is a client component
|
|
2
2
|
function isClientComponent(type) {
|
|
3
|
-
if (
|
|
3
|
+
if (!type) {
|
|
4
4
|
return false;
|
|
5
5
|
}
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return true;
|
|
6
|
+
const CLIENT_REFERENCE = Symbol.for("react.client.reference");
|
|
7
|
+
return (
|
|
8
|
+
(typeof type === "function" && type.$$typeof === CLIENT_REFERENCE) ||
|
|
9
|
+
(typeof type === "object" && type.$$typeof === CLIENT_REFERENCE)
|
|
10
|
+
);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
function renderJSXToClientJSX(jsx, key = null) {
|
|
@@ -4,15 +4,20 @@ const DINOU_CONTEXT_KEY = Symbol.for("dinou.request.context.storage");
|
|
|
4
4
|
let requestStorage;
|
|
5
5
|
|
|
6
6
|
if (typeof window === "undefined") {
|
|
7
|
-
const nodeRequire =
|
|
7
|
+
const nodeRequire =
|
|
8
|
+
typeof module !== "undefined" && typeof module.require === "function"
|
|
9
|
+
? module.require.bind(module)
|
|
10
|
+
: null;
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
if (nodeRequire) {
|
|
13
|
+
const { AsyncLocalStorage } = nodeRequire("node:async_hooks");
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
if (!global[DINOU_CONTEXT_KEY]) {
|
|
16
|
+
global[DINOU_CONTEXT_KEY] = new AsyncLocalStorage();
|
|
17
|
+
}
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
requestStorage = global[DINOU_CONTEXT_KEY];
|
|
20
|
+
}
|
|
16
21
|
} else {
|
|
17
22
|
requestStorage = {
|
|
18
23
|
run: (store, callback) => callback(),
|
|
@@ -6,41 +6,44 @@ const { buildStaticPage } = require("./build-static-pages");
|
|
|
6
6
|
const generateStaticRSC = require("./generate-static-rsc");
|
|
7
7
|
const { safeRename } = require("./safe-rename");
|
|
8
8
|
const { updateStatus } = require("./status-manifest");
|
|
9
|
-
const { getJSXJSON } = require("./jsx-json");
|
|
10
9
|
|
|
11
10
|
const regenerating = new Set();
|
|
12
11
|
|
|
13
12
|
function revalidating(reqPath, isDynamicFromServer) {
|
|
14
13
|
const dist2Folder = path.resolve(process.cwd(), "dist2");
|
|
15
|
-
const
|
|
16
|
-
if (!jsxJSON) return;
|
|
14
|
+
const metadataPath = path.join(dist2Folder, reqPath, "metadata.json");
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
if (regenerating.has(reqPath)) return;
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
fs.readFile(metadataPath, "utf8")
|
|
19
|
+
.then((content) => {
|
|
20
|
+
const metadata = JSON.parse(content);
|
|
21
|
+
const { revalidate, generatedAt } = metadata;
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
path.join(dist2Folder, reqPath, "index.html"),
|
|
30
|
-
path.join(dist2Folder, reqPath, "index._old.html")
|
|
31
|
-
);
|
|
32
|
-
if (existsSync(path.join(dist2Folder, reqPath, "rsc.rsc")))
|
|
33
|
-
copyFileSync(
|
|
34
|
-
path.join(dist2Folder, reqPath, "rsc.rsc"),
|
|
35
|
-
path.join(dist2Folder, reqPath, "rsc._old.rsc")
|
|
36
|
-
);
|
|
37
|
-
} catch (e) {}
|
|
23
|
+
const isExpired =
|
|
24
|
+
typeof revalidate === "number" &&
|
|
25
|
+
revalidate > 0 &&
|
|
26
|
+
Date.now() > generatedAt + revalidate;
|
|
38
27
|
|
|
39
|
-
|
|
28
|
+
if (isExpired && !regenerating.has(reqPath)) {
|
|
29
|
+
try {
|
|
30
|
+
if (existsSync(path.join(dist2Folder, reqPath, "index.html")))
|
|
31
|
+
copyFileSync(
|
|
32
|
+
path.join(dist2Folder, reqPath, "index.html"),
|
|
33
|
+
path.join(dist2Folder, reqPath, "index._old.html")
|
|
34
|
+
);
|
|
35
|
+
if (existsSync(path.join(dist2Folder, reqPath, "rsc.rsc")))
|
|
36
|
+
copyFileSync(
|
|
37
|
+
path.join(dist2Folder, reqPath, "rsc.rsc"),
|
|
38
|
+
path.join(dist2Folder, reqPath, "rsc._old.rsc")
|
|
39
|
+
);
|
|
40
|
+
} catch (e) {}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
regenerating.add(reqPath);
|
|
43
|
+
|
|
44
|
+
(async () => {
|
|
45
|
+
try {
|
|
46
|
+
console.log(`[ISR] Starting regeneration for ${reqPath}...`);
|
|
44
47
|
const isDynamic = {};
|
|
45
48
|
await buildStaticPage(reqPath, isDynamic);
|
|
46
49
|
if (isDynamic.value) {
|
|
@@ -80,6 +83,7 @@ function revalidating(reqPath, isDynamicFromServer) {
|
|
|
80
83
|
}
|
|
81
84
|
})();
|
|
82
85
|
}
|
|
86
|
+
}).catch((err) => {});
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
module.exports = { revalidating, regenerating };
|