dinou 2.0.2 → 2.1.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/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [2.1.0]
9
+
10
+ ### Added
11
+
12
+ - Server Functions handling.
13
+
14
+ ## [2.0.3]
15
+
16
+ ### Fixed
17
+
18
+ - Fixed cross-platform path handling using Node.js `path` module for macOS/Linux compatibility in `get-jsx.js`, `get-error-jsx.js`, and `build-static-pages.js`.
19
+ - Added `awaitWriteFinish` to `chokidar` in `server.js` to avoid parsing incomplete manifest files on macOS.
20
+
21
+ ## [2.0.2]
22
+
23
+ ### Fixed
24
+
25
+ - Watch server components in react manifest plugin.
26
+
27
+ ## [2.0.1]
28
+
29
+ ### Fixed
30
+
31
+ - Use createFromFetch from react-server-dom-esm in client-error.jsx.
32
+
8
33
  ## [2.0.0]
9
34
 
10
35
  ### Changed
@@ -357,7 +357,7 @@ async function buildStaticPages() {
357
357
  const updatedSlots = {};
358
358
  for (const [slotName, slotElement] of Object.entries(slots)) {
359
359
  const slotFolder = path.join(
360
- layoutPath.split("\\").slice(0, -1).join("\\"),
360
+ path.dirname(layoutPath),
361
361
  `@${slotName}`
362
362
  );
363
363
  const [slotPath] = getFilePathAndDynamicParams(
@@ -58,10 +58,10 @@ function getErrorJSX(reqPath, query, error) {
58
58
  });
59
59
 
60
60
  const noLayoutErrorPath = path.join(
61
- pagePath.split("\\").slice(0, -1).join("\\"),
61
+ path.dirname(pagePath),
62
62
  `no_layout_error`
63
63
  );
64
- if (existsSync(path.resolve(process.cwd(), `${noLayoutErrorPath}`))) {
64
+ if (existsSync(noLayoutErrorPath)) {
65
65
  return jsx;
66
66
  }
67
67
 
package/dinou/get-jsx.js CHANGED
@@ -55,11 +55,13 @@ async function getJSX(reqPath, query) {
55
55
  params: dParams ?? {},
56
56
  query,
57
57
  });
58
+
59
+ const notFoundDir = path.dirname(notFoundPath);
58
60
  const noLayoutNotFoundPath = path.join(
59
- notFoundPath.split("\\").slice(0, -1).join("\\"),
61
+ notFoundDir,
60
62
  `no_layout_not_found`
61
63
  );
62
- if (existsSync(path.resolve(process.cwd(), `${noLayoutNotFoundPath}`))) {
64
+ if (existsSync(noLayoutNotFoundPath)) {
63
65
  return jsx;
64
66
  }
65
67
  }
@@ -71,7 +73,8 @@ async function getJSX(reqPath, query) {
71
73
  params: dynamicParams,
72
74
  query,
73
75
  };
74
- const pageFolder = pagePath.split("\\").slice(0, -1).join("\\");
76
+
77
+ const pageFolder = path.dirname(pagePath);
75
78
  const [pageFunctionsPath] = getFilePathAndDynamicParams(
76
79
  reqSegments,
77
80
  query,
@@ -0,0 +1,23 @@
1
+ // public/server-function-proxy.js
2
+ import { createFromFetch } from "@matthamlin/react-server-dom-esm/client";
3
+
4
+ export function createServerFunctionProxy(id) {
5
+ return new Proxy(() => {}, {
6
+ apply: async (_target, _thisArg, args) => {
7
+ const res = await fetch("/____server_function____", {
8
+ method: "POST",
9
+ headers: { "Content-Type": "application/json" },
10
+ body: JSON.stringify({ id, args }),
11
+ });
12
+ if (!res.ok) throw new Error("Server function failed");
13
+
14
+ const contentType = res.headers.get("content-type") || "";
15
+
16
+ if (contentType.includes("text/x-component")) {
17
+ return createFromFetch(Promise.resolve(res));
18
+ } else {
19
+ return res.json();
20
+ }
21
+ },
22
+ });
23
+ }
package/dinou/server.js CHANGED
@@ -289,6 +289,48 @@ app.get(/^\/.*\/?$/, async (req, res) => {
289
289
  }
290
290
  });
291
291
 
292
+ app.post("/____server_function____", async (req, res) => {
293
+ try {
294
+ const { id, args } = req.body;
295
+ const [fileUrl, exportName] = id.split("#");
296
+
297
+ let relativePath = fileUrl.replace(/^file:\/\/\/?/, "");
298
+ const absolutePath = path.resolve(process.cwd(), relativePath);
299
+
300
+ const mod = require(absolutePath);
301
+
302
+ const fn = exportName === "default" ? mod.default : mod[exportName];
303
+
304
+ if (typeof fn !== "function") {
305
+ return res.status(400).json({ error: "Export is not a function" });
306
+ }
307
+
308
+ const result = await fn(...args);
309
+
310
+ if (
311
+ result &&
312
+ result.$$typeof === Symbol.for("react.transitional.element")
313
+ ) {
314
+ res.setHeader("Content-Type", "text/x-component");
315
+ const manifest = readFileSync(
316
+ path.resolve(
317
+ process.cwd(),
318
+ `${webpackFolder}/react-client-manifest.json`
319
+ ),
320
+ "utf8"
321
+ );
322
+ const moduleMap = JSON.parse(manifest);
323
+ const { pipe } = renderToPipeableStream(result, moduleMap);
324
+ pipe(res);
325
+ } else {
326
+ res.json(result);
327
+ }
328
+ } catch (err) {
329
+ console.error("Server function error:", err);
330
+ res.status(500).json({ error: err.message });
331
+ }
332
+ });
333
+
292
334
  const port = process.env.PORT || 3000;
293
335
 
294
336
  app.listen(port, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Minimal React 19 Framework",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,79 @@
1
+ // rollup-plugin-server-functions.js
2
+ const path = require("path");
3
+ const parser = require("@babel/parser");
4
+ const traverse = require("@babel/traverse").default;
5
+
6
+ function parseExports(code) {
7
+ const ast = parser.parse(code, {
8
+ sourceType: "module",
9
+ plugins: ["jsx", "typescript"],
10
+ });
11
+ const exports = new Set();
12
+
13
+ traverse(ast, {
14
+ ExportDefaultDeclaration() {
15
+ exports.add("default");
16
+ },
17
+ ExportNamedDeclaration(p) {
18
+ if (p.node.declaration) {
19
+ if (p.node.declaration.type === "FunctionDeclaration") {
20
+ exports.add(p.node.declaration.id.name);
21
+ } else if (p.node.declaration.type === "VariableDeclaration") {
22
+ p.node.declaration.declarations.forEach((d) => {
23
+ if (d.id.type === "Identifier") {
24
+ exports.add(d.id.name);
25
+ }
26
+ });
27
+ }
28
+ } else if (p.node.specifiers) {
29
+ p.node.specifiers.forEach((s) => {
30
+ if (s.type === "ExportSpecifier") {
31
+ exports.add(s.exported.name);
32
+ }
33
+ });
34
+ }
35
+ },
36
+ });
37
+
38
+ return Array.from(exports);
39
+ }
40
+
41
+ function serverFunctionsPlugin() {
42
+ return {
43
+ name: "server-functions-proxy",
44
+ transform(code, id) {
45
+ if (!code.trim().startsWith('"use server"')) return null;
46
+
47
+ const exports = parseExports(code);
48
+ if (exports.length === 0) return null;
49
+
50
+ const fileUrl = `file:///${path.relative(process.cwd(), id)}`;
51
+
52
+ // Generamos un módulo que exporta proxies en lugar del código real
53
+ let proxyCode = `
54
+ import { createServerFunctionProxy } from "/serverFunctionProxy.js";
55
+ `;
56
+
57
+ for (const exp of exports) {
58
+ const key =
59
+ exp === "default" ? `${fileUrl}#default` : `${fileUrl}#${exp}`;
60
+ if (exp === "default") {
61
+ proxyCode += `export default createServerFunctionProxy(${JSON.stringify(
62
+ key
63
+ )});\n`;
64
+ } else {
65
+ proxyCode += `export const ${exp} = createServerFunctionProxy(${JSON.stringify(
66
+ key
67
+ )});\n`;
68
+ }
69
+ }
70
+
71
+ return {
72
+ code: proxyCode,
73
+ map: null,
74
+ };
75
+ },
76
+ };
77
+ }
78
+
79
+ module.exports = serverFunctionsPlugin;
package/rollup.config.js CHANGED
@@ -12,6 +12,7 @@ const reactRefreshWrapModules = require("./react-refresh/react-refresh-wrap-modu
12
12
  const { esmHmrPlugin } = require("./react-refresh/rollup-plugin-esm-hmr.js");
13
13
  const dinouAssetPlugin = require("./rollup-plugins/dinou-asset-plugin.js");
14
14
  const tsconfigPaths = require("rollup-plugin-tsconfig-paths");
15
+ const serverFunctionsPlugin = require("./rollup-plugins/rollup-plugin-server-functions");
15
16
 
16
17
  const isDevelopment = process.env.NODE_ENV !== "production";
17
18
  const outputDirectory = isDevelopment ? "public" : "dist3";
@@ -31,10 +32,18 @@ module.exports = async function () {
31
32
  ),
32
33
  main: path.resolve(__dirname, "dinou/client.jsx"),
33
34
  error: path.resolve(__dirname, "dinou/client-error.jsx"),
35
+ serverFunctionProxy: path.resolve(
36
+ __dirname,
37
+ "dinou/server-function-proxy.js"
38
+ ),
34
39
  }
35
40
  : {
36
41
  main: path.resolve(__dirname, "dinou/client.jsx"),
37
42
  error: path.resolve(__dirname, "dinou/client-error.jsx"),
43
+ serverFunctionProxy: path.resolve(
44
+ __dirname,
45
+ "dinou/server-function-proxy.js"
46
+ ),
38
47
  },
39
48
  output: {
40
49
  dir: outputDirectory,
@@ -95,7 +104,12 @@ module.exports = async function () {
95
104
  },
96
105
  }),
97
106
  copy({
98
- targets: [{ src: "favicons/*", dest: outputDirectory }],
107
+ targets: [
108
+ {
109
+ src: "favicons/*",
110
+ dest: outputDirectory,
111
+ },
112
+ ],
99
113
  flatten: true,
100
114
  }),
101
115
  reactClientManifest({
@@ -103,6 +117,7 @@ module.exports = async function () {
103
117
  }),
104
118
  isDevelopment && reactRefreshWrapModules(),
105
119
  isDevelopment && esmHmrPlugin(),
120
+ serverFunctionsPlugin(),
106
121
  ].filter(Boolean),
107
122
  watch: {
108
123
  exclude: ["public/**"],
@@ -111,6 +126,9 @@ module.exports = async function () {
111
126
  if (
112
127
  warning.message.includes(
113
128
  'Module level directives cause errors when bundled, "use client"'
129
+ ) ||
130
+ warning.message.includes(
131
+ 'Module level directives cause errors when bundled, "use server"'
114
132
  )
115
133
  ) {
116
134
  return;