dinou 5.1.1 → 5.2.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 +18 -0
- package/dinou/core/navigation.js +2 -2
- package/dinou/core/server-function-proxy-webpack.js +7 -3
- package/dinou/core/server-function-proxy.js +7 -3
- package/dinou/core/server.js +87 -6
- package/dinou/esbuild/build.mjs +11 -0
- package/dinou/esbuild/dev.mjs +11 -0
- package/dinou/package.json +1 -1
- package/dinou/rollup/rollup.config.js +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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
|
+
## [5.2.0] - 2026-07-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Lightweight Plugin System & `dinou.config.js` support**: Added native support for extending the framework without manually modifying the core Express server.
|
|
12
|
+
* **Config Loader**: Automatically loads a `dinou.config.js` file at the root of the project to initialize custom plugins.
|
|
13
|
+
* **`onServerInit(app)` Hook**: Allows plugins to register global Express middlewares, custom endpoints (like webhooks), or API routes.
|
|
14
|
+
* **`onRequestContext(req, res, context)` Hook**: Allows plugins to inject custom properties (like `locale` or `userId`) into the RSC context across standard page requests, Server Actions, and dynamic compilation child processes.
|
|
15
|
+
* **Try-Catch Isolation**: Wrapped plugin hooks in safe boundaries with named logging to prevent third-party plugin errors from crashing the server.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **URL resolution for search params**: Fixed `useSearchParams` and `usePathname` to work correctly with URL hashes.
|
|
19
|
+
- **Server Actions with FormData**: Fixed server actions to correctly handle FormData arguments, supporting formData in any position in the args list.
|
|
20
|
+
|
|
21
|
+
## [5.1.2] - 2026-07-12
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Client Link Import Resolution**: Fixed `TypeError: Failed to resolve module specifier 'dinou/core/link.jsx'` client-side crashes occurring when importing and rendering `<Link>` from `"dinou"` inside Server Components under Esbuild and Rollup compilation environments.
|
|
25
|
+
|
|
8
26
|
## [5.1.1] - 2026-07-12
|
|
9
27
|
|
|
10
28
|
### Fixed
|
package/dinou/core/navigation.js
CHANGED
|
@@ -126,7 +126,7 @@ export function usePathname() {
|
|
|
126
126
|
return "";
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
const path = fullRoute.split("?")[0];
|
|
129
|
+
const path = fullRoute.split("#")[0].split("?")[0];
|
|
130
130
|
return normalizePath(path);
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -165,6 +165,6 @@ export function useSearchParams() {
|
|
|
165
165
|
|
|
166
166
|
if (typeof fullRoute !== "string") return new URLSearchParams();
|
|
167
167
|
|
|
168
|
-
const searchPart = fullRoute.split("?")[1] || "";
|
|
168
|
+
const searchPart = (fullRoute.split("?")[1] || "").split("#")[0];
|
|
169
169
|
return new URLSearchParams(searchPart);
|
|
170
170
|
}
|
|
@@ -22,13 +22,17 @@ function createServerFunctionProxy(id) {
|
|
|
22
22
|
"x-server-function-call": "1",
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const formDataIndex = args.findIndex((arg) => arg instanceof FormData);
|
|
26
|
+
|
|
27
|
+
if (formDataIndex !== -1) {
|
|
28
|
+
const formData = args[formDataIndex];
|
|
27
29
|
|
|
28
30
|
formData.append("__dinou_func_id", id);
|
|
31
|
+
formData.append("__dinou_formData_index", String(formDataIndex));
|
|
29
32
|
|
|
30
33
|
if (args.length > 1) {
|
|
31
|
-
|
|
34
|
+
const serializedArgs = args.map((arg, i) => (i === formDataIndex ? null : arg));
|
|
35
|
+
formData.append("__dinou_args", JSON.stringify(serializedArgs));
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
body = formData;
|
|
@@ -23,13 +23,17 @@ export function createServerFunctionProxy(id) {
|
|
|
23
23
|
"x-server-function-call": "1",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const formDataIndex = args.findIndex((arg) => arg instanceof FormData);
|
|
27
|
+
|
|
28
|
+
if (formDataIndex !== -1) {
|
|
29
|
+
const formData = args[formDataIndex];
|
|
28
30
|
|
|
29
31
|
formData.append("__dinou_func_id", id);
|
|
32
|
+
formData.append("__dinou_formData_index", String(formDataIndex));
|
|
30
33
|
|
|
31
34
|
if (args.length > 1) {
|
|
32
|
-
|
|
35
|
+
const serializedArgs = args.map((arg, i) => (i === formDataIndex ? null : arg));
|
|
36
|
+
formData.append("__dinou_args", JSON.stringify(serializedArgs));
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
body = formData;
|
package/dinou/core/server.js
CHANGED
|
@@ -72,6 +72,18 @@ addHook({
|
|
|
72
72
|
});
|
|
73
73
|
const importModule = require("./import-module");
|
|
74
74
|
const generateStatic = require("./generate-static.js");
|
|
75
|
+
|
|
76
|
+
// Load Dinou configuration and plugins
|
|
77
|
+
let dinouConfig = { plugins: [] };
|
|
78
|
+
const dinouConfigPath = path.resolve(process.cwd(), "dinou.config.js");
|
|
79
|
+
if (existsSync(dinouConfigPath)) {
|
|
80
|
+
try {
|
|
81
|
+
dinouConfig = require(dinouConfigPath);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error("[Dinou] Error loading dinou.config.js:", err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
const renderAppToHtml = require("./render-app-to-html.js");
|
|
76
88
|
const { revalidating, regenerating } = require("./revalidating.js");
|
|
77
89
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
@@ -283,6 +295,21 @@ const cookieParser = require("cookie-parser");
|
|
|
283
295
|
const appUseCookieParser = cookieParser();
|
|
284
296
|
const app = express();
|
|
285
297
|
app.use(appUseCookieParser);
|
|
298
|
+
|
|
299
|
+
// Run plugins onServerInit hook
|
|
300
|
+
if (dinouConfig.plugins && Array.isArray(dinouConfig.plugins)) {
|
|
301
|
+
for (const plugin of dinouConfig.plugins) {
|
|
302
|
+
if (typeof plugin.onServerInit === "function") {
|
|
303
|
+
try {
|
|
304
|
+
plugin.onServerInit(app);
|
|
305
|
+
console.log(`[Dinou] Plugin initialized: "${plugin.name || "unnamed"}"`);
|
|
306
|
+
} catch (err) {
|
|
307
|
+
console.error(`[Dinou] Error initializing plugin "${plugin.name || "unnamed"}":`, err);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
286
313
|
app.use(express.json());
|
|
287
314
|
|
|
288
315
|
// ============================================================
|
|
@@ -406,6 +433,20 @@ function getContext(req, res) {
|
|
|
406
433
|
redirect: (...args) => safeResCall("redirect", ...args),
|
|
407
434
|
},
|
|
408
435
|
};
|
|
436
|
+
|
|
437
|
+
// Run plugins onRequestContext hook
|
|
438
|
+
if (dinouConfig.plugins && Array.isArray(dinouConfig.plugins)) {
|
|
439
|
+
for (const plugin of dinouConfig.plugins) {
|
|
440
|
+
if (typeof plugin.onRequestContext === "function") {
|
|
441
|
+
try {
|
|
442
|
+
plugin.onRequestContext(req, res, context);
|
|
443
|
+
} catch (err) {
|
|
444
|
+
console.error(`[Dinou] Error running onRequestContext in plugin "${plugin.name || "unnamed"}":`, err);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
409
450
|
return context;
|
|
410
451
|
}
|
|
411
452
|
|
|
@@ -534,6 +575,20 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
534
575
|
},
|
|
535
576
|
},
|
|
536
577
|
};
|
|
578
|
+
|
|
579
|
+
// Run plugins onRequestContext hook
|
|
580
|
+
if (dinouConfig.plugins && Array.isArray(dinouConfig.plugins)) {
|
|
581
|
+
for (const plugin of dinouConfig.plugins) {
|
|
582
|
+
if (typeof plugin.onRequestContext === "function") {
|
|
583
|
+
try {
|
|
584
|
+
plugin.onRequestContext(req, res, context);
|
|
585
|
+
} catch (err) {
|
|
586
|
+
console.error(`[Dinou] Error running onRequestContext in plugin "${plugin.name || "unnamed"}":`, err);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
537
592
|
return context;
|
|
538
593
|
}
|
|
539
594
|
|
|
@@ -1084,6 +1139,20 @@ app.get(/^\/.*\/?$/, async (req, res) => {
|
|
|
1084
1139
|
},
|
|
1085
1140
|
// Do not include res here
|
|
1086
1141
|
};
|
|
1142
|
+
|
|
1143
|
+
// Run plugins onRequestContext hook
|
|
1144
|
+
if (dinouConfig.plugins && Array.isArray(dinouConfig.plugins)) {
|
|
1145
|
+
for (const plugin of dinouConfig.plugins) {
|
|
1146
|
+
if (typeof plugin.onRequestContext === "function") {
|
|
1147
|
+
try {
|
|
1148
|
+
plugin.onRequestContext(req, res, contextForChild);
|
|
1149
|
+
} catch (err) {
|
|
1150
|
+
console.error(`[Dinou] Error running onRequestContext in plugin "${plugin.name || "unnamed"}":`, err);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1087
1156
|
processLimiter
|
|
1088
1157
|
.run(async () => {
|
|
1089
1158
|
const isDynamic = true;
|
|
@@ -1211,7 +1280,12 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1211
1280
|
const formData = new FormData();
|
|
1212
1281
|
|
|
1213
1282
|
for (const key in req.body) {
|
|
1214
|
-
if (
|
|
1283
|
+
if (
|
|
1284
|
+
key === "__dinou_func_id" ||
|
|
1285
|
+
key === "__dinou_args" ||
|
|
1286
|
+
key === "__dinou_formData_index"
|
|
1287
|
+
)
|
|
1288
|
+
continue;
|
|
1215
1289
|
formData.append(key, req.body[key]);
|
|
1216
1290
|
}
|
|
1217
1291
|
|
|
@@ -1222,15 +1296,22 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
1222
1296
|
}
|
|
1223
1297
|
}
|
|
1224
1298
|
|
|
1225
|
-
args = [formData];
|
|
1226
|
-
|
|
1227
1299
|
if (req.body.__dinou_args) {
|
|
1228
1300
|
try {
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1301
|
+
const parsedArgs = JSON.parse(req.body.__dinou_args);
|
|
1302
|
+
if (req.body.__dinou_formData_index !== undefined) {
|
|
1303
|
+
const formDataIndex = parseInt(req.body.__dinou_formData_index, 10);
|
|
1304
|
+
parsedArgs[formDataIndex] = formData;
|
|
1305
|
+
args = parsedArgs;
|
|
1306
|
+
} else {
|
|
1307
|
+
args = [formData, ...parsedArgs];
|
|
1308
|
+
}
|
|
1231
1309
|
} catch (e) {
|
|
1232
|
-
console.error("Error parsing extra args in multipart request");
|
|
1310
|
+
console.error("Error parsing extra args in multipart request", e);
|
|
1311
|
+
args = [formData];
|
|
1233
1312
|
}
|
|
1313
|
+
} else {
|
|
1314
|
+
args = [formData];
|
|
1234
1315
|
}
|
|
1235
1316
|
} else {
|
|
1236
1317
|
id = req.body.id;
|
package/dinou/esbuild/build.mjs
CHANGED
|
@@ -18,6 +18,10 @@ const absPathToClientRedirect = path.resolve(
|
|
|
18
18
|
__dirname,
|
|
19
19
|
"../core/client-redirect.jsx"
|
|
20
20
|
);
|
|
21
|
+
const absPathToLink = path.resolve(
|
|
22
|
+
__dirname,
|
|
23
|
+
"../core/link.jsx"
|
|
24
|
+
);
|
|
21
25
|
|
|
22
26
|
const frameworkEntryPoints = {
|
|
23
27
|
main: path.resolve(__dirname, "../core/client.jsx"),
|
|
@@ -32,6 +36,7 @@ const frameworkEntryPoints = {
|
|
|
32
36
|
"react-refresh/react-refresh-entry.js"
|
|
33
37
|
),
|
|
34
38
|
dinouClientRedirect: absPathToClientRedirect,
|
|
39
|
+
dinouLink: absPathToLink,
|
|
35
40
|
};
|
|
36
41
|
|
|
37
42
|
try {
|
|
@@ -46,6 +51,12 @@ try {
|
|
|
46
51
|
true,
|
|
47
52
|
manifest
|
|
48
53
|
);
|
|
54
|
+
updateManifestForModule(
|
|
55
|
+
absPathToLink,
|
|
56
|
+
await fs.readFile(absPathToLink, "utf8"),
|
|
57
|
+
true,
|
|
58
|
+
manifest
|
|
59
|
+
);
|
|
49
60
|
|
|
50
61
|
const componentEntryPoints = [...esbuildEntries].reduce(
|
|
51
62
|
(acc, dCE) => ({ ...acc, [dCE.outfileName]: dCE.absPath }),
|
package/dinou/esbuild/dev.mjs
CHANGED
|
@@ -26,6 +26,10 @@ const absPathToClientRedirect = path.resolve(
|
|
|
26
26
|
__dirname,
|
|
27
27
|
"../core/client-redirect.jsx"
|
|
28
28
|
);
|
|
29
|
+
const absPathToLink = path.resolve(
|
|
30
|
+
__dirname,
|
|
31
|
+
"../core/link.jsx"
|
|
32
|
+
);
|
|
29
33
|
|
|
30
34
|
const frameworkEntryPoints = {
|
|
31
35
|
main: path.resolve(__dirname, "../core/client.jsx"),
|
|
@@ -40,6 +44,7 @@ const frameworkEntryPoints = {
|
|
|
40
44
|
"react-refresh/react-refresh-entry.js"
|
|
41
45
|
),
|
|
42
46
|
dinouClientRedirect: absPathToClientRedirect,
|
|
47
|
+
dinouLink: absPathToLink,
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
const changedIds = new Set();
|
|
@@ -70,6 +75,12 @@ async function updateEntriesAndComponents() {
|
|
|
70
75
|
true,
|
|
71
76
|
manifest
|
|
72
77
|
);
|
|
78
|
+
updateManifestForModule(
|
|
79
|
+
absPathToLink,
|
|
80
|
+
await fs.readFile(absPathToLink, "utf8"),
|
|
81
|
+
true,
|
|
82
|
+
manifest
|
|
83
|
+
);
|
|
73
84
|
|
|
74
85
|
currentServerFiles = new Set(
|
|
75
86
|
serverFiles.map((f) => normalizePath(path.resolve(f)))
|
package/dinou/package.json
CHANGED
|
@@ -52,6 +52,10 @@ module.exports = async function () {
|
|
|
52
52
|
__dirname,
|
|
53
53
|
"../core/client-redirect.jsx",
|
|
54
54
|
),
|
|
55
|
+
dinouLink: path.resolve(
|
|
56
|
+
__dirname,
|
|
57
|
+
"../core/link.jsx",
|
|
58
|
+
),
|
|
55
59
|
}
|
|
56
60
|
: {
|
|
57
61
|
main: path.resolve(__dirname, "../core/client.jsx"),
|
|
@@ -64,6 +68,10 @@ module.exports = async function () {
|
|
|
64
68
|
__dirname,
|
|
65
69
|
"../core/client-redirect.jsx",
|
|
66
70
|
),
|
|
71
|
+
dinouLink: path.resolve(
|
|
72
|
+
__dirname,
|
|
73
|
+
"../core/link.jsx",
|
|
74
|
+
),
|
|
67
75
|
},
|
|
68
76
|
output: {
|
|
69
77
|
dir: outputDirectory,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dinou",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
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": {
|