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 CHANGED
@@ -18,13 +18,18 @@ const runCommand = (command, options = {}) => {
18
18
 
19
19
  program
20
20
  .command("dev")
21
- .description("Starts the server")
21
+ .description("Starts")
22
22
  .action(() => {
23
- console.log("Starting the server...");
24
- runCommand(`node ${path.join(dinouPath, "ssg.js")}`);
25
- runCommand(
26
- `node --conditions react-server ${path.join(dinouPath, "server.js")}`
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, readFileSync } = require("fs");
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
- function deserializeReactElement(
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;
@@ -24,7 +24,8 @@ addHook({
24
24
  });
25
25
 
26
26
  const { renderToPipeableStream } = require("react-dom/server");
27
- const { getJSX, getSSGJSX } = require("./get-jsx");
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 { readFileSync } = require("fs");
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 { spawn } = require("child_process");
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 app = express();
41
- app.use(express.json());
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
- if (isDevelopment) {
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
- const manifest = readFileSync(
64
- path.resolve(process.cwd(), "____public____/react-client-manifest.json"),
65
- "utf8"
84
+
85
+ const manifest = JSON.parse(
86
+ readFileSync(
87
+ path.resolve("____public____/react-client-manifest.json"),
88
+ "utf8"
89
+ )
66
90
  );
67
- const moduleMap = JSON.parse(manifest);
68
- const { pipe } = renderToPipeableStream(jsx, moduleMap);
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
- // Render HTML via child process, returning a stream
96
- function renderAppToHtml(reqPath, paramsString) {
97
- return new Promise((resolve, reject) => {
98
- const child = spawn(
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
- child.on("spawn", () => {
118
- resolve(child.stdout);
119
- });
133
+ if (!isDevelopment) {
134
+ revalidating(reqPath);
120
135
 
121
- child.on("close", (code) => {
122
- if (code !== 0) {
123
- try {
124
- const errorResult = JSON.parse(errorOutput);
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
- "node dinou/ssg.js && node --conditions react-server dinou/server.js";
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.6.0",
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-middleware": "^7.4.2",
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
- isDevelopment && "webpack-hot-middleware/client?reload=true",
30
- path.resolve(__dirname, "./dinou/client.jsx"),
31
- ].filter(Boolean),
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())();