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 +25 -0
- package/dinou/build-static-pages.js +1 -1
- package/dinou/get-error-jsx.js +2 -2
- package/dinou/get-jsx.js +6 -3
- package/dinou/server-function-proxy.js +23 -0
- package/dinou/server.js +42 -0
- package/package.json +1 -1
- package/rollup-plugins/rollup-plugin-server-functions.js +79 -0
- package/rollup.config.js +19 -1
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
|
-
|
|
360
|
+
path.dirname(layoutPath),
|
|
361
361
|
`@${slotName}`
|
|
362
362
|
);
|
|
363
363
|
const [slotPath] = getFilePathAndDynamicParams(
|
package/dinou/get-error-jsx.js
CHANGED
|
@@ -58,10 +58,10 @@ function getErrorJSX(reqPath, query, error) {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
const noLayoutErrorPath = path.join(
|
|
61
|
-
|
|
61
|
+
path.dirname(pagePath),
|
|
62
62
|
`no_layout_error`
|
|
63
63
|
);
|
|
64
|
-
if (existsSync(
|
|
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
|
-
|
|
61
|
+
notFoundDir,
|
|
60
62
|
`no_layout_not_found`
|
|
61
63
|
);
|
|
62
|
-
if (existsSync(
|
|
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
|
-
|
|
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
|
@@ -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: [
|
|
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;
|