dinou 4.0.13 → 4.0.15

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,20 @@ 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
+ ## [4.0.15] - 2026-06-29
9
+
10
+ ### Added
11
+ - **VFS Routing Cache**: Introduced an in-memory Virtual File System (VFS) to cache filesystem structures under `src/` at production startup, resolving dynamic routes entirely in-memory and eliminating synchronous event loop-blocking disk I/O.
12
+
13
+ ### Fixed
14
+ - **Cross-Platform Separators**: Normalized Server Functions manifest keys and runtime lookup checks to consistently use forward slashes (`/`), preventing OS path format mismatches (`\` vs `/`) and subsequent `400 Bad Request` execution errors.
15
+
16
+ ## [4.0.14] - 2026-06-29
17
+ ### Added
18
+ - **Soft Redirects**: Exposes the SPA transition navigation internally via `window.__DINOU_ROUTER_NAVIGATE__` to enable smooth client-side redirects from both Server Functions and RSC payloads, keeping the React client state intact and preventing full browser page reloads.
19
+ ### Fixed
20
+ - **External Links Interception**: Restored native browser navigation for external domains, protocol-relative links (`//...`), and special protocols (`mailto:`, `tel:`, etc.) by preventing the SPA router from capturing or prefetching non-internal routes.
21
+
8
22
  ## [4.0.13] - 2026-06-29
9
23
 
10
24
  ### Added
@@ -9,7 +9,7 @@ import {
9
9
  import { createFromFetch } from "react-server-dom-webpack/client";
10
10
  import { hydrateRoot } from "react-dom/client";
11
11
  import { RouterContext } from "./navigation.js";
12
- import { resolveUrl } from "./navigation-utils.js";
12
+ import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
13
13
 
14
14
  // ====================================================================
15
15
  // 1. GLOBAL STATE (Outside the component)
@@ -167,7 +167,7 @@ function Router() {
167
167
  }
168
168
 
169
169
  const href = anchor.getAttribute("href");
170
- if (!href || href.startsWith("mailto:") || href.startsWith("tel:"))
170
+ if (!href || href.startsWith("mailto:") || href.startsWith("tel:") || isExternalUrl(href))
171
171
  return;
172
172
 
173
173
  // We use the unified helper
@@ -9,7 +9,7 @@ import {
9
9
  import { createFromFetch } from "@roggc/react-server-dom-esm/client";
10
10
  import { hydrateRoot } from "react-dom/client";
11
11
  import { RouterContext } from "./navigation.js";
12
- import { resolveUrl } from "./navigation-utils.js";
12
+ import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
13
13
 
14
14
  // ====================================================================
15
15
  // 1. GLOBAL STATE (Outside the component)
@@ -167,7 +167,7 @@ function Router() {
167
167
  }
168
168
 
169
169
  const href = anchor.getAttribute("href");
170
- if (!href || href.startsWith("mailto:") || href.startsWith("tel:"))
170
+ if (!href || href.startsWith("mailto:") || href.startsWith("tel:") || isExternalUrl(href))
171
171
  return;
172
172
 
173
173
  // We use the unified helper
@@ -1,15 +1,22 @@
1
1
  // dinou/core/client-redirect.jsx
2
2
  "use client";
3
3
 
4
- import { useEffect } from "react";
5
4
  import { useRouter } from "./navigation.js";
6
5
 
7
6
  export function ClientRedirect({ to }) {
8
7
  const router = useRouter();
9
8
 
10
- useEffect(() => {
11
- router.replace(to);
12
- }, [to, router]);
9
+ if (typeof window !== "undefined") {
10
+ Promise.resolve().then(() => {
11
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
12
+ window.__DINOU_ROUTER_NAVIGATE__(to, { replace: true });
13
+ } else {
14
+ router.replace(to);
15
+ }
16
+ });
17
+ // Suspend to prevent React from committing this intermediate page
18
+ throw new Promise(() => {});
19
+ }
13
20
 
14
21
  return null;
15
22
  }
@@ -9,7 +9,7 @@ import {
9
9
  import { createFromFetch } from "react-server-dom-webpack/client";
10
10
  import { hydrateRoot } from "react-dom/client";
11
11
  import { RouterContext } from "./navigation.js";
12
- import { resolveUrl } from "./navigation-utils.js";
12
+ import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
13
13
 
14
14
  // ====================================================================
15
15
  // 1. GLOBAL STATE (Outside the component)
@@ -68,10 +68,13 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
68
68
  fetch(payloadUrl).then((res) => {
69
69
  if (res.headers.has("x-rsc-redirect")) {
70
70
  const redirectUrl = res.headers.get("x-rsc-redirect");
71
+ cache.delete(url);
71
72
  if (!isPrefetch) {
72
- window.location.href = redirectUrl;
73
- } else {
74
- cache.delete(url);
73
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
74
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
75
+ } else {
76
+ window.location.href = redirectUrl;
77
+ }
75
78
  }
76
79
  // Return a promise that never resolves to avoid React Server DOM throwing "Connection closed"
77
80
  return new Promise(() => {});
@@ -93,18 +96,6 @@ function Router() {
93
96
  const [isPending, startTransition] = useTransition();
94
97
  const [version, setVersion] = useState(0);
95
98
 
96
- // 🔌 EFFECT 1: Expose Global Prefetch
97
- useEffect(() => {
98
- window.__DINOU_PREFETCH__ = (url) => {
99
- // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
100
- if (isHashChangeOnly(url)) return;
101
- getRSCPayload(url, true);
102
- };
103
-
104
- // Hydration
105
- document.body.setAttribute("data-hydrated", "true");
106
- }, []); // Only on mount
107
-
108
99
  // 🧭 NAVIGATE FUNCTION (Core Logic)
109
100
  const navigate = (href, options = {}) => {
110
101
  const finalPath = resolveUrl(href, window.location.pathname);
@@ -150,6 +141,26 @@ function Router() {
150
141
  });
151
142
  };
152
143
 
144
+ // 🔌 EFFECT 1: Expose Global Prefetch & Navigation
145
+ useEffect(() => {
146
+ window.__DINOU_PREFETCH__ = (url) => {
147
+ // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
148
+ if (isHashChangeOnly(url)) return;
149
+ getRSCPayload(url, true);
150
+ };
151
+
152
+ window.__DINOU_ROUTER_NAVIGATE__ = navigate;
153
+
154
+ // Hydration
155
+ document.body.setAttribute("data-hydrated", "true");
156
+
157
+ return () => {
158
+ if (window.__DINOU_ROUTER_NAVIGATE__ === navigate) {
159
+ window.__DINOU_ROUTER_NAVIGATE__ = undefined;
160
+ }
161
+ };
162
+ }, [navigate]);
163
+
153
164
  const back = () => window.history.back();
154
165
  const forward = () => window.history.forward();
155
166
  const refresh = () => {
@@ -188,7 +199,7 @@ function Router() {
188
199
  }
189
200
 
190
201
  const href = anchor.getAttribute("href");
191
- if (!href || href.startsWith("mailto:") || href.startsWith("tel:"))
202
+ if (!href || href.startsWith("mailto:") || href.startsWith("tel:") || isExternalUrl(href))
192
203
  return;
193
204
 
194
205
  // We use the unified helper
@@ -10,7 +10,7 @@ import {
10
10
  import { createFromFetch } from "@roggc/react-server-dom-esm/client";
11
11
  import { hydrateRoot } from "react-dom/client";
12
12
  import { RouterContext } from "./navigation.js";
13
- import { resolveUrl } from "./navigation-utils.js";
13
+ import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
14
14
 
15
15
  // ====================================================================
16
16
  // 1. GLOBAL STATE (Outside the component)
@@ -69,10 +69,13 @@ const getRSCPayload = (rscKey, isPrefetch = false) => {
69
69
  fetch(payloadUrl).then((res) => {
70
70
  if (res.headers.has("x-rsc-redirect")) {
71
71
  const redirectUrl = res.headers.get("x-rsc-redirect");
72
+ cache.delete(url);
72
73
  if (!isPrefetch) {
73
- window.location.href = redirectUrl;
74
- } else {
75
- cache.delete(url);
74
+ if (window.__DINOU_ROUTER_NAVIGATE__) {
75
+ window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
76
+ } else {
77
+ window.location.href = redirectUrl;
78
+ }
76
79
  }
77
80
  // Return a promise that never resolves to avoid React Server DOM throwing "Connection closed"
78
81
  return new Promise(() => {});
@@ -94,18 +97,6 @@ function Router() {
94
97
  const [isPending, startTransition] = useTransition();
95
98
  const [version, setVersion] = useState(0);
96
99
 
97
- // 🔌 EFFECT 1: Expose Global Prefetch
98
- useEffect(() => {
99
- window.__DINOU_PREFETCH__ = (url) => {
100
- // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
101
- if (isHashChangeOnly(url)) return;
102
- getRSCPayload(url, true);
103
- };
104
-
105
- // Hydration
106
- document.body.setAttribute("data-hydrated", "true");
107
- }, []); // Only on mount
108
-
109
100
  // 🧭 NAVIGATE FUNCTION (Core Logic)
110
101
  const navigate = (href, options = {}) => {
111
102
  const finalPath = resolveUrl(href, window.location.pathname);
@@ -151,6 +142,26 @@ function Router() {
151
142
  });
152
143
  };
153
144
 
145
+ // 🔌 EFFECT 1: Expose Global Prefetch & Navigation
146
+ useEffect(() => {
147
+ window.__DINOU_PREFETCH__ = (url) => {
148
+ // 🛡️ PREFETCH PROTECTION: If it's a local hash, do nothing
149
+ if (isHashChangeOnly(url)) return;
150
+ getRSCPayload(url, true);
151
+ };
152
+
153
+ window.__DINOU_ROUTER_NAVIGATE__ = navigate;
154
+
155
+ // Hydration
156
+ document.body.setAttribute("data-hydrated", "true");
157
+
158
+ return () => {
159
+ if (window.__DINOU_ROUTER_NAVIGATE__ === navigate) {
160
+ window.__DINOU_ROUTER_NAVIGATE__ = undefined;
161
+ }
162
+ };
163
+ }, [navigate]);
164
+
154
165
  const back = () => window.history.back();
155
166
  const forward = () => window.history.forward();
156
167
  const refresh = () => {
@@ -189,7 +200,7 @@ function Router() {
189
200
  }
190
201
 
191
202
  const href = anchor.getAttribute("href");
192
- if (!href || href.startsWith("mailto:") || href.startsWith("tel:"))
203
+ if (!href || href.startsWith("mailto:") || href.startsWith("tel:") || isExternalUrl(href))
193
204
  return;
194
205
 
195
206
  // We use the unified helper
@@ -1,5 +1,5 @@
1
1
  const path = require("path");
2
- const { existsSync } = require("fs");
2
+ const { existsSync } = require("./vfs");
3
3
  const React = require("react");
4
4
  const {
5
5
  getFilePathAndDynamicParams,
@@ -1,5 +1,5 @@
1
1
  const path = require("path");
2
- const { existsSync, readdirSync } = require("fs");
2
+ const { existsSync, readdirSync } = require("./vfs");
3
3
  const React = require("react");
4
4
 
5
5
  function safeDecode(val) {
@@ -1,5 +1,5 @@
1
1
  const path = require("path");
2
- const { existsSync } = require("fs");
2
+ const { existsSync } = require("./vfs");
3
3
  const React = require("react");
4
4
  const {
5
5
  getFilePathAndDynamicParams,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useRouter, usePathname } from "./navigation.js";
4
- import { resolveUrl } from "./navigation-utils.js";
4
+ import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
5
5
 
6
6
  export function Link({
7
7
  href,
@@ -15,14 +15,14 @@ export function Link({
15
15
  const resolvedHref = resolveUrl(href, pathname);
16
16
 
17
17
  const handlePrefetch = () => {
18
- if (!prefetch || !href || fresh) return;
18
+ if (!prefetch || !href || fresh || isExternalUrl(href)) return;
19
19
  if (window.__DINOU_PREFETCH__) {
20
20
  window.__DINOU_PREFETCH__(resolvedHref);
21
21
  }
22
22
  };
23
23
 
24
24
  const handleClick = (e) => {
25
- if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
25
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || isExternalUrl(href)) return;
26
26
 
27
27
  e.preventDefault();
28
28
  push(href, { fresh });
@@ -1,4 +1,35 @@
1
+ export function isExternalUrl(href) {
2
+ if (!href) return false;
3
+
4
+ // Protocol-relative (e.g. //google.com)
5
+ if (href.startsWith("//")) {
6
+ return true;
7
+ }
8
+
9
+ // Absolute with protocol (e.g. https://google.com)
10
+ if (href.includes("://")) {
11
+ try {
12
+ const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
13
+ const url = new URL(href, origin);
14
+ return url.origin !== origin;
15
+ } catch (e) {
16
+ return true;
17
+ }
18
+ }
19
+
20
+ // Other protocols (mailto:, tel:, javascript:, etc.)
21
+ if (/^[a-zA-Z0-9+-.]+:[^//]/.test(href) || href.startsWith("mailto:") || href.startsWith("tel:") || href.startsWith("javascript:")) {
22
+ return true;
23
+ }
24
+
25
+ return false;
26
+ }
27
+
1
28
  export function resolveUrl(href, currentPathname) {
29
+ if (isExternalUrl(href)) {
30
+ return href;
31
+ }
32
+
2
33
  const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
3
34
 
4
35
  if (href.startsWith("/") || href.includes("://")) {
@@ -4,6 +4,16 @@ function isSafeRedirect(url) {
4
4
  return typeof url === "string" && url.startsWith("/") && !url.startsWith("//");
5
5
  }
6
6
 
7
+ function executeRedirect(url) {
8
+ const safeUrl = isSafeRedirect(url) ? url : "/";
9
+ const isInternal = safeUrl.startsWith("/") && !safeUrl.startsWith("//");
10
+ if (isInternal && typeof window !== "undefined" && window.__DINOU_ROUTER_NAVIGATE__) {
11
+ window.__DINOU_ROUTER_NAVIGATE__(safeUrl);
12
+ } else if (typeof window !== "undefined") {
13
+ window.location.href = safeUrl;
14
+ }
15
+ }
16
+
7
17
  function createServerFunctionProxy(id) {
8
18
  return new Proxy(() => { }, {
9
19
  apply: async (_target, _thisArg, args) => {
@@ -37,8 +47,8 @@ function createServerFunctionProxy(id) {
37
47
  // Check header first
38
48
  const redirectUrl = res.headers.get("X-Dinou-Redirect");
39
49
  if (redirectUrl) {
40
- window.location.href = isSafeRedirect(redirectUrl) ? redirectUrl : "/";
41
- return new Promise(() => { });
50
+ executeRedirect(redirectUrl);
51
+ return Promise.resolve();
42
52
  }
43
53
 
44
54
  const contentType = res.headers.get("content-type") || "";
@@ -54,8 +64,8 @@ function createServerFunctionProxy(id) {
54
64
  if (contentType.includes("application/json")) {
55
65
  const data = await res.json();
56
66
  if (data && data.redirect) {
57
- window.location.href = isSafeRedirect(data.redirect) ? data.redirect : "/";
58
- return new Promise(() => { });
67
+ executeRedirect(data.redirect);
68
+ return Promise.resolve();
59
69
  }
60
70
  return data;
61
71
  }
@@ -82,7 +92,7 @@ function createServerFunctionProxy(id) {
82
92
  const payload = JSON.parse(buffer.slice(2));
83
93
  if (payload.type === "redirect") {
84
94
  isRedirecting = true;
85
- window.location.href = isSafeRedirect(payload.url) ? payload.url : "/";
95
+ executeRedirect(payload.url);
86
96
  } else if (payload.type === "cookie") {
87
97
  document.cookie = payload.cookie;
88
98
  }
@@ -119,7 +129,7 @@ function createServerFunctionProxy(id) {
119
129
  const payload = JSON.parse(line.slice(2));
120
130
  if (payload.type === "redirect") {
121
131
  isRedirecting = true;
122
- window.location.href = isSafeRedirect(payload.url) ? payload.url : "/";
132
+ executeRedirect(payload.url);
123
133
  } else if (payload.type === "cookie") {
124
134
  document.cookie = payload.cookie;
125
135
  }
@@ -130,18 +140,14 @@ function createServerFunctionProxy(id) {
130
140
  cleanChunk += line + "\n";
131
141
  }
132
142
  }
133
-
134
143
  if (cleanChunk) {
135
144
  controller.enqueue(encoder.encode(cleanChunk));
136
145
  }
137
146
  }
138
147
  }
148
+ controller.close();
139
149
  } catch (err) {
140
150
  controller.error(err);
141
- } finally {
142
- if (!isRedirecting) {
143
- controller.close();
144
- }
145
151
  }
146
152
  },
147
153
  });
@@ -5,6 +5,16 @@ function isSafeRedirect(url) {
5
5
  return typeof url === "string" && url.startsWith("/") && !url.startsWith("//");
6
6
  }
7
7
 
8
+ function executeRedirect(url) {
9
+ const safeUrl = isSafeRedirect(url) ? url : "/";
10
+ const isInternal = safeUrl.startsWith("/") && !safeUrl.startsWith("//");
11
+ if (isInternal && typeof window !== "undefined" && window.__DINOU_ROUTER_NAVIGATE__) {
12
+ window.__DINOU_ROUTER_NAVIGATE__(safeUrl);
13
+ } else if (typeof window !== "undefined") {
14
+ window.location.href = safeUrl;
15
+ }
16
+ }
17
+
8
18
  export function createServerFunctionProxy(id) {
9
19
  return new Proxy(() => {}, {
10
20
  apply: async (_target, _thisArg, args) => {
@@ -38,8 +48,8 @@ export function createServerFunctionProxy(id) {
38
48
  // Check header first
39
49
  const redirectUrl = res.headers.get("X-Dinou-Redirect");
40
50
  if (redirectUrl) {
41
- window.location.href = isSafeRedirect(redirectUrl) ? redirectUrl : "/";
42
- return new Promise(() => {});
51
+ executeRedirect(redirectUrl);
52
+ return Promise.resolve();
43
53
  }
44
54
 
45
55
  const contentType = res.headers.get("content-type") || "";
@@ -55,8 +65,8 @@ export function createServerFunctionProxy(id) {
55
65
  if (contentType.includes("application/json")) {
56
66
  const data = await res.json();
57
67
  if (data && data.redirect) {
58
- window.location.href = isSafeRedirect(data.redirect) ? data.redirect : "/";
59
- return new Promise(() => {});
68
+ executeRedirect(data.redirect);
69
+ return Promise.resolve();
60
70
  }
61
71
  return data;
62
72
  }
@@ -83,7 +93,7 @@ export function createServerFunctionProxy(id) {
83
93
  const payload = JSON.parse(buffer.slice(2));
84
94
  if (payload.type === "redirect") {
85
95
  isRedirecting = true;
86
- window.location.href = isSafeRedirect(payload.url) ? payload.url : "/";
96
+ executeRedirect(payload.url);
87
97
  } else if (payload.type === "cookie") {
88
98
  document.cookie = payload.cookie;
89
99
  }
@@ -120,7 +130,7 @@ export function createServerFunctionProxy(id) {
120
130
  const payload = JSON.parse(line.slice(2));
121
131
  if (payload.type === "redirect") {
122
132
  isRedirecting = true;
123
- window.location.href = isSafeRedirect(payload.url) ? payload.url : "/";
133
+ executeRedirect(payload.url);
124
134
  } else if (payload.type === "cookie") {
125
135
  document.cookie = payload.cookie;
126
136
  }
@@ -137,12 +147,9 @@ export function createServerFunctionProxy(id) {
137
147
  }
138
148
  }
139
149
  }
150
+ controller.close();
140
151
  } catch (err) {
141
152
  controller.error(err);
142
- } finally {
143
- if (!isRedirecting) {
144
- controller.close();
145
- }
146
153
  }
147
154
  },
148
155
  });
@@ -918,10 +918,11 @@ app.post("/____server_function____", async (req, res) => {
918
918
  return res.status(404).json({ error: "File not found" });
919
919
  }
920
920
 
921
+ const normalizedKey = relativePath.replace(/\\/g, "/");
921
922
  let allowedExports;
922
923
  if (serverFunctionsManifest) {
923
924
  // Prod: use manifest (relativePath is already normalized)
924
- allowedExports = serverFunctionsManifest[relativePath];
925
+ allowedExports = serverFunctionsManifest[normalizedKey];
925
926
  } else {
926
927
  if (!isDevelopment) {
927
928
  return res
@@ -0,0 +1,68 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const isDevelopment = process.env.NODE_ENV !== "production";
5
+ const vfs = {};
6
+
7
+ function buildVfs(dir) {
8
+ if (!fs.existsSync(dir)) return;
9
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
10
+ const children = [];
11
+
12
+ for (const entry of entries) {
13
+ const fullPath = path.join(dir, entry.name);
14
+ const isDirectory = entry.isDirectory();
15
+ children.push({
16
+ name: entry.name,
17
+ isDirectory
18
+ });
19
+ if (isDirectory) {
20
+ buildVfs(fullPath);
21
+ } else {
22
+ vfs[fullPath] = { type: "file" };
23
+ }
24
+ }
25
+
26
+ vfs[dir] = {
27
+ type: "directory",
28
+ children
29
+ };
30
+ }
31
+
32
+ if (!isDevelopment) {
33
+ const srcDir = path.resolve(process.cwd(), "src");
34
+ buildVfs(srcDir);
35
+ }
36
+
37
+ function existsSync(filePath) {
38
+ if (isDevelopment) {
39
+ return fs.existsSync(filePath);
40
+ }
41
+ const normalized = path.resolve(filePath);
42
+ return !!vfs[normalized];
43
+ }
44
+
45
+ function readdirSync(dirPath, options) {
46
+ if (isDevelopment) {
47
+ return fs.readdirSync(dirPath, options);
48
+ }
49
+ const normalized = path.resolve(dirPath);
50
+ const entry = vfs[normalized];
51
+ if (!entry || entry.type !== "directory") {
52
+ throw new Error(`ENOTDIR: not a directory, readdir '${dirPath}'`);
53
+ }
54
+
55
+ if (options && options.withFileTypes) {
56
+ return entry.children.map(child => ({
57
+ name: child.name,
58
+ isDirectory: () => child.isDirectory,
59
+ isFile: () => !child.isDirectory
60
+ }));
61
+ }
62
+ return entry.children.map(child => child.name);
63
+ }
64
+
65
+ module.exports = {
66
+ existsSync,
67
+ readdirSync
68
+ };
@@ -19,7 +19,7 @@ export default function serverFunctionsPlugin(manifestData = {}) {
19
19
  const exports = parseExports(code);
20
20
  if (exports.length === 0) return null;
21
21
 
22
- const relativePath = path.relative(root, args.path);
22
+ const relativePath = path.relative(root, args.path).replace(/\\/g, "/");
23
23
  serverFunctions.set(relativePath, new Set(exports)); // Save exports as a Set to guarantee uniqueness
24
24
 
25
25
  const fileUrl = `file:///${relativePath}`;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou-ejected",
3
- "version": "4.0.13",
3
+ "version": "4.0.15",
4
4
  "main": "index.js",
5
5
  "private": true,
6
6
  "exports": {
@@ -17,7 +17,7 @@ function serverFunctionsPlugin() {
17
17
  const exports = parseExports(code);
18
18
  if (exports.length === 0) return null;
19
19
 
20
- const relativePath = path.relative(root, id);
20
+ const relativePath = path.relative(root, id).replace(/\\/g, "/");
21
21
  serverFunctions.set(relativePath, new Set(exports)); // Save exports as a Set for uniqueness
22
22
 
23
23
  const fileUrl = `file:///${relativePath}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "4.0.13",
3
+ "version": "4.0.15",
4
4
  "description": "Full-Stack React 19 framework with React Server Components, Server Functions, and Streaming SSR.",
5
5
  "main": "index.js",
6
6
  "bin": {