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.
Files changed (35) hide show
  1. package/CHANGELOG.md +15 -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/navigation.js +42 -27
  14. package/dinou/core/redirect.jsx +18 -11
  15. package/dinou/core/register-loader.mjs +1 -0
  16. package/dinou/core/render-app-to-html.js +198 -31
  17. package/dinou/core/render-html.js +144 -26
  18. package/dinou/core/render-jsx-to-client-jsx.js +6 -7
  19. package/dinou/core/request-context.js +11 -6
  20. package/dinou/core/revalidating.js +29 -25
  21. package/dinou/core/server.js +270 -85
  22. package/dinou/esbuild/dev.mjs +31 -2
  23. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +0 -40
  24. package/dinou/index.mjs +31 -10
  25. package/dinou/package.json +1 -1
  26. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +88 -49
  27. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +7 -9
  28. package/dinou/rollup/rollup.config.js +1 -2
  29. package/dinou/webpack/helpers/get-webpack-entries.js +0 -40
  30. package/package.json +2 -2
  31. package/dinou/core/babel-plugin-register-imports.js +0 -50
  32. package/dinou/core/get-ssg-jsx-or-jsx.js +0 -14
  33. package/dinou/core/get-ssg-jsx.js +0 -83
  34. package/dinou/core/jsx-json.js +0 -55
  35. package/dinou/core/register-hooks.js +0 -18
@@ -11,6 +11,32 @@ const { getAbsPathWithExt } = require("../../core/get-abs-path-with-ext.js");
11
11
  const { useClientRegex } = require("../../constants.js");
12
12
  const parseExports = require("../../core/parse-exports.js");
13
13
 
14
+ function getDefaultExportName(code) {
15
+ let name = null;
16
+ try {
17
+ const ast = parser.parse(code, {
18
+ sourceType: "module",
19
+ plugins: ["jsx", "typescript"],
20
+ });
21
+ traverse(ast, {
22
+ ExportDefaultDeclaration(p) {
23
+ const decl = p.node.declaration;
24
+ if (decl.type === "Identifier") {
25
+ name = decl.name;
26
+ } else if (
27
+ (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") &&
28
+ decl.id
29
+ ) {
30
+ name = decl.id.name;
31
+ }
32
+ },
33
+ });
34
+ } catch (e) {
35
+ // Ignore parse errors
36
+ }
37
+ return name;
38
+ }
39
+
14
40
  function reactClientManifestPlugin({
15
41
  srcDir = path.resolve("src"),
16
42
  manifestPath = "react_client_manifest/react-client-manifest.json",
@@ -150,41 +176,6 @@ function reactClientManifestPlugin({
150
176
  return fileName.startsWith("page.") || fileName.startsWith("layout.");
151
177
  }
152
178
 
153
- function isAsyncDefaultExport(code) {
154
- const ast = parser.parse(code, {
155
- sourceType: "module",
156
- plugins: ["jsx", "typescript"],
157
- });
158
-
159
- let isAsync = false;
160
-
161
- traverse(ast, {
162
- ExportDefaultDeclaration(path) {
163
- let decl = path.node.declaration;
164
-
165
- if (decl.type === "Identifier") {
166
- const binding = path.scope.getBinding(decl.name);
167
- if (binding && binding.path) {
168
- decl = binding.path.node;
169
- if (decl.type === "VariableDeclarator") {
170
- decl = decl.init;
171
- }
172
- }
173
- }
174
-
175
- if (
176
- decl &&
177
- (decl.type === "FunctionDeclaration" ||
178
- decl.type === "ArrowFunctionExpression" ||
179
- decl.type === "FunctionExpression")
180
- ) {
181
- isAsync = decl.async;
182
- }
183
- },
184
- });
185
-
186
- return isAsync;
187
- }
188
179
 
189
180
  return {
190
181
  name: "react-client-manifest",
@@ -224,11 +215,6 @@ function reactClientManifestPlugin({
224
215
  name: path.basename(absPath, path.extname(absPath)),
225
216
  });
226
217
  } else if (isPageOrLayout(absPath)) {
227
- if (!isAsyncDefaultExport(code)) {
228
- this.warn(
229
- `[react-client-manifest] The file ${normalizedPath} is a page or layout without "use client" directive, but its default export is not an async function. Add "use client" if it's a client component, or make the default export async if it's a server component.`
230
- );
231
- }
232
218
  serverModules.add(normalizedPath);
233
219
  this.addWatchFile(absPath);
234
220
  const { imports, assets, csss } = await getImportsAndAssetsAndCsss(
@@ -258,6 +244,24 @@ function reactClientManifestPlugin({
258
244
  }
259
245
  }
260
246
  },
247
+ async transform(code, id) {
248
+ if (id.includes("\0") || id.startsWith("commonjsHelpers") || id.includes("node_modules/rollup") || id.includes("react-refresh")) return;
249
+ const normalizedId = id.split(path.sep).join(path.posix.sep);
250
+ const isClientModule = useClientRegex.test(code.trim());
251
+
252
+ if (isClientModule) {
253
+ // console.log("👉 [react-client-manifest] Found client module in transform:", normalizedId);
254
+ if (!clientModules.has(normalizedId)) {
255
+ clientModules.add(normalizedId);
256
+ updateManifestForModule(id, code, true);
257
+ this.emitFile({
258
+ type: "chunk",
259
+ id: id,
260
+ name: path.basename(id, path.extname(id)),
261
+ });
262
+ }
263
+ }
264
+ },
261
265
  async watchChange(id) {
262
266
  if (
263
267
  !id.endsWith(".tsx") &&
@@ -290,11 +294,6 @@ function reactClientManifestPlugin({
290
294
  } else {
291
295
  clientModules.delete(normalizedId);
292
296
  if (isPageOrLayout(id)) {
293
- if (!isAsyncDefaultExport(code)) {
294
- this.warn(
295
- `[react-client-manifest] The file ${normalizedId} is a page or layout without "use client" directive, but its default export is not an async function. Add "use client" if it's a client component, or make the default export async if it's a server component.`
296
- );
297
- }
298
297
  serverModules.add(normalizedId);
299
298
  this.addWatchFile(id);
300
299
  const { imports, assets, csss } = await getImportsAndAssetsAndCsss(
@@ -322,12 +321,52 @@ function reactClientManifestPlugin({
322
321
  generateBundle(outputOptions, bundle) {
323
322
  for (const [fileName, chunk] of Object.entries(bundle)) {
324
323
  if (chunk.type !== "chunk") continue;
324
+
325
+ // Process entry point facadeModuleId for default export preservation
326
+ if (chunk.facadeModuleId) {
327
+ const absModulePath = path.resolve(chunk.facadeModuleId);
328
+ if (!absModulePath.includes("\0") && !absModulePath.startsWith("commonjsHelpers")) {
329
+ const fileUrl = pathToFileURL(absModulePath).href;
330
+ manifest[fileUrl] = {
331
+ id: "/" + fileName,
332
+ chunks: "default",
333
+ name: "default",
334
+ };
335
+ manifest[fileUrl + "#default"] = {
336
+ id: "/" + fileName,
337
+ chunks: "default",
338
+ name: "default",
339
+ };
340
+
341
+ if (!chunk.exports.includes("default")) {
342
+ try {
343
+ const originalCode = readFileSync(absModulePath, "utf8");
344
+ const defaultName = getDefaultExportName(originalCode);
345
+ if (defaultName && chunk.exports.includes(defaultName)) {
346
+ chunk.code += `\nexport { ${defaultName} as default };\n`;
347
+ chunk.exports.push("default");
348
+ // console.log(`👉 [react-client-manifest] Appended default export alias to chunk ${fileName}: export { ${defaultName} as default };`);
349
+ }
350
+ } catch (err) {
351
+ // Ignore errors
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ // Map all modules in the chunk to this chunk in the manifest (only if they are client modules)
325
358
  for (const modulePath of Object.keys(chunk.modules)) {
359
+ if (modulePath.includes("\0") || modulePath.startsWith("commonjsHelpers")) continue;
326
360
  const absModulePath = path.resolve(modulePath);
327
- const baseFileUrl = pathToFileURL(absModulePath).href;
328
- for (const manifestKey in manifest) {
329
- if (manifestKey.startsWith(baseFileUrl)) {
330
- manifest[manifestKey].id = "/" + fileName;
361
+ const normalizedPath = absModulePath.split(path.sep).join(path.posix.sep);
362
+
363
+ if (clientModules.has(normalizedPath)) {
364
+ const fileUrl = pathToFileURL(absModulePath).href;
365
+ // Update the chunk id for all exports of this module
366
+ for (const key of Object.keys(manifest)) {
367
+ if (key === fileUrl || key.startsWith(fileUrl + "#")) {
368
+ manifest[key].id = "/" + fileName;
369
+ }
331
370
  }
332
371
  }
333
372
  }
@@ -47,7 +47,7 @@ function serverFunctionsPlugin() {
47
47
  };
48
48
  },
49
49
  // 🪄 After manifest exists, replace the placeholder with the final URL
50
- generateBundle(options, bundle) {
50
+ async generateBundle(options, bundle) {
51
51
  const manifest = manifestGeneratorPlugin.manifestData;
52
52
  const hashedPath =
53
53
  "/" + (manifest["serverFunctionProxy.js"] || "serverFunctionProxy.js");
@@ -74,14 +74,12 @@ function serverFunctionsPlugin() {
74
74
  "server_functions_manifest",
75
75
  "server-functions-manifest.json"
76
76
  );
77
- fs.mkdir(path.dirname(manifestPath), { recursive: true })
78
- .then(() =>
79
- fs.writeFile(manifestPath, JSON.stringify(manifestObj, null, 2))
80
- )
81
- .then(() => {
82
- // console.log(`[rollup-server-functions] Generated manifest at ${manifestPath}`);
83
- })
84
- .catch(console.error);
77
+ try {
78
+ await fs.mkdir(path.dirname(manifestPath), { recursive: true });
79
+ await fs.writeFile(manifestPath, JSON.stringify(manifestObj, null, 2));
80
+ } catch (err) {
81
+ console.error(err);
82
+ }
85
83
  },
86
84
  };
87
85
  }
@@ -78,7 +78,7 @@ module.exports = async function () {
78
78
  },
79
79
  // 🛑 ADD THIS MAGIC LINE
80
80
  // Tells Rollup: "Keep entry point signatures (export names) intact"
81
- preserveEntrySignatures: "exports-only",
81
+ preserveEntrySignatures: "strict",
82
82
  external: [
83
83
  "/refresh.js",
84
84
  "/__hmr_client__.js",
@@ -109,7 +109,6 @@ module.exports = async function () {
109
109
  }),
110
110
  commonjs({
111
111
  include: isEjected ? [/node_modules/, /dinou/] : /node_modules/,
112
- transformMixedEsModules: true,
113
112
  }),
114
113
  dinouAssetPlugin({
115
114
  include: regex,
@@ -138,41 +138,6 @@ async function getCSSEntries({
138
138
  return fileName.startsWith("page.") || fileName.startsWith("layout.");
139
139
  }
140
140
 
141
- function isAsyncDefaultExport(code) {
142
- const ast = parser.parse(code, {
143
- sourceType: "module",
144
- plugins: ["jsx", "typescript"],
145
- });
146
-
147
- let isAsync = false;
148
-
149
- traverse.default(ast, {
150
- ExportDefaultDeclaration(path) {
151
- let decl = path.node.declaration;
152
-
153
- if (decl.type === "Identifier") {
154
- const binding = path.scope.getBinding(decl.name);
155
- if (binding && binding.path) {
156
- decl = binding.path.node;
157
- if (decl.type === "VariableDeclarator") {
158
- decl = decl.init;
159
- }
160
- }
161
- }
162
-
163
- if (
164
- decl &&
165
- (decl.type === "FunctionDeclaration" ||
166
- decl.type === "ArrowFunctionExpression" ||
167
- decl.type === "FunctionExpression")
168
- ) {
169
- isAsync = decl.async;
170
- }
171
- },
172
- });
173
-
174
- return isAsync;
175
- }
176
141
 
177
142
  const files = await glob(["**/*.{js,jsx,ts,tsx}"], {
178
143
  cwd: srcDir,
@@ -209,11 +174,6 @@ async function getCSSEntries({
209
174
  }
210
175
  });
211
176
  } else if (isPageOrLayout(absPath)) {
212
- if (!isAsyncDefaultExport(code)) {
213
- console.warn(
214
- `[react-client-manifest] The file ${normalizedPath} is a page or layout without "use client" directive, but its default export is not an async function.`,
215
- );
216
- }
217
177
  try {
218
178
  const { csss } = await getImportsAndAssetsAndCsss(code, absPath);
219
179
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "4.0.16",
4
- "description": "Full-Stack React 19 framework with React Server Components, Server Functions, and Streaming SSR.",
3
+ "version": "5.0.1",
4
+ "description": "Lightweight-Ejectable Full-Stack React 19 Framework with support for React Server Components (RSC), Server Functions, Server-Side Rendering (SSR), Static Generation (SSG), Incremental Static Generation (ISG), and Incremental Static Regeneration (ISR)",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "dinou": "./cli.js"
@@ -1,50 +0,0 @@
1
- // babel-plugin-register-imports.js
2
- const { getAbsPathWithExt } = require("./get-abs-path-with-ext.js");
3
- const { pathToFileURL } = require("url");
4
-
5
- module.exports = function ({ types: t }) {
6
- return {
7
- visitor: {
8
- ImportDeclaration(pathNode, state) {
9
- const sourceValue = pathNode.node.source.value;
10
- const currentFile = state.file.opts.filename;
11
-
12
- // We try to get the absolute path
13
- const resolvedPath = getAbsPathWithExt(sourceValue, {
14
- parentURL: pathToFileURL(currentFile).href,
15
- });
16
-
17
- // If resolvedPath is null, it means it is a node_modules library
18
- // (because your helper returns null if it doesn't start with . or alias)
19
- const pathOrPackage = resolvedPath || sourceValue;
20
- const isPackage = !resolvedPath;
21
-
22
- const specifiers = pathNode.node.specifiers;
23
- const injections = [];
24
-
25
- specifiers.forEach((spec) => {
26
- if (t.isImportSpecifier(spec) || t.isImportDefaultSpecifier(spec)) {
27
- const localIdentifier = spec.local.name;
28
-
29
- injections.push(
30
- t.expressionStatement(
31
- t.callExpression(
32
- t.identifier("global.__DINOU_REGISTER_MODULE"),
33
- [
34
- t.identifier(localIdentifier),
35
- t.stringLiteral(pathOrPackage),
36
- t.booleanLiteral(isPackage),
37
- ]
38
- )
39
- )
40
- );
41
- }
42
- });
43
-
44
- if (injections.length > 0) {
45
- pathNode.insertAfter(injections);
46
- }
47
- },
48
- },
49
- };
50
- };
@@ -1,14 +0,0 @@
1
- const getJSX = require("./get-jsx.js");
2
- const getSSGJSX = require("./get-ssg-jsx.js");
3
- const { getJSXJSON } = require("./jsx-json.js");
4
-
5
- async function getSSGJSXOrJSX(reqPath, query, isDevelopment = false) {
6
- const isNotFound = null;
7
- const result = isDevelopment
8
- ? await getJSX(reqPath, query, isNotFound, isDevelopment)
9
- : (await getSSGJSX(getJSXJSON(reqPath))) ??
10
- (await getJSX(reqPath, query, isNotFound, isDevelopment));
11
- return result;
12
- }
13
-
14
- module.exports = getSSGJSXOrJSX;
@@ -1,83 +0,0 @@
1
- const path = require("path");
2
- const React = require("react");
3
- const importModule = require("./import-module");
4
-
5
- async function deserializeReactElement(serialized) {
6
- if (
7
- !serialized ||
8
- typeof serialized !== "object" ||
9
- !("props" in serialized)
10
- ) {
11
- return serialized;
12
- }
13
-
14
- const { type, modulePath, props, name, isPackage } = serialized;
15
- let Component = type;
16
-
17
- let isInvalidComponent = false;
18
-
19
- if (modulePath) {
20
- try {
21
- const mod = await importModule(
22
- isPackage ? modulePath : path.resolve(process.cwd(), modulePath)
23
- );
24
-
25
- if (name && name !== "default" && mod[name]) {
26
- Component = mod[name];
27
- } else {
28
- Component = mod.default ?? mod;
29
- }
30
- } catch (err) {
31
- console.error(`Error loading module ${modulePath}:`, err);
32
- Component = React.Fragment;
33
- }
34
- } else if (type === "__clientComponent__") {
35
- isInvalidComponent = true;
36
- } else if (type === "Suspense") {
37
- Component = React.Suspense;
38
- } else if (type === "Fragment") {
39
- Component = React.Fragment;
40
- }
41
-
42
- if (!Component) {
43
- Component = React.Fragment;
44
- }
45
-
46
- if (isInvalidComponent) {
47
- return undefined;
48
- }
49
-
50
- // Deserialize props
51
- const deserializedProps = {};
52
- for (const [key, value] of Object.entries(props)) {
53
- if (key === "children") {
54
- deserializedProps[key] = Array.isArray(value)
55
- ? await Promise.all(
56
- value.map((child) => deserializeReactElement(child))
57
- )
58
- : value
59
- ? await deserializeReactElement(value)
60
- : null;
61
- } else if (
62
- value &&
63
- typeof value === "object" &&
64
- "props" in value &&
65
- "modulePath" in value
66
- ) {
67
- deserializedProps[key] = await deserializeReactElement(value);
68
- } else {
69
- deserializedProps[key] = value;
70
- }
71
- }
72
-
73
- return React.createElement(Component, deserializedProps);
74
- }
75
-
76
- async function getSSGJSX(jsxJson) {
77
- if (!jsxJson) return;
78
- const { jsx } = jsxJson;
79
- const deserializedJSX = await deserializeReactElement(jsx);
80
- return deserializedJSX;
81
- }
82
-
83
- module.exports = getSSGJSX;
@@ -1,55 +0,0 @@
1
- // In-memory store
2
- const store = new Map();
3
-
4
- /**
5
- * Saves the result of the static generation.
6
- * @param {string} reqPath - The route (e.g., "/about/")
7
- * @param {object} json - { jsx, revalidate, generatedAt }
8
- */
9
- function setJSXJSON(reqPath, json) {
10
- // We save the object directly in memory.
11
- // As a reference, it is instantaneous.
12
- store.set(reqPath, json);
13
- }
14
-
15
- /**
16
- * Gets the data to serve the RSC or check revalidation.
17
- * @param {string} reqPath
18
- * @returns {object|undefined}
19
- */
20
- function getJSXJSON(reqPath) {
21
- return store.get(reqPath);
22
- }
23
-
24
- /**
25
- * Verifies if we have data for that route
26
- */
27
- function hasJSXJSON(reqPath) {
28
- return store.has(reqPath);
29
- }
30
-
31
- /**
32
- * (Optional) Deletes data if a page no longer exists
33
- */
34
- function deleteJSXJSON(reqPath) {
35
- store.delete(reqPath);
36
- }
37
-
38
- /**
39
- * 🆕 Gets all available static routes.
40
- * Replaces the old function that read the disk recursively.
41
- *
42
- * @returns {string[]} Array of routes (e.g., ["/", "/about/", "/blog/post-1/"])
43
- */
44
- function getStaticPaths() {
45
- // Array.from converts the iterator of keys into a real Array
46
- return Array.from(store.keys());
47
- }
48
-
49
- module.exports = {
50
- setJSXJSON,
51
- getJSXJSON,
52
- hasJSXJSON,
53
- deleteJSXJSON,
54
- getStaticPaths,
55
- };
@@ -1,18 +0,0 @@
1
- // src/core/register-hooks.js
2
- if (!global.__DINOU_MODULE_MAP) {
3
- global.__DINOU_MODULE_MAP = new WeakMap();
4
- }
5
-
6
- global.__DINOU_REGISTER_MODULE = function (mod, id, isPackage) {
7
- if (!mod) return;
8
-
9
- const meta = { id, isPackage };
10
-
11
- if (typeof mod === "object" || typeof mod === "function") {
12
- global.__DINOU_MODULE_MAP.set(mod, meta);
13
- }
14
-
15
- try {
16
- if (mod.default) global.__DINOU_MODULE_MAP.set(mod.default, meta);
17
- } catch (e) {}
18
- };