dinou 4.0.15 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +150 -57
  3. package/dinou/core/babel-esm-loader.js +87 -5
  4. package/dinou/core/build-static-pages.js +17 -97
  5. package/dinou/core/generate-static-page.js +21 -3
  6. package/dinou/core/generate-static-pages.js +30 -41
  7. package/dinou/core/generate-static-rsc.js +11 -5
  8. package/dinou/core/generate-static-rscs.js +11 -5
  9. package/dinou/core/generate-static.js +2 -3
  10. package/dinou/core/get-jsx.js +2 -1
  11. package/dinou/core/get-ssg-metadata.js +5 -13
  12. package/dinou/core/import-module.js +25 -6
  13. package/dinou/core/redirect.jsx +18 -11
  14. package/dinou/core/register-loader.mjs +1 -0
  15. package/dinou/core/render-app-to-html.js +209 -35
  16. package/dinou/core/render-html.js +144 -26
  17. package/dinou/core/render-jsx-to-client-jsx.js +6 -7
  18. package/dinou/core/request-context.js +11 -6
  19. package/dinou/core/revalidating.js +29 -25
  20. package/dinou/core/server.js +279 -88
  21. package/dinou/core/url-resolver.js +3 -0
  22. package/dinou/esbuild/dev.mjs +31 -2
  23. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +0 -40
  24. package/dinou/index.d.ts +1 -1
  25. package/dinou/index.mjs +31 -10
  26. package/dinou/package.json +1 -1
  27. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +88 -49
  28. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +7 -9
  29. package/dinou/rollup/rollup.config.js +1 -2
  30. package/dinou/webpack/helpers/get-webpack-entries.js +0 -40
  31. package/package.json +3 -3
  32. package/dinou/core/babel-plugin-register-imports.js +0 -50
  33. package/dinou/core/get-ssg-jsx-or-jsx.js +0 -14
  34. package/dinou/core/get-ssg-jsx.js +0 -83
  35. package/dinou/core/jsx-json.js +0 -55
  36. package/dinou/core/register-hooks.js +0 -18
@@ -1,8 +1,9 @@
1
1
  const path = require("path");
2
- const { mkdirSync, createWriteStream } = require("fs");
2
+ const { mkdirSync, createWriteStream, existsSync } = require("fs");
3
3
  const fs = require("fs").promises;
4
4
  const renderAppToHtml = require("./render-app-to-html.js");
5
- const getSSGMetadata = require("./get-ssg-metadata.js");
5
+ const { getStaticMetadata } = require("./build-static-pages.js");
6
+ const { processMetadata } = require("./get-ssg-metadata.js");
6
7
 
7
8
  const OUT_DIR = path.resolve("dist2");
8
9
 
@@ -64,7 +65,11 @@ async function generateStaticPage(reqPath) {
64
65
  capturedStatus
65
66
  );
66
67
 
67
- const sideEffectScripts = getSSGMetadata(reqPath);
68
+ const metadata = getStaticMetadata(finalReqPath);
69
+ let sideEffectScripts = "";
70
+ if (metadata && metadata.effects) {
71
+ sideEffectScripts = processMetadata(metadata.effects);
72
+ }
68
73
 
69
74
  await new Promise((resolve, reject) => {
70
75
  if (sideEffectScripts) fileStream.write(sideEffectScripts);
@@ -81,6 +86,19 @@ async function generateStaticPage(reqPath) {
81
86
  fileStream.on("error", reject);
82
87
  });
83
88
 
89
+ if (metadata) {
90
+ const metadataPath = path.join(OUT_DIR, finalReqPath, "metadata.json");
91
+ await fs.writeFile(
92
+ metadataPath,
93
+ JSON.stringify({
94
+ revalidate: metadata.revalidate,
95
+ generatedAt: Date.now(),
96
+ effects: metadata.effects,
97
+ }, null, 2),
98
+ "utf8"
99
+ );
100
+ }
101
+
84
102
  const status = capturedStatus.value || 200;
85
103
  const success = status !== 500;
86
104
 
@@ -1,80 +1,57 @@
1
1
  // generate-static-pages.js
2
2
  const path = require("path");
3
3
  const { mkdirSync, createWriteStream } = require("fs");
4
+ const fs = require("fs").promises;
4
5
  const renderAppToHtml = require("./render-app-to-html.js");
5
- const getSSGMetadata = require("./get-ssg-metadata.js");
6
+ const { getStaticMetadata } = require("./build-static-pages.js");
7
+ const { processMetadata } = require("./get-ssg-metadata.js");
6
8
  const { updateStatus } = require("./status-manifest.js");
7
9
 
8
10
  const OUT_DIR = path.resolve("dist2");
9
11
 
10
12
  async function generateStaticPages(routes) {
11
13
  for (const route of routes) {
12
- // Normalization of the route
13
14
  const reqPath = route.endsWith("/") ? route : route + "/";
14
15
  const htmlPath = path.join(OUT_DIR, reqPath, "index.html");
15
16
 
16
- // Prepare Query params (empty by default in SSG, unless your router supports it)
17
17
  const query = {};
18
18
  const paramsString = JSON.stringify(query);
19
19
  const capturedStatus = {};
20
- // ---------------------------------------------------------
21
- // 1. CREATE MOCK REQUEST (Solution to your second question)
22
- // ---------------------------------------------------------
23
20
  const contextForChild = {
24
21
  req: {
25
22
  query,
26
- cookies: {}, // No cookies at build time
23
+ cookies: {},
27
24
  headers: {
28
25
  "user-agent": "Dinou-SSG-Builder",
29
- host: "localhost", // Safe default value
26
+ host: "localhost",
30
27
  "x-forwarded-proto": "http",
31
28
  },
32
- path: reqPath, // 💡 Vital for active menu logic, etc.
29
+ path: reqPath,
33
30
  method: "GET",
34
31
  },
35
- // We do not pass 'res' here, the child ignores it, we pass the mockRes as an argument
36
32
  };
37
33
 
38
34
  try {
39
35
  mkdirSync(path.dirname(htmlPath), { recursive: true });
40
36
  const fileStream = createWriteStream(htmlPath);
41
- let htmlStream = null; // Declare outside to use in the closure
37
+ let htmlStream = null;
42
38
 
43
- // ---------------------------------------------------------
44
- // 2. CREATE MOCK RESPONSE (With Fix for Webpack)
45
- // ---------------------------------------------------------
46
39
  const mockRes = {
47
- headersSent: true, // Force "streaming/script injection" mode
48
- _cookies: [], // Optional: for debug
49
-
50
- // 👇 ADD THIS METHOD
40
+ headersSent: true,
41
+ _cookies: [],
51
42
  cookie(name, value, options) {
52
- // In SSG we don't do anything real, but we save a record if you want to debug
53
- // console.log(`[SSG] Cookie set ignored: ${name}=${value}`);
54
43
  this._cookies.push({ name, value, options });
55
44
  },
56
-
57
45
  write: (chunk) => {
58
46
  if (!fileStream.writableEnded) fileStream.write(chunk);
59
47
  },
60
-
61
48
  end: (chunk) => {
62
- // If they send us a last chunk (e.g., </script>)
63
49
  if (chunk && !fileStream.writableEnded) fileStream.write(chunk);
64
-
65
- // 🔥 WEBPACK "WRITE AFTER END" FIX:
66
- // Disconnect the child's stream immediately.
67
- // If Webpack sends garbage afterwards, it won't reach the fileStream.
68
50
  if (htmlStream) {
69
51
  htmlStream.unpipe(fileStream);
70
- // Optional: Pause or destroy the child's stream to free memory
71
- // htmlStream.destroy();
72
52
  }
73
-
74
- // Officially close the file
75
53
  if (!fileStream.writableEnded) fileStream.end();
76
54
  },
77
-
78
55
  status: (code) => {
79
56
  if (code !== 200)
80
57
  console.warn(`[SSG] Status ${code} ignored for ${reqPath}`);
@@ -85,25 +62,26 @@ async function generateStaticPages(routes) {
85
62
  redirect: () => {},
86
63
  };
87
64
 
88
- // 3. Execute Render
89
65
  htmlStream = renderAppToHtml(
90
66
  reqPath,
91
67
  paramsString,
92
- contextForChild, // ✅ Pass the MockReq
68
+ contextForChild,
93
69
  mockRes,
94
70
  capturedStatus
95
71
  );
96
72
 
97
- const sideEffectScripts = getSSGMetadata(reqPath);
73
+ const metadata = getStaticMetadata(reqPath);
74
+ let sideEffectScripts = "";
75
+ if (metadata && metadata.effects) {
76
+ sideEffectScripts = processMetadata(metadata.effects);
77
+ }
78
+
98
79
  await new Promise((resolve, reject) => {
99
- // 🟢 SCRIPT INJECTION
100
80
  if (sideEffectScripts) {
101
81
  fileStream.write(sideEffectScripts);
102
82
  }
103
- // Connect standard output to the file
104
- htmlStream.pipe(fileStream, { end: false }); // end: false gives us manual control in the mockRes
83
+ htmlStream.pipe(fileStream, { end: false });
105
84
 
106
- // Event handling
107
85
  htmlStream.on("end", () => {
108
86
  if (!fileStream.writableEnded) fileStream.end();
109
87
  updateStatus(reqPath, capturedStatus.value);
@@ -111,7 +89,6 @@ async function generateStaticPages(routes) {
111
89
  });
112
90
 
113
91
  htmlStream.on("error", (err) => {
114
- // If the error is "write after end" and we already closed, ignore it (it's noise)
115
92
  if (err.code === "ERR_STREAM_WRITE_AFTER_END") {
116
93
  resolve();
117
94
  } else {
@@ -122,9 +99,21 @@ async function generateStaticPages(routes) {
122
99
  fileStream.on("error", reject);
123
100
  });
124
101
 
102
+ if (metadata) {
103
+ const metadataPath = path.join(OUT_DIR, reqPath, "metadata.json");
104
+ await fs.writeFile(
105
+ metadataPath,
106
+ JSON.stringify({
107
+ revalidate: metadata.revalidate,
108
+ generatedAt: Date.now(),
109
+ effects: metadata.effects,
110
+ }, null, 2),
111
+ "utf8"
112
+ );
113
+ }
114
+
125
115
  console.log("✅ Generated HTML:", reqPath);
126
116
  } catch (error) {
127
- // Extra safety filter in case the error bubbles up here
128
117
  if (error.code === "ERR_STREAM_WRITE_AFTER_END") {
129
118
  console.log("⚠️ Ignored write-after-end race condition for:", reqPath);
130
119
  } else {
@@ -1,12 +1,16 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { PassThrough } = require("stream");
4
- const getSSGJSXOrJSX = require("./get-ssg-jsx-or-jsx.js");
5
- const { renderToPipeableStream } = require("react-server-dom-webpack/server");
4
+ const url = require("url");
5
+ const getJSX = require("./get-jsx.js");
6
+ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
7
+ const { renderToPipeableStream } = isWebpack
8
+ ? require("react-server-dom-webpack/server")
9
+ : require("@roggc/react-server-dom-esm/server");
6
10
  const { requestStorage } = require("./request-context.js");
7
11
 
8
12
  const OUT_DIR = path.resolve("dist2");
9
- const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
13
+ // const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
10
14
 
11
15
  async function generateStaticRSC(reqPath) {
12
16
  const finalReqPath = reqPath.endsWith("/") ? reqPath : reqPath + "/";
@@ -94,8 +98,10 @@ async function generateStaticRSC(reqPath) {
94
98
  const passThrough = new PassThrough();
95
99
 
96
100
  await requestStorage.run(mockContext, async () => {
97
- const jsx = await getSSGJSXOrJSX(finalReqPath, {});
98
- const { pipe } = renderToPipeableStream(jsx, manifest);
101
+ const jsx = await getJSX(finalReqPath, {}, null, false);
102
+ const { pipe } = isWebpack
103
+ ? renderToPipeableStream(jsx, manifest)
104
+ : renderToPipeableStream(jsx, url.pathToFileURL(process.cwd()).href + "/");
99
105
  pipe(passThrough);
100
106
  passThrough.pipe(fileStream);
101
107
 
@@ -1,14 +1,18 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { PassThrough } = require("stream");
4
- const getSSGJSXOrJSX = require("./get-ssg-jsx-or-jsx.js");
5
- const { renderToPipeableStream } = require("react-server-dom-webpack/server");
4
+ const url = require("url");
5
+ const getJSX = require("./get-jsx.js");
6
+ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
7
+ const { renderToPipeableStream } = isWebpack
8
+ ? require("react-server-dom-webpack/server")
9
+ : require("@roggc/react-server-dom-esm/server");
6
10
 
7
11
  // 👇 Your context storage
8
12
  const { requestStorage } = require("./request-context.js");
9
13
 
10
14
  const OUT_DIR = path.resolve("dist2");
11
- const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
15
+ // const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
12
16
 
13
17
  async function generateStaticRSCs(routes) {
14
18
  const manifest = JSON.parse(
@@ -110,8 +114,10 @@ async function generateStaticRSCs(routes) {
110
114
 
111
115
  // 1. The entire lifecycle of the stream must be inside the storage
112
116
  await requestStorage.run(mockContext, async () => {
113
- const jsx = await getSSGJSXOrJSX(reqPath, {});
114
- const { pipe } = renderToPipeableStream(jsx, manifest);
117
+ const jsx = await getJSX(reqPath, {}, null, false);
118
+ const { pipe } = isWebpack
119
+ ? renderToPipeableStream(jsx, manifest)
120
+ : renderToPipeableStream(jsx, url.pathToFileURL(process.cwd()).href + "/");
115
121
 
116
122
  pipe(passThrough);
117
123
  passThrough.pipe(fileStream);
@@ -2,8 +2,7 @@ const path = require("path");
2
2
  const { existsSync, rmSync } = require("fs");
3
3
  const generateStaticRSCs = require("./generate-static-rscs");
4
4
  const generateStaticPages = require("./generate-static-pages");
5
- const { getStaticPaths } = require("./jsx-json");
6
- const { buildStaticPages } = require("./build-static-pages");
5
+ const { buildStaticPages, getStaticPaths } = require("./build-static-pages");
7
6
 
8
7
  async function generateStatic() {
9
8
  const distFolder2 = path.resolve(process.cwd(), "dist2");
@@ -16,8 +15,8 @@ async function generateStatic() {
16
15
  await buildStaticPages();
17
16
  const routes = getStaticPaths();
18
17
  console.log("Static paths:", routes);
19
- await generateStaticPages(routes);
20
18
  await generateStaticRSCs(routes);
19
+ await generateStaticPages(routes);
21
20
  }
22
21
 
23
22
  module.exports = generateStatic;
@@ -12,6 +12,7 @@ async function getJSX(
12
12
  query,
13
13
  isNotFound = null,
14
14
  isDevelopment = false,
15
+ forceNotFound = false,
15
16
  ) {
16
17
  const srcFolder = path.resolve(process.cwd(), "src");
17
18
  const reqSegments = reqPath.split("/").filter(Boolean);
@@ -52,7 +53,7 @@ async function getJSX(
52
53
  let jsx;
53
54
  let pageFunctionsProps;
54
55
 
55
- if (!pagePath) {
56
+ if (!pagePath || forceNotFound) {
56
57
  if (isNotFound) isNotFound.value = true;
57
58
  const [notFoundPath, dParams] = getFilePathAndDynamicParams(
58
59
  reqSegments,
@@ -1,9 +1,6 @@
1
- const { getJSXJSON, hasJSXJSON } = require("./jsx-json");
1
+ function processMetadata(effects) {
2
+ if (!effects) return "";
2
3
 
3
- function processMetadata(metadata) {
4
- if (!metadata || !metadata.effects) return "";
5
-
6
- const { effects } = metadata;
7
4
  let scriptContent = "";
8
5
 
9
6
  if (effects.cookies && effects.cookies.length > 0) {
@@ -29,11 +26,6 @@ function processMetadata(metadata) {
29
26
  return `<script>(function(){ ${scriptContent} })();</script>`;
30
27
  }
31
28
 
32
- function getSSGMetadata(reqPath) {
33
- if (!hasJSXJSON(reqPath)) return;
34
- const { metadata } = getJSXJSON(reqPath);
35
- const script = processMetadata(metadata);
36
- return script;
37
- }
38
-
39
- module.exports = getSSGMetadata;
29
+ module.exports = {
30
+ processMetadata,
31
+ };
@@ -1,19 +1,38 @@
1
1
  const { pathToFileURL } = require("url");
2
2
  const path = require("path");
3
+ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
3
4
 
4
5
  async function importModule(modulePath) {
6
+ const absPath = path.isAbsolute(modulePath)
7
+ ? modulePath
8
+ : path.resolve(process.cwd(), modulePath);
9
+
10
+ if (!isWebpack) {
11
+ let fileUrl = pathToFileURL(absPath).href;
12
+ if (process.env.NODE_ENV !== "production") {
13
+ fileUrl += `?t=${Date.now()}`;
14
+ }
15
+ const mod = await import(fileUrl);
16
+ return mod;
17
+ }
18
+
5
19
  try {
6
- return require(modulePath);
20
+ if (process.env.NODE_ENV !== "production") {
21
+ try {
22
+ const resolved = require.resolve(absPath);
23
+ delete require.cache[resolved];
24
+ } catch (e) {}
25
+ }
26
+ return require(absPath);
7
27
  } catch (err) {
8
28
  if (
9
29
  err.code === "ERR_REQUIRE_ESM" ||
10
30
  /require\(\) of ES Module/.test(err.message)
11
31
  ) {
12
- const absPath = path.isAbsolute(modulePath)
13
- ? modulePath
14
- : path.resolve(process.cwd(), modulePath);
15
-
16
- const fileUrl = pathToFileURL(absPath).href;
32
+ let fileUrl = pathToFileURL(absPath).href;
33
+ if (process.env.NODE_ENV !== "production") {
34
+ fileUrl += `?t=${Date.now()}`;
35
+ }
17
36
  const mod = await import(fileUrl);
18
37
  return mod;
19
38
  }
@@ -8,18 +8,25 @@ export function redirect(destination) {
8
8
  // 1. We try to get the server context
9
9
  if (typeof window === "undefined") {
10
10
  // getContext() must be accessible here
11
- const dynamicRequire = require;
12
- const { getContext } = dynamicRequire(
13
- /* webpackIgnore: true */ "./request-context.js"
14
- );
15
- const ctx = getContext();
11
+ const dynamicRequire =
12
+ typeof __dinou_require__ !== "undefined"
13
+ ? __dinou_require__
14
+ : typeof module !== "undefined" && typeof module.require === "function"
15
+ ? module.require.bind(module)
16
+ : null;
17
+ if (dynamicRequire) {
18
+ const { getContext } = dynamicRequire(
19
+ /* webpackIgnore: true */ "./request-context.js"
20
+ );
21
+ const ctx = getContext();
16
22
 
17
- // 2. If we are on the server and headers have NOT been sent yet...
18
- // We can do a real HTTP redirect (Status 307).
19
- // This is better for SEO and speed in Hard Navigation.
20
- if (ctx && ctx.res) {
21
- ctx.res.redirect(destination);
22
- return <ClientRedirect to={destination} />;
23
+ // 2. If we are on the server and headers have NOT been sent yet...
24
+ // We can do a real HTTP redirect (Status 307).
25
+ // This is better for SEO and speed in Hard Navigation.
26
+ if (ctx && ctx.res) {
27
+ ctx.res.redirect(destination);
28
+ return <ClientRedirect to={destination} />;
29
+ }
23
30
  }
24
31
  }
25
32
 
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
4
4
  import { createRequire } from "node:module";
5
5
 
6
6
  const require = createRequire(import.meta.url);
7
+ globalThis.__dinou_require__ = require;
7
8
 
8
9
  const loaderPath = require.resolve("./babel-esm-loader.js");
9
10