dinou 1.6.0 → 1.7.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/cli.js +11 -7
- package/dinou/generate-static-page.js +34 -0
- package/dinou/generate-static-pages.js +38 -0
- package/dinou/generate-static-rsc.js +45 -0
- package/dinou/generate-static-rscs.js +48 -0
- package/dinou/generate-static.js +23 -0
- package/dinou/get-jsx.js +2 -102
- package/dinou/get-ssg-jsx-or-jsx.js +11 -0
- package/dinou/get-ssg-jsx.js +80 -0
- package/dinou/get-static-paths.js +32 -0
- package/dinou/render-app-to-html.js +42 -0
- package/dinou/render-html.js +2 -1
- package/dinou/revalidating.js +32 -0
- package/dinou/server.js +84 -67
- package/eject.js +4 -9
- package/package.json +3 -3
- package/webpack.config.js +23 -10
- package/dinou/ssg.js +0 -27
package/cli.js
CHANGED
|
@@ -18,13 +18,18 @@ const runCommand = (command, options = {}) => {
|
|
|
18
18
|
|
|
19
19
|
program
|
|
20
20
|
.command("dev")
|
|
21
|
-
.description("Starts
|
|
21
|
+
.description("Starts")
|
|
22
22
|
.action(() => {
|
|
23
|
-
console.log("Starting
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
)
|
|
23
|
+
console.log("Starting...");
|
|
24
|
+
const startExpress = `node --conditions react-server ${path.join(
|
|
25
|
+
dinouPath,
|
|
26
|
+
"server.js"
|
|
27
|
+
)}`;
|
|
28
|
+
const startDevServer = `webpack serve --config ${path.join(
|
|
29
|
+
__dirname,
|
|
30
|
+
"webpack.config.js"
|
|
31
|
+
)}`;
|
|
32
|
+
runCommand(`npx concurrently "${startExpress}" "${startDevServer}"`);
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
program
|
|
@@ -32,7 +37,6 @@ program
|
|
|
32
37
|
.description("Builds the app for production")
|
|
33
38
|
.action(() => {
|
|
34
39
|
console.log("Building the app...");
|
|
35
|
-
runCommand(`node ${path.join(dinouPath, "ssg.js")}`);
|
|
36
40
|
const configPath = path.join(__dirname, "webpack.config.js");
|
|
37
41
|
runCommand(
|
|
38
42
|
`cross-env NODE_ENV=production npx webpack --config ${configPath}`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { mkdirSync, createWriteStream } = require("fs");
|
|
3
|
+
const renderAppToHtml = require("./render-app-to-html.js");
|
|
4
|
+
|
|
5
|
+
const OUT_DIR = path.resolve("dist2");
|
|
6
|
+
|
|
7
|
+
async function generateStaticPage(reqPath) {
|
|
8
|
+
const finalReqPath = reqPath.endsWith("/") ? reqPath : reqPath + "/";
|
|
9
|
+
const htmlPath = path.join(OUT_DIR, finalReqPath, "index.html");
|
|
10
|
+
const query = {};
|
|
11
|
+
const paramsString = JSON.stringify(query);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
console.log("🔄 Rendering HTML for:", finalReqPath);
|
|
15
|
+
const htmlStream = await renderAppToHtml(finalReqPath, paramsString);
|
|
16
|
+
|
|
17
|
+
mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
18
|
+
const fileStream = createWriteStream(htmlPath);
|
|
19
|
+
|
|
20
|
+
await new Promise((resolve, reject) => {
|
|
21
|
+
htmlStream.pipe(fileStream);
|
|
22
|
+
htmlStream.on("end", resolve);
|
|
23
|
+
htmlStream.on("error", reject);
|
|
24
|
+
fileStream.on("error", reject);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log("✅ Generated HTML:", finalReqPath);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("❌ Error rendering HTML for:", finalReqPath);
|
|
30
|
+
console.error(error.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = generateStaticPage;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { mkdirSync, createWriteStream } = require("fs");
|
|
3
|
+
const renderAppToHtml = require("./render-app-to-html.js");
|
|
4
|
+
|
|
5
|
+
const OUT_DIR = path.resolve("dist2");
|
|
6
|
+
|
|
7
|
+
async function generateStaticPages(routes) {
|
|
8
|
+
for (const route of routes) {
|
|
9
|
+
const reqPath = route.endsWith("/") ? route : route + "/";
|
|
10
|
+
const htmlPath = path.join(OUT_DIR, reqPath, "index.html");
|
|
11
|
+
const query = {};
|
|
12
|
+
const paramsString = JSON.stringify(query);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
console.log("🔄 Rendering HTML for:", reqPath);
|
|
16
|
+
const htmlStream = await renderAppToHtml(reqPath, paramsString);
|
|
17
|
+
|
|
18
|
+
mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
19
|
+
const fileStream = createWriteStream(htmlPath);
|
|
20
|
+
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
htmlStream.pipe(fileStream);
|
|
23
|
+
htmlStream.on("end", resolve);
|
|
24
|
+
htmlStream.on("error", reject);
|
|
25
|
+
fileStream.on("error", reject);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log("✅ Generated HTML:", reqPath);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("❌ Error rendering:", reqPath);
|
|
31
|
+
console.error(error.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log("🟢 Static page generation complete.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = generateStaticPages;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { PassThrough } = require("stream");
|
|
4
|
+
const getSSGJSXOrJSX = require("./get-ssg-jsx-or-jsx.js");
|
|
5
|
+
const { renderToPipeableStream } = require("react-server-dom-webpack/server");
|
|
6
|
+
|
|
7
|
+
const OUT_DIR = path.resolve("dist2");
|
|
8
|
+
|
|
9
|
+
async function generateStaticRSC(reqPath) {
|
|
10
|
+
const finalReqPath = reqPath.endsWith("/") ? reqPath : reqPath + "/";
|
|
11
|
+
const payloadPath = path.join(OUT_DIR, finalReqPath, "rsc.rsc");
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
console.log("🔄 Generating RSC payload for:", finalReqPath);
|
|
15
|
+
const jsx = await getSSGJSXOrJSX(finalReqPath, {});
|
|
16
|
+
// console.log("✅ JSX retrieved for:", finalReqPath);
|
|
17
|
+
|
|
18
|
+
const manifest = JSON.parse(
|
|
19
|
+
fs.readFileSync(
|
|
20
|
+
path.resolve("____public____/react-client-manifest.json"),
|
|
21
|
+
"utf8"
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
fs.mkdirSync(path.dirname(payloadPath), { recursive: true });
|
|
26
|
+
|
|
27
|
+
const fileStream = fs.createWriteStream(payloadPath);
|
|
28
|
+
const passThrough = new PassThrough();
|
|
29
|
+
|
|
30
|
+
const { pipe } = renderToPipeableStream(jsx, manifest);
|
|
31
|
+
pipe(passThrough);
|
|
32
|
+
passThrough.pipe(fileStream);
|
|
33
|
+
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
fileStream.on("finish", resolve);
|
|
36
|
+
fileStream.on("error", reject);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log("✅ Generated RSC payload:", finalReqPath);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error("❌ Error generating RSC payload for:", finalReqPath, error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = generateStaticRSC;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { PassThrough } = require("stream");
|
|
4
|
+
const getSSGJSXOrJSX = require("./get-ssg-jsx-or-jsx.js");
|
|
5
|
+
const { renderToPipeableStream } = require("react-server-dom-webpack/server");
|
|
6
|
+
|
|
7
|
+
const OUT_DIR = path.resolve("dist2");
|
|
8
|
+
|
|
9
|
+
async function generateStaticRSCs(routes) {
|
|
10
|
+
const manifest = JSON.parse(
|
|
11
|
+
fs.readFileSync(
|
|
12
|
+
path.resolve("____public____/react-client-manifest.json"),
|
|
13
|
+
"utf8"
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
for (const route of routes) {
|
|
18
|
+
const reqPath = route.endsWith("/") ? route : route + "/";
|
|
19
|
+
const payloadPath = path.join(OUT_DIR, reqPath, "rsc.rsc");
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
console.log("🔄 Generating RSC payload for:", reqPath);
|
|
23
|
+
const jsx = await getSSGJSXOrJSX(reqPath, {});
|
|
24
|
+
// console.log("✅ JSX retrieved for:", reqPath);
|
|
25
|
+
fs.mkdirSync(path.dirname(payloadPath), { recursive: true });
|
|
26
|
+
|
|
27
|
+
const fileStream = fs.createWriteStream(payloadPath);
|
|
28
|
+
const passThrough = new PassThrough();
|
|
29
|
+
|
|
30
|
+
const { pipe } = renderToPipeableStream(jsx, manifest);
|
|
31
|
+
pipe(passThrough);
|
|
32
|
+
passThrough.pipe(fileStream);
|
|
33
|
+
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
fileStream.on("finish", resolve);
|
|
36
|
+
fileStream.on("error", reject);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log("✅ Generated RSC payload:", reqPath);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error("❌ Error generating RSC payload for:", reqPath, error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log("🟢 Static RSC payload generation complete.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = generateStaticRSCs;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { existsSync, rmSync } = require("fs");
|
|
3
|
+
const generateStaticRSCs = require("./generate-static-rscs");
|
|
4
|
+
const generateStaticPages = require("./generate-static-pages");
|
|
5
|
+
const getStaticPaths = require("./get-static-paths");
|
|
6
|
+
const { buildStaticPages } = require("./build-static-pages");
|
|
7
|
+
|
|
8
|
+
async function generateStatic() {
|
|
9
|
+
const distFolder2 = path.resolve(process.cwd(), "dist2");
|
|
10
|
+
|
|
11
|
+
if (existsSync(distFolder2)) {
|
|
12
|
+
rmSync(distFolder2, { recursive: true, force: true });
|
|
13
|
+
console.log("Deleted existing dist2 folder");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await buildStaticPages();
|
|
17
|
+
const routes = await getStaticPaths();
|
|
18
|
+
console.log("Static paths:", routes);
|
|
19
|
+
await generateStaticPages(routes);
|
|
20
|
+
await generateStaticRSCs(routes);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = generateStatic;
|
package/dinou/get-jsx.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const { existsSync
|
|
2
|
+
const { existsSync } = require("fs");
|
|
3
3
|
const React = require("react");
|
|
4
4
|
const {
|
|
5
5
|
getFilePathAndDynamicParams,
|
|
6
6
|
} = require("./get-file-path-and-dynamic-params");
|
|
7
|
-
const { buildStaticPage } = require("./build-static-pages");
|
|
8
7
|
|
|
9
8
|
async function getJSX(reqPath, query) {
|
|
10
9
|
const srcFolder = path.resolve(process.cwd(), "src");
|
|
@@ -148,103 +147,4 @@ async function getJSX(reqPath, query) {
|
|
|
148
147
|
return jsx;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
serialized,
|
|
153
|
-
returnUndefined = { value: false }
|
|
154
|
-
) {
|
|
155
|
-
// Check if serialized is a React element object
|
|
156
|
-
if (
|
|
157
|
-
serialized &&
|
|
158
|
-
typeof serialized === "object" &&
|
|
159
|
-
"type" in serialized &&
|
|
160
|
-
"props" in serialized
|
|
161
|
-
) {
|
|
162
|
-
const { type, modulePath, props } = serialized;
|
|
163
|
-
let Component;
|
|
164
|
-
if (modulePath) {
|
|
165
|
-
try {
|
|
166
|
-
const module = require(path.resolve(process.cwd(), modulePath));
|
|
167
|
-
Component = module.default ?? module;
|
|
168
|
-
} catch (err) {
|
|
169
|
-
console.error(`Error loading module ${modulePath}:`, err);
|
|
170
|
-
Component = type; // Fallback
|
|
171
|
-
}
|
|
172
|
-
} else if (type === "__clientComponent__") {
|
|
173
|
-
returnUndefined.value = true;
|
|
174
|
-
} else if (typeof type === "string" && type !== "Fragment") {
|
|
175
|
-
Component = type; // HTML elements (e.g., "html", "div")
|
|
176
|
-
} else if (type === "Fragment") {
|
|
177
|
-
Component = React.Fragment;
|
|
178
|
-
} else {
|
|
179
|
-
Component = type; // Fallback for unknown types
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Deserialize all props that are React elements
|
|
183
|
-
const deserializedProps = {};
|
|
184
|
-
for (const [key, value] of Object.entries(props)) {
|
|
185
|
-
if (key === "children") {
|
|
186
|
-
deserializedProps[key] = Array.isArray(value)
|
|
187
|
-
? value.map((child) =>
|
|
188
|
-
deserializeReactElement(child, returnUndefined)
|
|
189
|
-
)
|
|
190
|
-
: value
|
|
191
|
-
? deserializeReactElement(value, returnUndefined)
|
|
192
|
-
: null;
|
|
193
|
-
} else if (
|
|
194
|
-
value &&
|
|
195
|
-
typeof value === "object" &&
|
|
196
|
-
"type" in value &&
|
|
197
|
-
"props" in value
|
|
198
|
-
) {
|
|
199
|
-
deserializedProps[key] = deserializeReactElement(
|
|
200
|
-
value,
|
|
201
|
-
returnUndefined
|
|
202
|
-
);
|
|
203
|
-
} else {
|
|
204
|
-
deserializedProps[key] = value;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return returnUndefined.value
|
|
209
|
-
? undefined
|
|
210
|
-
: React.createElement(Component, deserializedProps);
|
|
211
|
-
}
|
|
212
|
-
// Pass through non-serialized values (e.g., strings, null)
|
|
213
|
-
return returnUndefined.value ? undefined : serialized;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const regenerating = new Set();
|
|
217
|
-
|
|
218
|
-
function getSSGJSX(reqPath) {
|
|
219
|
-
const distFolder = path.resolve(process.cwd(), "dist");
|
|
220
|
-
const jsonPath = path.join(distFolder, reqPath, "index.json");
|
|
221
|
-
if (existsSync(jsonPath)) {
|
|
222
|
-
const { jsx, revalidate, generatedAt } = JSON.parse(
|
|
223
|
-
readFileSync(jsonPath, "utf8")
|
|
224
|
-
);
|
|
225
|
-
if (
|
|
226
|
-
typeof revalidate === "number" &&
|
|
227
|
-
revalidate > 0 &&
|
|
228
|
-
Date.now() > generatedAt + revalidate &&
|
|
229
|
-
!regenerating.has(reqPath)
|
|
230
|
-
) {
|
|
231
|
-
buildStaticPage(reqPath)
|
|
232
|
-
.catch(console.error)
|
|
233
|
-
.finally(() => regenerating.delete(reqPath));
|
|
234
|
-
}
|
|
235
|
-
return deserializeReactElement(jsx);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async function getSSGJSXOrJSX(reqPath, query) {
|
|
240
|
-
const result = Object.keys(query).length
|
|
241
|
-
? await getJSX(reqPath, query)
|
|
242
|
-
: getSSGJSX(reqPath) ?? (await getJSX(reqPath, query));
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
module.exports = {
|
|
247
|
-
getSSGJSXOrJSX,
|
|
248
|
-
getSSGJSX,
|
|
249
|
-
getJSX,
|
|
250
|
-
};
|
|
150
|
+
module.exports = getJSX;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const getJSX = require("./get-jsx.js");
|
|
2
|
+
const getSSGJSX = require("./get-ssg-jsx.js");
|
|
3
|
+
|
|
4
|
+
async function getSSGJSXOrJSX(reqPath, query) {
|
|
5
|
+
const result = Object.keys(query).length
|
|
6
|
+
? await getJSX(reqPath, query)
|
|
7
|
+
: getSSGJSX(reqPath) ?? (await getJSX(reqPath, query));
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = getSSGJSXOrJSX;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { existsSync, readFileSync } = require("fs");
|
|
3
|
+
const React = require("react");
|
|
4
|
+
|
|
5
|
+
function deserializeReactElement(
|
|
6
|
+
serialized,
|
|
7
|
+
returnUndefined = { value: false }
|
|
8
|
+
) {
|
|
9
|
+
// Check if serialized is a React element object
|
|
10
|
+
if (
|
|
11
|
+
serialized &&
|
|
12
|
+
typeof serialized === "object" &&
|
|
13
|
+
"type" in serialized &&
|
|
14
|
+
"props" in serialized
|
|
15
|
+
) {
|
|
16
|
+
const { type, modulePath, props } = serialized;
|
|
17
|
+
let Component;
|
|
18
|
+
if (modulePath) {
|
|
19
|
+
try {
|
|
20
|
+
const module = require(path.resolve(process.cwd(), modulePath));
|
|
21
|
+
Component = module.default ?? module;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(`Error loading module ${modulePath}:`, err);
|
|
24
|
+
Component = type; // Fallback
|
|
25
|
+
}
|
|
26
|
+
} else if (type === "__clientComponent__") {
|
|
27
|
+
returnUndefined.value = true;
|
|
28
|
+
} else if (typeof type === "string" && type !== "Fragment") {
|
|
29
|
+
Component = type; // HTML elements (e.g., "html", "div")
|
|
30
|
+
} else if (type === "Fragment") {
|
|
31
|
+
Component = React.Fragment;
|
|
32
|
+
} else {
|
|
33
|
+
Component = type; // Fallback for unknown types
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Deserialize all props that are React elements
|
|
37
|
+
const deserializedProps = {};
|
|
38
|
+
for (const [key, value] of Object.entries(props)) {
|
|
39
|
+
if (key === "children") {
|
|
40
|
+
deserializedProps[key] = Array.isArray(value)
|
|
41
|
+
? value.map((child) =>
|
|
42
|
+
deserializeReactElement(child, returnUndefined)
|
|
43
|
+
)
|
|
44
|
+
: value
|
|
45
|
+
? deserializeReactElement(value, returnUndefined)
|
|
46
|
+
: null;
|
|
47
|
+
} else if (
|
|
48
|
+
value &&
|
|
49
|
+
typeof value === "object" &&
|
|
50
|
+
"type" in value &&
|
|
51
|
+
"props" in value
|
|
52
|
+
) {
|
|
53
|
+
deserializedProps[key] = deserializeReactElement(
|
|
54
|
+
value,
|
|
55
|
+
returnUndefined
|
|
56
|
+
);
|
|
57
|
+
} else {
|
|
58
|
+
deserializedProps[key] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return returnUndefined.value
|
|
63
|
+
? undefined
|
|
64
|
+
: React.createElement(Component, deserializedProps);
|
|
65
|
+
}
|
|
66
|
+
// Pass through non-serialized values (e.g., strings, null)
|
|
67
|
+
return returnUndefined.value ? undefined : serialized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getSSGJSX(reqPath) {
|
|
71
|
+
const distFolder = path.resolve(process.cwd(), "dist");
|
|
72
|
+
const jsonPath = path.join(distFolder, reqPath, "index.json");
|
|
73
|
+
if (existsSync(jsonPath)) {
|
|
74
|
+
const { jsx } = JSON.parse(readFileSync(jsonPath, "utf8"));
|
|
75
|
+
const deserializedJSX = deserializeReactElement(jsx);
|
|
76
|
+
return deserializedJSX;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = getSSGJSX;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { readdirSync } = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const DIST_DIR = path.resolve("dist");
|
|
5
|
+
|
|
6
|
+
async function getStaticPaths() {
|
|
7
|
+
const paths = [];
|
|
8
|
+
|
|
9
|
+
function walk(dir) {
|
|
10
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
11
|
+
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const fullPath = path.join(dir, entry.name);
|
|
14
|
+
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
walk(fullPath);
|
|
17
|
+
} else if (entry.isFile() && entry.name === "index.json") {
|
|
18
|
+
const relativeDir = path.relative(DIST_DIR, path.dirname(fullPath));
|
|
19
|
+
const route =
|
|
20
|
+
"/" +
|
|
21
|
+
(relativeDir === "" ? "" : relativeDir + "/").replace(/\\/g, "/");
|
|
22
|
+
paths.push(route);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
walk(DIST_DIR);
|
|
28
|
+
|
|
29
|
+
return paths;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = getStaticPaths;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { spawn } = require("child_process");
|
|
3
|
+
|
|
4
|
+
function renderAppToHtml(reqPath, paramsString) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const child = spawn(
|
|
7
|
+
"node",
|
|
8
|
+
[path.resolve(__dirname, "render-html.js"), reqPath, paramsString],
|
|
9
|
+
{
|
|
10
|
+
env: {
|
|
11
|
+
...process.env,
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
let errorOutput = "";
|
|
17
|
+
child.stderr.on("data", (data) => {
|
|
18
|
+
errorOutput += data.toString();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
child.on("error", (error) => {
|
|
22
|
+
reject(new Error(`Failed to start child process: ${error.message}`));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
child.on("spawn", () => {
|
|
26
|
+
resolve(child.stdout);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
child.on("close", (code) => {
|
|
30
|
+
if (code !== 0) {
|
|
31
|
+
try {
|
|
32
|
+
const errorResult = JSON.parse(errorOutput);
|
|
33
|
+
reject(new Error(errorResult.error || errorOutput));
|
|
34
|
+
} catch {
|
|
35
|
+
reject(new Error(`Child process failed: ${errorOutput}`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = renderAppToHtml;
|
package/dinou/render-html.js
CHANGED
|
@@ -24,7 +24,8 @@ addHook({
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
const { renderToPipeableStream } = require("react-dom/server");
|
|
27
|
-
const
|
|
27
|
+
const getJSX = require("./get-jsx");
|
|
28
|
+
const getSSGJSX = require("./get-ssg-jsx.js");
|
|
28
29
|
const { getErrorJSX } = require("./get-error-jsx");
|
|
29
30
|
const { renderJSXToClientJSX } = require("./render-jsx-to-client-jsx");
|
|
30
31
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { existsSync, readFileSync } = require("fs");
|
|
3
|
+
const generateStaticPage = require("./generate-static-page");
|
|
4
|
+
const { buildStaticPage } = require("./build-static-pages");
|
|
5
|
+
const generateStaticRSC = require("./generate-static-rsc");
|
|
6
|
+
|
|
7
|
+
const regenerating = new Set();
|
|
8
|
+
|
|
9
|
+
function revalidating(reqPath) {
|
|
10
|
+
const distFolder = path.resolve(process.cwd(), "dist");
|
|
11
|
+
const jsonPath = path.join(distFolder, reqPath, "index.json");
|
|
12
|
+
if (existsSync(jsonPath)) {
|
|
13
|
+
const { revalidate, generatedAt } = JSON.parse(
|
|
14
|
+
readFileSync(jsonPath, "utf8")
|
|
15
|
+
);
|
|
16
|
+
if (
|
|
17
|
+
typeof revalidate === "number" &&
|
|
18
|
+
revalidate > 0 &&
|
|
19
|
+
Date.now() > generatedAt + revalidate &&
|
|
20
|
+
!regenerating.has(reqPath)
|
|
21
|
+
) {
|
|
22
|
+
buildStaticPage(reqPath)
|
|
23
|
+
.then(() =>
|
|
24
|
+
generateStaticPage(reqPath).then(() => generateStaticRSC(reqPath))
|
|
25
|
+
)
|
|
26
|
+
.catch(console.error)
|
|
27
|
+
.finally(() => regenerating.delete(reqPath));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = revalidating;
|
package/dinou/server.js
CHANGED
|
@@ -2,15 +2,16 @@ require("dotenv/config");
|
|
|
2
2
|
require("./register-paths");
|
|
3
3
|
const webpackRegister = require("react-server-dom-webpack/node-register");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
readFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
// mkdirSync,
|
|
9
|
+
// createWriteStream,
|
|
10
|
+
createReadStream,
|
|
11
|
+
} = require("fs");
|
|
6
12
|
const { renderToPipeableStream } = require("react-server-dom-webpack/server");
|
|
7
13
|
const express = require("express");
|
|
8
|
-
const
|
|
9
|
-
const webpack = require("webpack");
|
|
10
|
-
const webpackDevMiddleware = require("webpack-dev-middleware");
|
|
11
|
-
const webpackHotMiddleware = require("webpack-hot-middleware");
|
|
12
|
-
const webpackConfig = require(path.resolve(__dirname, "../webpack.config.js"));
|
|
13
|
-
const { getSSGJSXOrJSX } = require("./get-jsx.js");
|
|
14
|
+
const getSSGJSXOrJSX = require("./get-ssg-jsx-or-jsx.js");
|
|
14
15
|
const { getErrorJSX } = require("./get-error-jsx.js");
|
|
15
16
|
const addHook = require("./asset-require-hook.js");
|
|
16
17
|
webpackRegister();
|
|
@@ -36,37 +37,69 @@ addHook({
|
|
|
36
37
|
},
|
|
37
38
|
publicPath: "images/",
|
|
38
39
|
});
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
app.
|
|
40
|
+
// const { PassThrough } = require("stream");
|
|
41
|
+
const generateStatic = require("./generate-static.js");
|
|
42
|
+
const renderAppToHtml = require("./render-app-to-html.js");
|
|
43
|
+
const revalidating = require("./revalidating.js");
|
|
42
44
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
45
|
+
const app = express();
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
const compiler = webpack(webpackConfig);
|
|
46
|
-
app.use(
|
|
47
|
-
webpackDevMiddleware(compiler, {
|
|
48
|
-
publicPath: webpackConfig.output.publicPath,
|
|
49
|
-
writeToDisk: true,
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
app.use(webpackHotMiddleware(compiler));
|
|
53
|
-
}
|
|
47
|
+
app.use(express.json());
|
|
54
48
|
|
|
55
49
|
app.use(express.static(path.resolve(process.cwd(), "____public____")));
|
|
56
50
|
|
|
51
|
+
app.get("/.well-known/appspecific/com.chrome.devtools.json", (req, res) => {
|
|
52
|
+
res.setHeader("Content-Type", "application/json");
|
|
53
|
+
res.json({
|
|
54
|
+
name: "Dinou DevTools",
|
|
55
|
+
description: "Dinou DevTools for Chrome",
|
|
56
|
+
version: "1.0.0",
|
|
57
|
+
devtools_page: "/____public____/devtools.html",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
57
61
|
app.get(/^\/____rsc_payload____\/.*\/?$/, async (req, res) => {
|
|
58
62
|
try {
|
|
59
63
|
const reqPath = (
|
|
60
64
|
req.path.endsWith("/") ? req.path : req.path + "/"
|
|
61
65
|
).replace("/____rsc_payload____", "");
|
|
66
|
+
|
|
67
|
+
if (!isDevelopment) {
|
|
68
|
+
const payloadPath = path.join("dist2", reqPath, "rsc.rsc");
|
|
69
|
+
if (existsSync(payloadPath)) {
|
|
70
|
+
// console.log(`[RSC] Serving existing payload for ${reqPath}`);
|
|
71
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
72
|
+
const readStream = createReadStream(payloadPath);
|
|
73
|
+
readStream.on("error", (err) => {
|
|
74
|
+
console.error("Error reading RSC file:", err);
|
|
75
|
+
res.status(500).end();
|
|
76
|
+
});
|
|
77
|
+
return readStream.pipe(res);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// console.log(`[RSC] Payload not found, generating new one for ${reqPath}`);
|
|
82
|
+
|
|
62
83
|
const jsx = await getSSGJSXOrJSX(reqPath, { ...req.query });
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
|
|
85
|
+
const manifest = JSON.parse(
|
|
86
|
+
readFileSync(
|
|
87
|
+
path.resolve("____public____/react-client-manifest.json"),
|
|
88
|
+
"utf8"
|
|
89
|
+
)
|
|
66
90
|
);
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
|
|
92
|
+
// mkdirSync(path.dirname(payloadPath), { recursive: true });
|
|
93
|
+
// const fileWriteStream = createWriteStream(payloadPath);
|
|
94
|
+
|
|
95
|
+
const { pipe } = renderToPipeableStream(jsx, manifest);
|
|
69
96
|
pipe(res);
|
|
97
|
+
|
|
98
|
+
// Pipe both to response and file
|
|
99
|
+
// const passThrough = new PassThrough();
|
|
100
|
+
// pipe(passThrough);
|
|
101
|
+
// passThrough.pipe(res);
|
|
102
|
+
// passThrough.pipe(fileWriteStream);
|
|
70
103
|
} catch (error) {
|
|
71
104
|
console.error("Error rendering RSC:", error);
|
|
72
105
|
res.status(500).send("Internal Server Error");
|
|
@@ -92,62 +125,41 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
|
92
125
|
}
|
|
93
126
|
});
|
|
94
127
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
"node",
|
|
100
|
-
[path.resolve(__dirname, "render-html.js"), reqPath, paramsString],
|
|
101
|
-
{
|
|
102
|
-
env: {
|
|
103
|
-
...process.env,
|
|
104
|
-
},
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
let errorOutput = "";
|
|
109
|
-
child.stderr.on("data", (data) => {
|
|
110
|
-
errorOutput += data.toString();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
child.on("error", (error) => {
|
|
114
|
-
reject(new Error(`Failed to start child process: ${error.message}`));
|
|
115
|
-
});
|
|
128
|
+
app.get(/^\/.*\/?$/, async (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const reqPath = req.path.endsWith("/") ? req.path : req.path + "/";
|
|
131
|
+
const htmlPath = path.join("dist2", reqPath, "index.html");
|
|
116
132
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
133
|
+
if (!isDevelopment) {
|
|
134
|
+
revalidating(reqPath);
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
reject(new Error(errorResult.error || errorOutput));
|
|
126
|
-
} catch {
|
|
127
|
-
reject(new Error(`Child process failed: ${errorOutput}`));
|
|
128
|
-
}
|
|
136
|
+
if (existsSync(htmlPath)) {
|
|
137
|
+
// console.log("Serving cached HTML:", htmlPath);
|
|
138
|
+
res.setHeader("Content-Type", "text/html");
|
|
139
|
+
return createReadStream(htmlPath).pipe(res);
|
|
129
140
|
}
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
}
|
|
141
|
+
}
|
|
133
142
|
|
|
134
|
-
app.get(/^\/.*\/?$/, async (req, res) => {
|
|
135
|
-
try {
|
|
136
|
-
const reqPath = req.path.endsWith("/") ? req.path : req.path + "/";
|
|
137
|
-
// Get the stream from the child process
|
|
138
143
|
const appHtmlStream = await renderAppToHtml(
|
|
139
144
|
reqPath,
|
|
140
145
|
JSON.stringify({ ...req.query })
|
|
141
146
|
);
|
|
142
|
-
// Set headers for the response
|
|
143
|
-
res.setHeader("Content-Type", "text/html");
|
|
144
147
|
|
|
148
|
+
// mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
149
|
+
|
|
150
|
+
// const fileStream = createWriteStream(htmlPath);
|
|
151
|
+
res.setHeader("Content-Type", "text/html");
|
|
145
152
|
appHtmlStream.pipe(res);
|
|
153
|
+
// appHtmlStream.pipe(fileStream);
|
|
146
154
|
|
|
147
155
|
appHtmlStream.on("error", (error) => {
|
|
148
156
|
console.error("Stream error:", error);
|
|
149
157
|
res.status(500).send("Internal Server Error");
|
|
150
158
|
});
|
|
159
|
+
|
|
160
|
+
// fileStream.on("error", (error) => {
|
|
161
|
+
// console.error("File write error:", error);
|
|
162
|
+
// });
|
|
151
163
|
} catch (error) {
|
|
152
164
|
console.error("Error rendering React app:", error);
|
|
153
165
|
res.status(500).send("Internal Server Error");
|
|
@@ -156,6 +168,11 @@ app.get(/^\/.*\/?$/, async (req, res) => {
|
|
|
156
168
|
|
|
157
169
|
const port = process.env.PORT || 3000;
|
|
158
170
|
|
|
159
|
-
app.listen(port, () => {
|
|
171
|
+
app.listen(port, async () => {
|
|
172
|
+
if (!isDevelopment) {
|
|
173
|
+
await generateStatic();
|
|
174
|
+
} else {
|
|
175
|
+
console.log("⚙️ Rendering dynamically in dev mode");
|
|
176
|
+
}
|
|
160
177
|
console.log(`Listening on port ${port}`);
|
|
161
178
|
});
|
package/eject.js
CHANGED
|
@@ -23,17 +23,12 @@ if (fs.existsSync(path.join(modulePath, "postcss.config.js"))) {
|
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// don't copy the LICENSE.md file, as it is not needed in the project root
|
|
27
|
-
// fs.copyFileSync(
|
|
28
|
-
// path.join(modulePath, "LICENSE.md"),
|
|
29
|
-
// path.join(projectRoot, "dinou/LICENSE.md")
|
|
30
|
-
// );
|
|
31
|
-
|
|
32
26
|
const pkg = require(path.join(projectRoot, "package.json"));
|
|
27
|
+
pkg.scripts["start:express"] = "node --conditions react-server dinou/server.js";
|
|
28
|
+
pkg.scripts["start:dev-server"] = "webpack serve --config webpack.config.js";
|
|
33
29
|
pkg.scripts.dev =
|
|
34
|
-
"
|
|
35
|
-
pkg.scripts.build =
|
|
36
|
-
"node dinou/ssg.js && cross-env NODE_ENV=production webpack";
|
|
30
|
+
'concurrently "npm run start:express" "npm run start:dev-server"';
|
|
31
|
+
pkg.scripts.build = "cross-env NODE_ENV=production webpack";
|
|
37
32
|
pkg.scripts.start =
|
|
38
33
|
"cross-env NODE_ENV=production node --conditions react-server dinou/server.js";
|
|
39
34
|
delete pkg.scripts.eject;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dinou",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Minimal React 19 Framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"autoprefixer": "^10.4.21",
|
|
31
31
|
"babel-loader": "^10.0.0",
|
|
32
32
|
"commander": "^14.0.0",
|
|
33
|
+
"concurrently": "^9.2.0",
|
|
33
34
|
"copy-webpack-plugin": "^13.0.0",
|
|
34
35
|
"cross-env": "^7.0.3",
|
|
35
36
|
"css-loader": "^7.1.2",
|
|
@@ -47,8 +48,7 @@
|
|
|
47
48
|
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
|
48
49
|
"webpack": "^5.99.8",
|
|
49
50
|
"webpack-cli": "^6.0.1",
|
|
50
|
-
"webpack-dev-
|
|
51
|
-
"webpack-hot-middleware": "^2.26.1"
|
|
51
|
+
"webpack-dev-server": "^5.2.2"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"react": "^19.1.0",
|
package/webpack.config.js
CHANGED
|
@@ -2,7 +2,6 @@ require("dotenv/config");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const ReactServerWebpackPlugin = require("react-server-dom-webpack/plugin");
|
|
5
|
-
const webpack = require("webpack");
|
|
6
5
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
|
7
6
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
|
8
7
|
const createScopedName = require("./dinou/createScopedName");
|
|
@@ -25,14 +24,10 @@ const configFile = getConfigFileIfExists();
|
|
|
25
24
|
module.exports = {
|
|
26
25
|
mode: isDevelopment ? "development" : "production",
|
|
27
26
|
entry: {
|
|
28
|
-
main: [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
error: [
|
|
33
|
-
isDevelopment && "webpack-hot-middleware/client?reload=true",
|
|
34
|
-
path.resolve(__dirname, "./dinou/client-error.jsx"),
|
|
35
|
-
].filter(Boolean),
|
|
27
|
+
main: [path.resolve(__dirname, "./dinou/client.jsx")].filter(Boolean),
|
|
28
|
+
error: [path.resolve(__dirname, "./dinou/client-error.jsx")].filter(
|
|
29
|
+
Boolean
|
|
30
|
+
),
|
|
36
31
|
},
|
|
37
32
|
output: {
|
|
38
33
|
path: path.resolve(process.cwd(), "./____public____"),
|
|
@@ -121,7 +116,6 @@ module.exports = {
|
|
|
121
116
|
],
|
|
122
117
|
},
|
|
123
118
|
plugins: [
|
|
124
|
-
isDevelopment && new webpack.HotModuleReplacementPlugin(),
|
|
125
119
|
new ReactServerWebpackPlugin({ isServer: false }),
|
|
126
120
|
new CopyWebpackPlugin({
|
|
127
121
|
patterns: [
|
|
@@ -162,4 +156,23 @@ module.exports = {
|
|
|
162
156
|
watchOptions: {
|
|
163
157
|
ignored: /____public____/,
|
|
164
158
|
},
|
|
159
|
+
...(isDevelopment
|
|
160
|
+
? {
|
|
161
|
+
devServer: {
|
|
162
|
+
port: 3001,
|
|
163
|
+
hot: true,
|
|
164
|
+
devMiddleware: {
|
|
165
|
+
index: false,
|
|
166
|
+
writeToDisk: true,
|
|
167
|
+
},
|
|
168
|
+
proxy: [
|
|
169
|
+
{
|
|
170
|
+
context: () => true,
|
|
171
|
+
target: "http://localhost:3000",
|
|
172
|
+
changeOrigin: true,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
: {}),
|
|
165
178
|
};
|
package/dinou/ssg.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
require("./register-paths");
|
|
2
|
-
const babelRegister = require("@babel/register");
|
|
3
|
-
babelRegister({
|
|
4
|
-
ignore: [/[\\\/](build|server|node_modules)[\\\/]/],
|
|
5
|
-
presets: [
|
|
6
|
-
["@babel/preset-react", { runtime: "automatic" }],
|
|
7
|
-
"@babel/preset-typescript",
|
|
8
|
-
],
|
|
9
|
-
plugins: ["@babel/transform-modules-commonjs"],
|
|
10
|
-
extensions: [".js", ".jsx", ".ts", ".tsx"],
|
|
11
|
-
});
|
|
12
|
-
const { buildStaticPages } = require("./build-static-pages");
|
|
13
|
-
const addHook = require("./asset-require-hook.js");
|
|
14
|
-
const createScopedName = require("./createScopedName");
|
|
15
|
-
require("css-modules-require-hook")({
|
|
16
|
-
generateScopedName: createScopedName,
|
|
17
|
-
});
|
|
18
|
-
addHook({
|
|
19
|
-
extensions: ["png", "jpg", "jpeg", "gif", "svg", "webp"],
|
|
20
|
-
name: function (localName, filepath) {
|
|
21
|
-
const result = createScopedName(localName, filepath);
|
|
22
|
-
return result + ".[ext]";
|
|
23
|
-
},
|
|
24
|
-
publicPath: "images/",
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
(async () => await buildStaticPages())();
|