dinou 5.0.2 → 5.0.3

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,15 @@ 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.0.3] - 2026-07-09
9
+
10
+ ### Added
11
+ - **Native React 19 Server Actions & `callServer` Support**: Enabled full integration for submitting forms directly from Server Components using the native `<form action={ServerAction}>` pattern.
12
+ - **Client-side Integration**: Implemented the global `callServer` option hook within `createFromFetch` in both the ESM client (`client.jsx`) and Webpack client (`client-webpack.jsx`). React now intercepts form submits and marshals arguments through Dinou's `createServerFunctionProxy` seamlessly.
13
+ - **Server-side Module Loader Integration**: Updated `babel-esm-loader.js` to automatically scan `"use server"` files and register exported functions using React's `registerServerReference` runtime call.
14
+ - **Route-Level Soft Navigation Error Recovery**: Integrated client-side React `ErrorBoundary` support wrapping the router context. If rendering crashes during a soft navigation or initial mount (on either Server or Client components), the router captures the failure and dynamically fetches `/____rsc_payload_error____` to render the custom slot-based error page fallback, avoiding white screens and React root unmounts.
15
+
16
+
8
17
  ## [5.0.2] - 2026-07-08
9
18
 
10
19
  ### 🛡️ Security & Route Control
@@ -6,6 +6,8 @@ const createScopedName = require("./createScopedName");
6
6
  const { extensionsWithDot } = require("./asset-extensions.js");
7
7
  const { getAbsPathWithExt } = require("./get-abs-path-with-ext.js");
8
8
 
9
+ const { normalizePathCase } = require("./path-utils.js");
10
+
9
11
  const Module = require("module");
10
12
  const originalResolveFilename = Module._resolveFilename;
11
13
  const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
@@ -90,7 +92,10 @@ exports.load = async function load(url, context, defaultLoad) {
90
92
  } catch (e) {
91
93
  throw e;
92
94
  }
93
- const rel = path.relative(process.cwd(), filename);
95
+ const rel = path.relative(
96
+ normalizePathCase(process.cwd()),
97
+ normalizePathCase(filename)
98
+ );
94
99
  const source = fs.readFileSync(filename, "utf-8");
95
100
  const urlToReturn = pathToFileURL(filename).href;
96
101
 
@@ -144,6 +149,48 @@ exports.load = async function load(url, context, defaultLoad) {
144
149
  };
145
150
  }
146
151
 
152
+ const { useServerRegex } = require("../constants.js");
153
+ const hasUseServer = useServerRegex.test(source);
154
+
155
+ if (isReactServer && hasUseServer) {
156
+ const parseExports = require("./parse-exports.js");
157
+ const exports = parseExports(source);
158
+
159
+ const { code } = await transformAsync(source, {
160
+ filename,
161
+ presets: [
162
+ ["@babel/preset-react", { runtime: "automatic" }],
163
+ "@babel/preset-typescript",
164
+ ],
165
+ sourceMaps: "inline",
166
+ ast: false,
167
+ });
168
+
169
+ let newSrc = code + "\n\n";
170
+
171
+ if (!isWebpack) {
172
+ const packageJsonPath = require.resolve("@roggc/react-server-dom-esm/package.json");
173
+ const serverNodePath = path.join(path.dirname(packageJsonPath), "server.node.js");
174
+ const serverNodeUrl = pathToFileURL(serverNodePath).href;
175
+ newSrc += `import pkgServer from ${JSON.stringify(serverNodeUrl)};\n`;
176
+ newSrc += 'const {registerServerReference} = pkgServer;\n';
177
+ }
178
+
179
+ const relativeFileUrl = "file:///" + rel.replace(/\\/g, "/");
180
+ for (const name of exports) {
181
+ if (name !== 'default') {
182
+ newSrc += `registerServerReference(${name}, ${JSON.stringify(relativeFileUrl)}, ${JSON.stringify(name)});\n`;
183
+ }
184
+ }
185
+
186
+ return {
187
+ format: "module",
188
+ source: newSrc,
189
+ shortCircuit: true,
190
+ url: urlToReturn,
191
+ };
192
+ }
193
+
147
194
  if (ext === ".js") {
148
195
  return {
149
196
  format: "module",
@@ -5,11 +5,13 @@ import {
5
5
  useTransition,
6
6
  useLayoutEffect,
7
7
  useMemo,
8
+ Component,
8
9
  } from "react";
9
10
  import { createFromFetch } from "react-server-dom-webpack/client";
10
11
  import { hydrateRoot } from "react-dom/client";
11
12
  import { RouterContext } from "./navigation.js";
12
13
  import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
14
+ import { createServerFunctionProxy } from "./server-function-proxy-webpack.js";
13
15
 
14
16
  // ====================================================================
15
17
  // 1. GLOBAL STATE (Outside the component)
@@ -38,30 +40,182 @@ const isHashChangeOnly = (finalPath) => {
38
40
  );
39
41
  };
40
42
 
41
- const getRSCPayload = (rscKey) => {
43
+ let isInitialErrorLoad = true;
44
+
45
+ const getRSCPayload = (rscKey, isPrefetch = false) => {
42
46
  const url = rscKey.split("::")[0];
43
- // Important: url must already be normalized here
47
+ // 1. Check Idempotence (Avoids the infinite loop of React)
44
48
  if (cache.has(url)) return cache.get(url);
45
49
 
46
- const content = createFromFetch(
47
- fetch("/____rsc_payload_error____" + url, {
50
+ let promise;
51
+ if (isInitialErrorLoad && url === getCurrentRoute()) {
52
+ isInitialErrorLoad = false;
53
+ const payloadUrl = "/____rsc_payload_error____" + url;
54
+ promise = createFromFetch(
55
+ fetch(payloadUrl, {
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ },
60
+ body: JSON.stringify({
61
+ error: {
62
+ message: window.__DINOU_ERROR_MESSAGE__ || "Unknown Error",
63
+ stack: window.__DINOU_ERROR_STACK__,
64
+ name: window.__DINOU_ERROR_NAME__,
65
+ },
66
+ }),
67
+ }).then((res) => {
68
+ if (res.headers.has("x-rsc-redirect")) {
69
+ const redirectUrl = res.headers.get("x-rsc-redirect");
70
+ cache.delete(url);
71
+ if (!isPrefetch) {
72
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
73
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
74
+ } else {
75
+ window.location.href = redirectUrl;
76
+ }
77
+ }
78
+ return new Promise(() => {});
79
+ }
80
+ return res;
81
+ }),
82
+ {
83
+ callServer: async (id, args) => {
84
+ const proxy = createServerFunctionProxy(id);
85
+ return proxy(...args);
86
+ }
87
+ }
88
+ );
89
+ } else {
90
+ // Normal client.jsx load logic!
91
+ let payloadUrl = "/____rsc_payload____" + url;
92
+ const buildId = window.__DINOU_BUILD_ID__;
93
+ if (buildId) {
94
+ payloadUrl += (payloadUrl.includes("?") ? "&" : "?") + "buildId=" + buildId;
95
+ window.__DINOU_BUILD_ID__ = undefined;
96
+ }
97
+
98
+ promise = createFromFetch(
99
+ fetch(payloadUrl).then((res) => {
100
+ if (res.headers.has("x-rsc-redirect")) {
101
+ const redirectUrl = res.headers.get("x-rsc-redirect");
102
+ cache.delete(url);
103
+ if (!isPrefetch) {
104
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
105
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
106
+ } else {
107
+ window.location.href = redirectUrl;
108
+ }
109
+ }
110
+ return new Promise(() => {});
111
+ }
112
+ return res;
113
+ }),
114
+ {
115
+ callServer: async (id, args) => {
116
+ const proxy = createServerFunctionProxy(id);
117
+ return proxy(...args);
118
+ }
119
+ }
120
+ );
121
+ }
122
+
123
+ cache.set(url, promise);
124
+ return promise;
125
+ };
126
+
127
+ const getErrorRSCPayload = (route, error) => {
128
+ const url = route.split("::")[0];
129
+ const cacheKey = `error::${url}::${error.message || String(error)}`;
130
+ if (cache.has(cacheKey)) {
131
+ return cache.get(cacheKey);
132
+ }
133
+
134
+ const payloadUrl = "/____rsc_payload_error____" + url;
135
+ const promise = createFromFetch(
136
+ fetch(payloadUrl, {
48
137
  method: "POST",
49
138
  headers: {
50
139
  "Content-Type": "application/json",
51
140
  },
52
141
  body: JSON.stringify({
53
142
  error: {
54
- message: window.__DINOU_ERROR_MESSAGE__ || "Unknown Error",
55
- stack: window.__DINOU_ERROR_STACK__,
56
- name: window.__DINOU_ERROR_NAME__,
143
+ message: error.message || "Unknown Error",
144
+ name: error.name,
145
+ stack: error.stack,
57
146
  },
58
147
  }),
148
+ }).then((res) => {
149
+ if (res.headers.has("x-rsc-redirect")) {
150
+ const redirectUrl = res.headers.get("x-rsc-redirect");
151
+ cache.delete(cacheKey);
152
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
153
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
154
+ } else {
155
+ window.location.href = redirectUrl;
156
+ }
157
+ return new Promise(() => {});
158
+ }
159
+ return res;
59
160
  }),
161
+ {
162
+ callServer: async (id, args) => {
163
+ const proxy = createServerFunctionProxy(id);
164
+ return proxy(...args);
165
+ }
166
+ }
60
167
  );
61
- cache.set(url, content);
62
- return content;
168
+ cache.set(cacheKey, promise);
169
+ return promise;
63
170
  };
64
171
 
172
+ class ErrorBoundary extends Component {
173
+ constructor(props) {
174
+ super(props);
175
+ this.state = { hasError: false, error: null };
176
+ }
177
+
178
+ static getDerivedStateFromError(error) {
179
+ return { hasError: true, error };
180
+ }
181
+
182
+ componentDidCatch(error, errorInfo) {
183
+ console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
184
+ if (this.props.onError) {
185
+ this.props.onError(error);
186
+ }
187
+ }
188
+
189
+ render() {
190
+ if (this.state.hasError) {
191
+ if (this.props.fallback) {
192
+ return this.props.fallback(this.state.error);
193
+ }
194
+ const isDev = process.env.NODE_ENV !== "production";
195
+ return (
196
+ <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
197
+ <h2>Application Error</h2>
198
+ <p>An unexpected error occurred on the client.</p>
199
+ <pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
200
+ {isDev ? (
201
+ <>
202
+ <div style={{ fontWeight: "bold", marginBottom: "10px" }}>
203
+ {this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
204
+ </div>
205
+ <div>{this.state.error?.stack}</div>
206
+ </>
207
+ ) : (
208
+ this.state.error?.message || String(this.state.error)
209
+ )}
210
+ </pre>
211
+ </div>
212
+ );
213
+ }
214
+ return this.props.children;
215
+ }
216
+ }
217
+
218
+
65
219
  // ====================================================================
66
220
  // 3. ROUTER COMPONENT
67
221
  // ====================================================================
@@ -71,18 +225,7 @@ function Router() {
71
225
  const [isPopState, setIsPopState] = useState(false);
72
226
  const [isPending, startTransition] = useTransition();
73
227
  const [version, setVersion] = useState(0);
74
-
75
- // 🔌 EFFECT 1: Expose Global Prefetch
76
- useEffect(() => {
77
- window.__DINOU_PREFETCH__ = (url) => {
78
- // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
79
- if (isHashChangeOnly(url)) return;
80
- getRSCPayload(url);
81
- };
82
-
83
- // Hydration
84
- document.body.setAttribute("data-hydrated", "true");
85
- }, []); // Only on mount
228
+ const [navError, setNavError] = useState(null);
86
229
 
87
230
  // 🧭 NAVIGATE FUNCTION (Core Logic)
88
231
  const navigate = (href, options = {}) => {
@@ -126,9 +269,30 @@ function Router() {
126
269
  startTransition(() => {
127
270
  setIsPopState(false);
128
271
  setRoute(finalPath);
272
+ setNavError(null);
129
273
  });
130
274
  };
131
275
 
276
+ // 🔌 EFFECT 1: Expose Global Prefetch & Navigation
277
+ useEffect(() => {
278
+ window.__DINOU_PREFETCH__ = (url) => {
279
+ // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
280
+ if (isHashChangeOnly(url)) return;
281
+ getRSCPayload(url, true);
282
+ };
283
+
284
+ window.__DINOU_ROUTER_NAVIGATE__ = navigate;
285
+
286
+ // Hydration
287
+ document.body.setAttribute("data-hydrated", "true");
288
+
289
+ return () => {
290
+ if (window.__DINOU_ROUTER_NAVIGATE__ === navigate) {
291
+ window.__DINOU_ROUTER_NAVIGATE__ = undefined;
292
+ }
293
+ };
294
+ }, [navigate]);
295
+
132
296
  const back = () => window.history.back();
133
297
  const forward = () => window.history.forward();
134
298
  const refresh = () => {
@@ -142,6 +306,7 @@ function Router() {
142
306
  startTransition(() => {
143
307
  // 3. Increment version to force re-execution of useMemo
144
308
  setVersion((v) => v + 1);
309
+ setNavError(null);
145
310
  });
146
311
  };
147
312
 
@@ -188,6 +353,7 @@ function Router() {
188
353
  startTransition(() => {
189
354
  setIsPopState(true);
190
355
  setRoute(target);
356
+ setNavError(null);
191
357
  });
192
358
  };
193
359
 
@@ -233,7 +399,7 @@ function Router() {
233
399
 
234
400
  // RSC Logic
235
401
  const rscKey = route + "::" + version;
236
- const content = getRSCPayload(rscKey);
402
+ const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
237
403
 
238
404
  const contextValue = useMemo(
239
405
  () => ({
@@ -249,7 +415,12 @@ function Router() {
249
415
 
250
416
  return (
251
417
  <RouterContext.Provider value={contextValue}>
252
- {use(content)}
418
+ <ErrorBoundary
419
+ key={navError ? "error" : "normal"}
420
+ onError={setNavError}
421
+ >
422
+ {use(content)}
423
+ </ErrorBoundary>
253
424
  </RouterContext.Provider>
254
425
  );
255
426
  }
@@ -5,11 +5,13 @@ import {
5
5
  useTransition,
6
6
  useLayoutEffect,
7
7
  useMemo,
8
+ Component,
8
9
  } from "react";
9
10
  import { createFromFetch } from "@roggc/react-server-dom-esm/client";
10
11
  import { hydrateRoot } from "react-dom/client";
11
12
  import { RouterContext } from "./navigation.js";
12
13
  import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
14
+ import { createServerFunctionProxy } from "./server-function-proxy.js";
13
15
 
14
16
  // ====================================================================
15
17
  // 1. GLOBAL STATE (Outside the component)
@@ -38,30 +40,182 @@ const isHashChangeOnly = (finalPath) => {
38
40
  );
39
41
  };
40
42
 
41
- const getRSCPayload = (rscKey) => {
43
+ let isInitialErrorLoad = true;
44
+
45
+ const getRSCPayload = (rscKey, isPrefetch = false) => {
42
46
  const url = rscKey.split("::")[0];
43
- // Important: url must already be normalized here
47
+ // 1. Check Idempotence (Avoids the infinite loop of React)
44
48
  if (cache.has(url)) return cache.get(url);
45
49
 
46
- const content = createFromFetch(
47
- fetch("/____rsc_payload_error____" + url, {
50
+ let promise;
51
+ if (isInitialErrorLoad && url === getCurrentRoute()) {
52
+ isInitialErrorLoad = false;
53
+ const payloadUrl = "/____rsc_payload_error____" + url;
54
+ promise = createFromFetch(
55
+ fetch(payloadUrl, {
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ },
60
+ body: JSON.stringify({
61
+ error: {
62
+ message: window.__DINOU_ERROR_MESSAGE__ || "Unknown Error",
63
+ stack: window.__DINOU_ERROR_STACK__,
64
+ name: window.__DINOU_ERROR_NAME__,
65
+ },
66
+ }),
67
+ }).then((res) => {
68
+ if (res.headers.has("x-rsc-redirect")) {
69
+ const redirectUrl = res.headers.get("x-rsc-redirect");
70
+ cache.delete(url);
71
+ if (!isPrefetch) {
72
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
73
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
74
+ } else {
75
+ window.location.href = redirectUrl;
76
+ }
77
+ }
78
+ return new Promise(() => {});
79
+ }
80
+ return res;
81
+ }),
82
+ {
83
+ callServer: async (id, args) => {
84
+ const proxy = createServerFunctionProxy(id);
85
+ return proxy(...args);
86
+ }
87
+ }
88
+ );
89
+ } else {
90
+ // Normal client.jsx load logic!
91
+ let payloadUrl = "/____rsc_payload____" + url;
92
+ const buildId = window.__DINOU_BUILD_ID__;
93
+ if (buildId) {
94
+ payloadUrl += (payloadUrl.includes("?") ? "&" : "?") + "buildId=" + buildId;
95
+ window.__DINOU_BUILD_ID__ = undefined;
96
+ }
97
+
98
+ promise = createFromFetch(
99
+ fetch(payloadUrl).then((res) => {
100
+ if (res.headers.has("x-rsc-redirect")) {
101
+ const redirectUrl = res.headers.get("x-rsc-redirect");
102
+ cache.delete(url);
103
+ if (!isPrefetch) {
104
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
105
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
106
+ } else {
107
+ window.location.href = redirectUrl;
108
+ }
109
+ }
110
+ return new Promise(() => {});
111
+ }
112
+ return res;
113
+ }),
114
+ {
115
+ callServer: async (id, args) => {
116
+ const proxy = createServerFunctionProxy(id);
117
+ return proxy(...args);
118
+ }
119
+ }
120
+ );
121
+ }
122
+
123
+ cache.set(url, promise);
124
+ return promise;
125
+ };
126
+
127
+ const getErrorRSCPayload = (route, error) => {
128
+ const url = route.split("::")[0];
129
+ const cacheKey = `error::${url}::${error.message || String(error)}`;
130
+ if (cache.has(cacheKey)) {
131
+ return cache.get(cacheKey);
132
+ }
133
+
134
+ const payloadUrl = "/____rsc_payload_error____" + url;
135
+ const promise = createFromFetch(
136
+ fetch(payloadUrl, {
48
137
  method: "POST",
49
138
  headers: {
50
139
  "Content-Type": "application/json",
51
140
  },
52
141
  body: JSON.stringify({
53
142
  error: {
54
- message: window.__DINOU_ERROR_MESSAGE__ || "Unknown Error",
55
- stack: window.__DINOU_ERROR_STACK__,
56
- name: window.__DINOU_ERROR_NAME__,
143
+ message: error.message || "Unknown Error",
144
+ name: error.name,
145
+ stack: error.stack,
57
146
  },
58
147
  }),
148
+ }).then((res) => {
149
+ if (res.headers.has("x-rsc-redirect")) {
150
+ const redirectUrl = res.headers.get("x-rsc-redirect");
151
+ cache.delete(cacheKey);
152
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
153
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
154
+ } else {
155
+ window.location.href = redirectUrl;
156
+ }
157
+ return new Promise(() => {});
158
+ }
159
+ return res;
59
160
  }),
161
+ {
162
+ callServer: async (id, args) => {
163
+ const proxy = createServerFunctionProxy(id);
164
+ return proxy(...args);
165
+ }
166
+ }
60
167
  );
61
- cache.set(url, content);
62
- return content;
168
+ cache.set(cacheKey, promise);
169
+ return promise;
63
170
  };
64
171
 
172
+ class ErrorBoundary extends Component {
173
+ constructor(props) {
174
+ super(props);
175
+ this.state = { hasError: false, error: null };
176
+ }
177
+
178
+ static getDerivedStateFromError(error) {
179
+ return { hasError: true, error };
180
+ }
181
+
182
+ componentDidCatch(error, errorInfo) {
183
+ console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
184
+ if (this.props.onError) {
185
+ this.props.onError(error);
186
+ }
187
+ }
188
+
189
+ render() {
190
+ if (this.state.hasError) {
191
+ if (this.props.fallback) {
192
+ return this.props.fallback(this.state.error);
193
+ }
194
+ const isDev = process.env.NODE_ENV !== "production";
195
+ return (
196
+ <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
197
+ <h2>Application Error</h2>
198
+ <p>An unexpected error occurred on the client.</p>
199
+ <pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
200
+ {isDev ? (
201
+ <>
202
+ <div style={{ fontWeight: "bold", marginBottom: "10px" }}>
203
+ {this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
204
+ </div>
205
+ <div>{this.state.error?.stack}</div>
206
+ </>
207
+ ) : (
208
+ this.state.error?.message || String(this.state.error)
209
+ )}
210
+ </pre>
211
+ </div>
212
+ );
213
+ }
214
+ return this.props.children;
215
+ }
216
+ }
217
+
218
+
65
219
  // ====================================================================
66
220
  // 3. ROUTER COMPONENT
67
221
  // ====================================================================
@@ -71,18 +225,7 @@ function Router() {
71
225
  const [isPopState, setIsPopState] = useState(false);
72
226
  const [isPending, startTransition] = useTransition();
73
227
  const [version, setVersion] = useState(0);
74
-
75
- // 🔌 EFFECT 1: Expose Global Prefetch
76
- useEffect(() => {
77
- window.__DINOU_PREFETCH__ = (url) => {
78
- // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
79
- if (isHashChangeOnly(url)) return;
80
- getRSCPayload(url);
81
- };
82
-
83
- // Hydration
84
- document.body.setAttribute("data-hydrated", "true");
85
- }, []); // Only on mount
228
+ const [navError, setNavError] = useState(null);
86
229
 
87
230
  // 🧭 NAVIGATE FUNCTION (Core Logic)
88
231
  const navigate = (href, options = {}) => {
@@ -126,9 +269,30 @@ function Router() {
126
269
  startTransition(() => {
127
270
  setIsPopState(false);
128
271
  setRoute(finalPath);
272
+ setNavError(null);
129
273
  });
130
274
  };
131
275
 
276
+ // 🔌 EFFECT 1: Expose Global Prefetch & Navigation
277
+ useEffect(() => {
278
+ window.__DINOU_PREFETCH__ = (url) => {
279
+ // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
280
+ if (isHashChangeOnly(url)) return;
281
+ getRSCPayload(url, true);
282
+ };
283
+
284
+ window.__DINOU_ROUTER_NAVIGATE__ = navigate;
285
+
286
+ // Hydration
287
+ document.body.setAttribute("data-hydrated", "true");
288
+
289
+ return () => {
290
+ if (window.__DINOU_ROUTER_NAVIGATE__ === navigate) {
291
+ window.__DINOU_ROUTER_NAVIGATE__ = undefined;
292
+ }
293
+ };
294
+ }, [navigate]);
295
+
132
296
  const back = () => window.history.back();
133
297
  const forward = () => window.history.forward();
134
298
  const refresh = () => {
@@ -142,6 +306,7 @@ function Router() {
142
306
  startTransition(() => {
143
307
  // 3. Increment version to force re-execution of useMemo
144
308
  setVersion((v) => v + 1);
309
+ setNavError(null);
145
310
  });
146
311
  };
147
312
 
@@ -188,6 +353,7 @@ function Router() {
188
353
  startTransition(() => {
189
354
  setIsPopState(true);
190
355
  setRoute(target);
356
+ setNavError(null);
191
357
  });
192
358
  };
193
359
 
@@ -233,7 +399,7 @@ function Router() {
233
399
 
234
400
  // RSC Logic
235
401
  const rscKey = route + "::" + version;
236
- const content = getRSCPayload(rscKey);
402
+ const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
237
403
 
238
404
  const contextValue = useMemo(
239
405
  () => ({
@@ -249,7 +415,12 @@ function Router() {
249
415
 
250
416
  return (
251
417
  <RouterContext.Provider value={contextValue}>
252
- {use(content)}
418
+ <ErrorBoundary
419
+ key={navError ? "error" : "normal"}
420
+ onError={setNavError}
421
+ >
422
+ {use(content)}
423
+ </ErrorBoundary>
253
424
  </RouterContext.Provider>
254
425
  );
255
426
  }
@@ -5,11 +5,13 @@ import {
5
5
  useTransition,
6
6
  useLayoutEffect,
7
7
  useMemo,
8
+ Component,
8
9
  } from "react";
9
10
  import { createFromFetch } from "react-server-dom-webpack/client";
10
11
  import { hydrateRoot } from "react-dom/client";
11
12
  import { RouterContext } from "./navigation.js";
12
13
  import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
14
+ import { createServerFunctionProxy } from "./server-function-proxy-webpack.js";
13
15
 
14
16
  // ====================================================================
15
17
  // 1. GLOBAL STATE (Outside the component)
@@ -81,11 +83,109 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
81
83
  }
82
84
  return res;
83
85
  }),
86
+ {
87
+ callServer: async (id, args) => {
88
+ const proxy = createServerFunctionProxy(id);
89
+ return proxy(...args);
90
+ }
91
+ }
84
92
  );
85
93
  cache.set(url, promise); // <--- KEY TO AVOID LOOP
86
94
  return promise;
87
95
  };
88
96
 
97
+ const getErrorRSCPayload = (route, error) => {
98
+ const url = route.split("::")[0];
99
+ const cacheKey = `error::${url}::${error.message || String(error)}`;
100
+ if (cache.has(cacheKey)) {
101
+ return cache.get(cacheKey);
102
+ }
103
+
104
+ const payloadUrl = "/____rsc_payload_error____" + url;
105
+ const promise = createFromFetch(
106
+ fetch(payloadUrl, {
107
+ method: "POST",
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ },
111
+ body: JSON.stringify({
112
+ error: {
113
+ message: error.message || "Unknown Error",
114
+ name: error.name,
115
+ stack: error.stack,
116
+ },
117
+ }),
118
+ }).then((res) => {
119
+ if (res.headers.has("x-rsc-redirect")) {
120
+ const redirectUrl = res.headers.get("x-rsc-redirect");
121
+ cache.delete(cacheKey);
122
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
123
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
124
+ } else {
125
+ window.location.href = redirectUrl;
126
+ }
127
+ return new Promise(() => {});
128
+ }
129
+ return res;
130
+ }),
131
+ {
132
+ callServer: async (id, args) => {
133
+ const proxy = createServerFunctionProxy(id);
134
+ return proxy(...args);
135
+ }
136
+ }
137
+ );
138
+ cache.set(cacheKey, promise);
139
+ return promise;
140
+ };
141
+
142
+ class ErrorBoundary extends Component {
143
+ constructor(props) {
144
+ super(props);
145
+ this.state = { hasError: false, error: null };
146
+ }
147
+
148
+ static getDerivedStateFromError(error) {
149
+ return { hasError: true, error };
150
+ }
151
+
152
+ componentDidCatch(error, errorInfo) {
153
+ console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
154
+ if (this.props.onError) {
155
+ this.props.onError(error);
156
+ }
157
+ }
158
+
159
+ render() {
160
+ if (this.state.hasError) {
161
+ if (this.props.fallback) {
162
+ return this.props.fallback(this.state.error);
163
+ }
164
+ const isDev = process.env.NODE_ENV !== "production";
165
+ return (
166
+ <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
167
+ <h2>Application Error</h2>
168
+ <p>An unexpected error occurred on the client.</p>
169
+ <pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
170
+ {isDev ? (
171
+ <>
172
+ <div style={{ fontWeight: "bold", marginBottom: "10px" }}>
173
+ {this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
174
+ </div>
175
+ <div>{this.state.error?.stack}</div>
176
+ </>
177
+ ) : (
178
+ this.state.error?.message || String(this.state.error)
179
+ )}
180
+ </pre>
181
+ </div>
182
+ );
183
+ }
184
+ return this.props.children;
185
+ }
186
+ }
187
+
188
+
89
189
  // ====================================================================
90
190
  // 3. ROUTER COMPONENT
91
191
  // ====================================================================
@@ -95,6 +195,7 @@ function Router() {
95
195
  const [isPopState, setIsPopState] = useState(false);
96
196
  const [isPending, startTransition] = useTransition();
97
197
  const [version, setVersion] = useState(0);
198
+ const [navError, setNavError] = useState(null);
98
199
 
99
200
  // 🧭 NAVIGATE FUNCTION (Core Logic)
100
201
  const navigate = (href, options = {}) => {
@@ -138,6 +239,7 @@ function Router() {
138
239
  startTransition(() => {
139
240
  setIsPopState(false);
140
241
  setRoute(finalPath);
242
+ setNavError(null);
141
243
  });
142
244
  };
143
245
 
@@ -174,6 +276,7 @@ function Router() {
174
276
  startTransition(() => {
175
277
  // 3. Increment version to force re-execution of useMemo
176
278
  setVersion((v) => v + 1);
279
+ setNavError(null);
177
280
  });
178
281
  };
179
282
 
@@ -220,6 +323,7 @@ function Router() {
220
323
  startTransition(() => {
221
324
  setIsPopState(true);
222
325
  setRoute(target);
326
+ setNavError(null);
223
327
  });
224
328
  };
225
329
 
@@ -265,7 +369,7 @@ function Router() {
265
369
 
266
370
  // RSC Logic
267
371
  const rscKey = route + "::" + version;
268
- const content = getRSCPayload(rscKey);
372
+ const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
269
373
 
270
374
  const contextValue = useMemo(
271
375
  () => ({
@@ -281,7 +385,12 @@ function Router() {
281
385
 
282
386
  return (
283
387
  <RouterContext.Provider value={contextValue}>
284
- {use(content)}
388
+ <ErrorBoundary
389
+ key={navError ? "error" : "normal"}
390
+ onError={setNavError}
391
+ >
392
+ {use(content)}
393
+ </ErrorBoundary>
285
394
  </RouterContext.Provider>
286
395
  );
287
396
  }
@@ -6,11 +6,13 @@ import {
6
6
  useTransition,
7
7
  useLayoutEffect,
8
8
  useMemo,
9
+ Component,
9
10
  } from "react";
10
11
  import { createFromFetch } from "@roggc/react-server-dom-esm/client";
11
12
  import { hydrateRoot } from "react-dom/client";
12
13
  import { RouterContext } from "./navigation.js";
13
14
  import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
15
+ import { createServerFunctionProxy } from "./server-function-proxy.js";
14
16
 
15
17
  // ====================================================================
16
18
  // 1. GLOBAL STATE (Outside the component)
@@ -88,11 +90,109 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
88
90
  }
89
91
  return res;
90
92
  }),
93
+ {
94
+ callServer: async (id, args) => {
95
+ const proxy = createServerFunctionProxy(id);
96
+ return proxy(...args);
97
+ }
98
+ }
91
99
  );
92
100
  cache.set(url, promise); // <--- KEY TO AVOID LOOP
93
101
  return promise;
94
102
  };
95
103
 
104
+ const getErrorRSCPayload = (route, error) => {
105
+ const url = route.split("::")[0];
106
+ const cacheKey = `error::${url}::${error.message || String(error)}`;
107
+ if (cache.has(cacheKey)) {
108
+ return cache.get(cacheKey);
109
+ }
110
+
111
+ const payloadUrl = "/____rsc_payload_error____" + url;
112
+ const promise = createFromFetch(
113
+ fetch(payloadUrl, {
114
+ method: "POST",
115
+ headers: {
116
+ "Content-Type": "application/json",
117
+ },
118
+ body: JSON.stringify({
119
+ error: {
120
+ message: error.message || "Unknown Error",
121
+ name: error.name,
122
+ stack: error.stack,
123
+ },
124
+ }),
125
+ }).then((res) => {
126
+ if (res.headers.has("x-rsc-redirect")) {
127
+ const redirectUrl = res.headers.get("x-rsc-redirect");
128
+ cache.delete(cacheKey);
129
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
130
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
131
+ } else {
132
+ window.location.href = redirectUrl;
133
+ }
134
+ return new Promise(() => {});
135
+ }
136
+ return res;
137
+ }),
138
+ {
139
+ callServer: async (id, args) => {
140
+ const proxy = createServerFunctionProxy(id);
141
+ return proxy(...args);
142
+ }
143
+ }
144
+ );
145
+ cache.set(cacheKey, promise);
146
+ return promise;
147
+ };
148
+
149
+ class ErrorBoundary extends Component {
150
+ constructor(props) {
151
+ super(props);
152
+ this.state = { hasError: false, error: null };
153
+ }
154
+
155
+ static getDerivedStateFromError(error) {
156
+ return { hasError: true, error };
157
+ }
158
+
159
+ componentDidCatch(error, errorInfo) {
160
+ console.error("[Dinou] ErrorBoundary caught error:", error, errorInfo);
161
+ if (this.props.onError) {
162
+ this.props.onError(error);
163
+ }
164
+ }
165
+
166
+ render() {
167
+ if (this.state.hasError) {
168
+ if (this.props.fallback) {
169
+ return this.props.fallback(this.state.error);
170
+ }
171
+ const isDev = process.env.NODE_ENV !== "production";
172
+ return (
173
+ <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
174
+ <h2>Application Error</h2>
175
+ <p>An unexpected error occurred on the client.</p>
176
+ <pre style={{ backgroundColor: "#f5f5f5", padding: "15px", borderRadius: "5px", overflowX: "auto" }}>
177
+ {isDev ? (
178
+ <>
179
+ <div style={{ fontWeight: "bold", marginBottom: "10px" }}>
180
+ {this.state.error?.name || "Error"}: {this.state.error?.message || String(this.state.error)}
181
+ </div>
182
+ <div>{this.state.error?.stack}</div>
183
+ </>
184
+ ) : (
185
+ this.state.error?.message || String(this.state.error)
186
+ )}
187
+ </pre>
188
+ </div>
189
+ );
190
+ }
191
+ return this.props.children;
192
+ }
193
+ }
194
+
195
+
96
196
  // ====================================================================
97
197
  // 3. ROUTER COMPONENT
98
198
  // ====================================================================
@@ -102,6 +202,7 @@ function Router() {
102
202
  const [isPopState, setIsPopState] = useState(false);
103
203
  const [isPending, startTransition] = useTransition();
104
204
  const [version, setVersion] = useState(0);
205
+ const [navError, setNavError] = useState(null);
105
206
 
106
207
  // 🧭 NAVIGATE FUNCTION (Core Logic)
107
208
  const navigate = (href, options = {}) => {
@@ -145,6 +246,7 @@ function Router() {
145
246
  startTransition(() => {
146
247
  setIsPopState(false);
147
248
  setRoute(finalPath);
249
+ setNavError(null);
148
250
  });
149
251
  };
150
252
 
@@ -181,6 +283,7 @@ function Router() {
181
283
  startTransition(() => {
182
284
  // 3. Increment version to force re-execution of useMemo
183
285
  setVersion((v) => v + 1);
286
+ setNavError(null);
184
287
  });
185
288
  };
186
289
 
@@ -227,6 +330,7 @@ function Router() {
227
330
  startTransition(() => {
228
331
  setIsPopState(true);
229
332
  setRoute(target);
333
+ setNavError(null);
230
334
  });
231
335
  };
232
336
 
@@ -272,7 +376,7 @@ function Router() {
272
376
 
273
377
  // RSC Logic
274
378
  const rscKey = route + "::" + version;
275
- const content = getRSCPayload(rscKey);
379
+ const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
276
380
 
277
381
  const contextValue = useMemo(
278
382
  () => ({
@@ -288,7 +392,12 @@ function Router() {
288
392
 
289
393
  return (
290
394
  <RouterContext.Provider value={contextValue}>
291
- {use(content)}
395
+ <ErrorBoundary
396
+ key={navError ? "error" : "normal"}
397
+ onError={setNavError}
398
+ >
399
+ {use(content)}
400
+ </ErrorBoundary>
292
401
  </RouterContext.Provider>
293
402
  );
294
403
  }
@@ -0,0 +1,10 @@
1
+ function normalizePathCase(p) {
2
+ if (process.platform === "win32" && typeof p === "string" && p[1] === ":") {
3
+ return p.charAt(0).toLowerCase() + p.slice(1);
4
+ }
5
+ return p;
6
+ }
7
+
8
+ module.exports = {
9
+ normalizePathCase,
10
+ };
@@ -5,6 +5,8 @@ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
5
5
  globalThis.__dinou_require__ = require;
6
6
  const path = require("path");
7
7
 
8
+ const { normalizePathCase } = require("./path-utils.js");
9
+
8
10
  let reactServerPath, reactDomServerPath, reactJsxRuntimePath, reactJsxDevRuntimePath;
9
11
 
10
12
  if (!isWebpack) {
@@ -124,11 +126,10 @@ if (isDevelopment) {
124
126
  while (attempts < maxRetries) {
125
127
  try {
126
128
  // console.log(`Attempting to load manifest (try ${attempts + 1})...`);
127
- return JSON.parse(readFileSync(manifestPath, "utf8"));
129
+ const text = readFileSync(manifestPath, "utf8");
130
+ if (!text.trim()) throw new Error("Empty JSON");
131
+ return JSON.parse(text);
128
132
  } catch (err) {
129
- if (err.code !== "ENOENT") {
130
- throw err; // Rethrow if it's not a file not found error
131
- }
132
133
  attempts++;
133
134
  if (attempts >= maxRetries) {
134
135
  throw err; // Rethrow after max retries
@@ -628,7 +629,7 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
628
629
  const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
629
630
  currentGeneratedAt = metaObj.generatedAt || null;
630
631
  }
631
- } catch (e) {}
632
+ } catch (e) { }
632
633
 
633
634
  const useOld =
634
635
  isOld ||
@@ -797,7 +798,43 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
797
798
  });
798
799
  } catch (error) {
799
800
  console.error("Error rendering RSC:", error);
800
- res.status(500).send("Internal Server Error");
801
+ try {
802
+ const serializedError = {
803
+ message: error.message || "Unknown Error",
804
+ name: error.name,
805
+ stack: isDevelopment ? error.stack : undefined,
806
+ };
807
+ const context = getContext(req, res);
808
+ await requestStorage.run(context, async () => {
809
+ const jsx = await getErrorJSX(
810
+ reqPath,
811
+ { ...req.query },
812
+ serializedError,
813
+ isDevelopment,
814
+ );
815
+ const manifest = isDevelopment
816
+ ? JSON.parse(
817
+ readFileSync(
818
+ path.resolve(
819
+ process.cwd(),
820
+ isWebpack
821
+ ? `${outputFolder}/react-client-manifest.json`
822
+ : `react_client_manifest/react-client-manifest.json`,
823
+ ),
824
+ "utf8",
825
+ ),
826
+ )
827
+ : cachedClientManifest;
828
+ const { pipe } = isWebpack
829
+ ? renderToPipeableStream(jsx, manifest)
830
+ : renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
831
+ res.status(500);
832
+ pipe(res);
833
+ });
834
+ } catch (innerError) {
835
+ console.error("Fatal error rendering fallback error RSC:", innerError);
836
+ res.status(500).send("Internal Server Error");
837
+ }
801
838
  }
802
839
  }
803
840
 
@@ -822,29 +859,33 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
822
859
  const reqPath = (
823
860
  req.path.endsWith("/") ? req.path : req.path + "/"
824
861
  ).replace("/____rsc_payload_error____", "");
825
- const jsx = await getErrorJSX(
826
- reqPath,
827
- { ...req.query },
828
- req.body.error,
829
- isDevelopment,
830
- );
831
- const manifest = isDevelopment
832
- ? JSON.parse(
833
- readFileSync(
834
- path.resolve(
835
- process.cwd(),
836
- isWebpack
837
- ? `${outputFolder}/react-client-manifest.json`
838
- : `react_client_manifest/react-client-manifest.json`,
862
+
863
+ const context = getContext(req, res);
864
+ await requestStorage.run(context, async () => {
865
+ const jsx = await getErrorJSX(
866
+ reqPath,
867
+ { ...req.query },
868
+ req.body.error,
869
+ isDevelopment,
870
+ );
871
+ const manifest = isDevelopment
872
+ ? JSON.parse(
873
+ readFileSync(
874
+ path.resolve(
875
+ process.cwd(),
876
+ isWebpack
877
+ ? `${outputFolder}/react-client-manifest.json`
878
+ : `react_client_manifest/react-client-manifest.json`,
879
+ ),
880
+ "utf8",
839
881
  ),
840
- "utf8",
841
- ),
842
- )
843
- : cachedClientManifest;
844
- const { pipe } = isWebpack
845
- ? renderToPipeableStream(jsx, manifest)
846
- : renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
847
- pipe(res);
882
+ )
883
+ : cachedClientManifest;
884
+ const { pipe } = isWebpack
885
+ ? renderToPipeableStream(jsx, manifest)
886
+ : renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
887
+ pipe(res);
888
+ });
848
889
  } catch (error) {
849
890
  console.error("Error rendering RSC:", error);
850
891
  res.status(500).send("Internal Server Error");
@@ -1001,7 +1042,7 @@ app.get(/^\/.*\/?$/, async (req, res) => {
1001
1042
  const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
1002
1043
  buildId = metaObj.generatedAt || "";
1003
1044
  }
1004
- } catch (e) {}
1045
+ } catch (e) { }
1005
1046
 
1006
1047
  let scripts = `<script>window.__DINOU_USE_STATIC__=true;</script>`;
1007
1048
  if (htmlPathOld) {
@@ -1208,8 +1249,27 @@ app.post("/____server_function____", async (req, res) => {
1208
1249
  return res.status(400).json({ error: "Invalid file URL format" });
1209
1250
  }
1210
1251
 
1211
- // Extract relativePath and normalize (remove 'file://' and potential '/')
1212
- let relativePath = fileUrl.replace(/^file:\/\/\/?/, "").trim();
1252
+ let relativePath;
1253
+
1254
+ // Check if the URL is a relative reference (e.g. file:///src/...)
1255
+ // If so, extract it directly without using fileURLToPath (which throws on Windows without a drive letter)
1256
+ const isRelativeSrc = fileUrl.startsWith("file:///src/") || fileUrl.startsWith("file:///src\\");
1257
+
1258
+ if (isRelativeSrc) {
1259
+ relativePath = fileUrl.replace(/^file:\/\/\/?/, "").trim();
1260
+ } else {
1261
+ const resolvedPath = fileURLToPath(fileUrl);
1262
+ relativePath = resolvedPath;
1263
+
1264
+ const normalizedCwd = normalizePathCase(process.cwd());
1265
+ const normalizedResolved = normalizePathCase(resolvedPath);
1266
+
1267
+ if (normalizedResolved.startsWith(normalizedCwd)) {
1268
+ relativePath = path.relative(normalizedCwd, normalizedResolved);
1269
+ } else {
1270
+ relativePath = relativePath.replace(/^[\\/]+/, "");
1271
+ }
1272
+ }
1213
1273
  const normalizedRelative = relativePath.replace(/\\/g, "/");
1214
1274
  if (
1215
1275
  normalizedRelative.startsWith("/") ||
@@ -1220,7 +1280,7 @@ app.post("/____server_function____", async (req, res) => {
1220
1280
  .status(400)
1221
1281
  .json({ error: "Invalid path: no absolute, traversal, or drive letter allowed" });
1222
1282
  }
1223
- // console.log("relPath", relativePath);
1283
+
1224
1284
  // Restrict to 'src/' folder: prepend 'src/' if missing, and resolve absolutePath
1225
1285
  if (!relativePath.startsWith("src/") && !relativePath.startsWith("src\\")) {
1226
1286
  relativePath = path.join("src", relativePath);
@@ -1234,9 +1294,10 @@ app.post("/____server_function____", async (req, res) => {
1234
1294
  .status(403)
1235
1295
  .json({ error: "Access denied: file outside src directory" });
1236
1296
  }
1237
- // console.log("absPath", absolutePath);
1297
+
1238
1298
  // Verify that the file exists
1239
1299
  if (!existsSync(absolutePath)) {
1300
+ // console.error("❌ [Dinou Server Function 404] File not found! id:", id, "relativePath:", relativePath, "absolutePath:", absolutePath);
1240
1301
  return res.status(404).json({ error: "File not found" });
1241
1302
  }
1242
1303
 
@@ -1257,8 +1318,8 @@ app.post("/____server_function____", async (req, res) => {
1257
1318
  .status(403)
1258
1319
  .json({ error: "Not a valid server function file" });
1259
1320
  }
1260
- // Parse exports (you need to implement parseExports on server if not present)
1261
- const exports = parseExports(fileContent); // Assume you move parseExports to a shared util
1321
+ // Parse exports
1322
+ const exports = parseExports(fileContent);
1262
1323
  allowedExports = new Set(exports);
1263
1324
  }
1264
1325
 
@@ -1271,10 +1332,10 @@ app.post("/____server_function____", async (req, res) => {
1271
1332
  return res.status(400).json({ error: "Invalid export name" });
1272
1333
  }
1273
1334
 
1274
- // Proceed with import (using your importModule)
1335
+ // Proceed with import
1275
1336
  const mod = await importModule(absolutePath);
1276
1337
 
1277
- // Validate exportName: only allow 'default' or others if you define a whitelist
1338
+ // Validate exportName
1278
1339
  if (!exportName || (exportName !== "default" && !mod[exportName])) {
1279
1340
  return res.status(400).json({ error: "Invalid export name" });
1280
1341
  }
@@ -56,6 +56,9 @@ export default function getConfigEsbuildProd({
56
56
  metafile: true,
57
57
  logLevel: "warning",
58
58
  minify: true,
59
+ define: {
60
+ "process.env.NODE_ENV": JSON.stringify("production"),
61
+ },
59
62
  external: [
60
63
  "/__SERVER_FUNCTION_PROXY__",
61
64
  "/serverFunctionProxy.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou-ejected",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "main": "index.js",
5
5
  "private": true,
6
6
  "exports": {
@@ -1,4 +1,6 @@
1
1
  const path = require("path");
2
+
3
+ const { normalizePathCase } = require("../../core/path-utils.js");
2
4
  const parseExports = require("../../core/parse-exports.js");
3
5
  const { useServerRegex } = require("../../constants.js");
4
6
 
@@ -16,7 +18,10 @@ module.exports = function (source) {
16
18
 
17
19
  // Build IDs
18
20
  const moduleId = this.resourcePath;
19
- const relativePath = path.relative(process.cwd(), moduleId);
21
+ const relativePath = path.relative(
22
+ normalizePathCase(process.cwd()),
23
+ normalizePathCase(moduleId)
24
+ );
20
25
  const normalizedPath = relativePath.replace(/\\/g, "/");
21
26
 
22
27
  const fileUrl = `file:///${normalizedPath}`;
@@ -14,7 +14,7 @@ class ManifestGeneratorPlugin {
14
14
  compilation.hooks.processAssets.tap(
15
15
  {
16
16
  name: pluginName,
17
- stage: Compilation.PROCESS_ASSETS_STAGE_REPORT,
17
+ stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
18
18
  },
19
19
  (assets) => {
20
20
  // Traverse chunks to generate manifest
@@ -45,7 +45,17 @@ const outputDirs = [
45
45
 
46
46
  function cleanDir(dir) {
47
47
  if (fs.existsSync(dir)) {
48
- fs.rmSync(dir, { recursive: true, force: true });
48
+ try {
49
+ const files = fs.readdirSync(dir);
50
+ for (const file of files) {
51
+ const fullPath = path.join(dir, file);
52
+ fs.rmSync(fullPath, { recursive: true, force: true });
53
+ }
54
+ } catch (e) {
55
+ try {
56
+ fs.rmSync(dir, { recursive: true, force: true });
57
+ } catch (err) {}
58
+ }
49
59
  }
50
60
  }
51
61
 
@@ -61,6 +71,7 @@ module.exports = async () => {
61
71
  maxEntrypointSize: 512000,
62
72
  maxAssetSize: 512000,
63
73
  },
74
+ cache: false,
64
75
  mode: isDevelopment ? "development" : "production",
65
76
  entry: {
66
77
  main: [path.resolve(__dirname, "../core/client-webpack.jsx")].filter(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
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": {