dinou 4.0.15 → 5.0.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +150 -57
  3. package/dinou/core/babel-esm-loader.js +87 -5
  4. package/dinou/core/build-static-pages.js +17 -97
  5. package/dinou/core/generate-static-page.js +21 -3
  6. package/dinou/core/generate-static-pages.js +30 -41
  7. package/dinou/core/generate-static-rsc.js +11 -5
  8. package/dinou/core/generate-static-rscs.js +11 -5
  9. package/dinou/core/generate-static.js +2 -3
  10. package/dinou/core/get-jsx.js +2 -1
  11. package/dinou/core/get-ssg-metadata.js +5 -13
  12. package/dinou/core/import-module.js +25 -6
  13. package/dinou/core/redirect.jsx +18 -11
  14. package/dinou/core/register-loader.mjs +1 -0
  15. package/dinou/core/render-app-to-html.js +209 -35
  16. package/dinou/core/render-html.js +144 -26
  17. package/dinou/core/render-jsx-to-client-jsx.js +6 -7
  18. package/dinou/core/request-context.js +11 -6
  19. package/dinou/core/revalidating.js +29 -25
  20. package/dinou/core/server.js +279 -88
  21. package/dinou/core/url-resolver.js +3 -0
  22. package/dinou/esbuild/dev.mjs +31 -2
  23. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +0 -40
  24. package/dinou/index.d.ts +1 -1
  25. package/dinou/index.mjs +31 -10
  26. package/dinou/package.json +1 -1
  27. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +88 -49
  28. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +7 -9
  29. package/dinou/rollup/rollup.config.js +1 -2
  30. package/dinou/webpack/helpers/get-webpack-entries.js +0 -40
  31. package/package.json +3 -3
  32. package/dinou/core/babel-plugin-register-imports.js +0 -50
  33. package/dinou/core/get-ssg-jsx-or-jsx.js +0 -14
  34. package/dinou/core/get-ssg-jsx.js +0 -83
  35. package/dinou/core/jsx-json.js +0 -55
  36. package/dinou/core/register-hooks.js +0 -18
package/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ 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.0] - 2026-07-05
9
+
10
+ ### Added
11
+ - **Dynamic Parameter Validation (`validateParams`)**: Support for defining `validateParams` in `page_functions.ts` to validate route parameters on the server and return a 404 response immediately if invalid.
12
+ - **ISG Generation Opt-Out (`allowISG`)**: Support for defining `allowISG` in `page_functions.ts` to disable on-demand Incremental Static Generation (ISG) for undeclared route parameters.
13
+ - **Anti-Bot Shield**: Integrated an in-memory Express middleware firewall to detect and block common bot scans and malicious queries instantly.
14
+
15
+ ### Refactored
16
+ - **Native RSC Flight Payload Streaming**: Major architectural refactor replacing process-to-process custom JSX serialization/deserialization with React's native streaming of RSC Flight payloads via `createFromNodeStream` on the SSR process.
17
+
18
+ ## [4.0.16] - 2026-06-30
19
+
20
+ ### Fixed
21
+ - **Cookie Deletion Attributes**: Reconstructed streaming cookie deletion to serialize and include `domain`, `secure`, and `sameSite` properties, ensuring browsers match scopes and delete cookies correctly.
22
+ - **Defensive Redirects**: Added a safety check in `resolveRelativeUrl` to fallback to `/` if the destination path is invalid (such as undefined or null), avoiding server crashes.
23
+ - **TypeScript Type Definitions**: Updated the `clearCookie` signature in `index.d.ts` to accept the full `CookieOptions` interface, resolving compilation errors when using `sameSite` or `secure` attributes.
24
+
8
25
  ## [4.0.15] - 2026-06-29
9
26
 
10
27
  ### Added
package/README.md CHANGED
@@ -2,22 +2,22 @@
2
2
 
3
3
  [![Documentation](https://img.shields.io/badge/docs-dinou.dev-blue?style=flat-square)](https://dinou.dev)
4
4
 
5
- ### **Full-Stack React 19 Framework**
5
+ ### **Lightweight-Ejectable Full-Stack React 19 Framework**
6
6
 
7
7
  ---
8
8
 
9
- Support for React Server Components (RSC), Server-Side Rendering (SSR), Static Generation (SSG), Incremental Static Generation (ISG), and Incremental Static Regeneration (ISR).
9
+ Support for React Server Components (RSC), Server Functions, Server-Side Rendering (SSR), Static Generation (SSG), Incremental Static Generation (ISG), and Incremental Static Regeneration (ISR).
10
10
 
11
11
  ## Key Features
12
12
 
13
- - **React Server Components:** Built on the React 19 core, leveraging Suspense and Streaming for optimal performance without client-side waterfalls.
14
- - **Server Functions:** Type-safe RPC actions. Execute server-side logic directly from your components without creating manual API endpoints.
15
- - **Automatic Static Bailout:** Static by default (SSG). Automatically switches to Dynamic Rendering (SSR) when request-specific data (cookies, headers, search params) is detected.
16
- - **Full-Featured Router:** Client-side soft navigation including `push`, `replace`, `back`, `forward`, and `refresh` (soft reload).
17
- - **Generation Strategies:** Comprehensive support for Incremental Static Regeneration (ISR) and Incremental Static Generation (ISG).
18
- - **Bundler Agnostic:** The only framework that lets you choose your build engine. Switch between Webpack, Rollup, or Esbuild via configuration.
19
- - **Smart Navigation:** `<Link>` component with automatic prefetching and opt-in `fresh` data fetching for volatile states.
20
- - **File-System Routing:** Intuitive routing based on `page.{jsx,tsx}` files located within the `src` directory structure.
13
+ - **React Server Components:** Native integration of React 19 Server Components, streaming HTML/RSC payloads progressively using `renderToPipeableStream` and Suspense.
14
+ - **Server Functions:** Functions marked with `"use server"` that execute on the server and can be called from client components. Supports returning rendered React components (Server or Client) directly to the client.
15
+ - **Automatic Static Bailout:** Static by default (SSG). Bypasses static files and evaluates dynamically at request time if the request accesses headers, cookies, or query parameters.
16
+ - **Client Router:** Client-side soft navigation (`push`, `replace`, `back`, `forward`, `refresh`) with a `useNavigationLoading` hook to track transition loading states.
17
+ - **Generation Strategies:** Support for background Incremental Static Regeneration (ISR) and on-demand Incremental Static Generation (ISG) which generates and caches pages on their very first request.
18
+ - **Bundler Agnostic:** Supports Webpack, Rollup, and Esbuild as build engines, chosen by executing the corresponding npm or npx script (e.g., `dev:rollup`, `build:webpack`).
19
+ - **Navigation Prefetching:** `<Link>` component with automatic prefetching on hover, and an opt-in `fresh` prop to bypass the client cache.
20
+ - **File-System Routing:** Routing based on `page.{jsx,tsx}` file paths, supporting optional segments, catch-all paths, Route Groups, and Parallel Route slots (with layout error containment).
21
21
 
22
22
  ## Table of contents
23
23
 
@@ -51,15 +51,18 @@ Support for React Server Components (RSC), Server-Side Rendering (SSR), Static G
51
51
  - [2. `getStaticPaths` (Static Generation)](#2-getstaticpaths-static-generation)
52
52
  - [3. `revalidate` (ISR)](#3-revalidate-isr)
53
53
  - [4. `dynamic` (Force SSR)](#4-dynamic-force-ssr)
54
+ - [5. `validateParams` (Route Parameter Validation)](#5-validateparams-route-parameter-validation)
55
+ - [6. `allowISG` (Control On-Demand Generation)](#6-allowisg-control-on-demand-generation)
54
56
  - [⚡ React Compiler & Automatic Optimizations](#-react-compiler--automatic-optimizations)
55
57
  - [Integration Strategy](#integration-strategy)
56
58
  - [Code Example](#code-example)
57
59
  - [📚 API Reference](#-api-reference)
58
60
  - [1. Components (`dinou`)](#1-components-dinou)
59
- - [2. Hooks & Utilities (`dinou`)](#2-hooks--utilities-dinou)
60
- - [3. Server-Only Utilities (`dinou`)](#3-server-only-utilities-dinou)
61
- - [4. Page Configuration (`page_functions.ts`)](#4-page-configuration-page_functionsts)
62
- - [5. File Conventions Cheatsheet](#5-file-conventions-cheatsheet)
61
+ - [2. Utilities (`dinou`)](#2-utilities-dinou)
62
+ - [3. Client Hooks (`dinou` - Client Components Only)](#3-client-hooks-dinou---client-components-only)
63
+ - [4. Server-Only Utilities (`dinou`)](#4-server-only-utilities-dinou)
64
+ - [5. Page Configuration (`page_functions.ts`)](#5-page-configuration-page_functionsts)
65
+ - [6. File Conventions Cheatsheet](#6-file-conventions-cheatsheet)
63
66
  - [🎨 Favicons](#-favicons)
64
67
  - [🔐 Environment Variables (`.env`)](#-environment-variables-env)
65
68
  - [💅 Styles (Tailwind, CSS Modules, & Global CSS)](#-styles-tailwind-css-modules--global-css)
@@ -246,6 +249,18 @@ export default function Controls() {
246
249
  }
247
250
  ```
248
251
 
252
+ #### Relative Routing (Directory-First Convention)
253
+
254
+ Relative paths in both `<Link>` and programmatic navigation (`router.push`) resolve by treating the current path segment as a directory. The framework internally appends a trailing slash `/` to the current pathname before resolving.
255
+
256
+ | Current Path | Target Path | Resolves To (Dinou) | Resolves To (Native Browser) |
257
+ | :----------- | :---------- | :------------------ | :--------------------------- |
258
+ | `/blog/post` | `details` | `/blog/post/details`| `/blog/details` |
259
+ | `/blog/post` | `./details` | `/blog/post/details`| `/blog/details` |
260
+ | `/blog/post` | `../about` | `/blog/about` | `/about` |
261
+
262
+ To navigate from `/blog/post` to `/blog/about`, use `../about` or the absolute path `/blog/about`.
263
+
249
264
  ## Layouts & Hierarchical Rendering
250
265
 
251
266
  Dinou uses a nested routing system. Layouts, Error pages, and Not Found pages cascade down the directory hierarchy.
@@ -284,7 +299,7 @@ The Root Layout that applies to a specific page can receive additional props by
284
299
 
285
300
  ```typescript
286
301
  // src/foo/bar/page_functions.ts
287
- export async function getProps({ params }) {
302
+ export async function getProps(params) {
288
303
  // fetch data using route params
289
304
  const data = await fetchData(params.id);
290
305
  // 'layout' props are injected into the Root Layout for this page
@@ -689,15 +704,16 @@ For advanced control over rendering behavior, data fetching, and static generati
689
704
 
690
705
  Use this function to fetch data based on the **route parameters** and inject it into your Page and Layout.
691
706
 
692
- > **Design Note:** `getProps` only receives `params`. To use request-specific data like `searchParams` or `cookies`, fetch data directly inside your Server Components using `Suspense` or Hooks to avoid blocking the initial HTML render.
707
+ > **Design Note:** While `getProps` only receives the route `params` as its parameter, you can still read cookies, headers, or query parameters by calling `getContext()` since the function executes on the server.
708
+ > Because `getProps` blocks the rendering process at request time, keep it fast for dynamic routes. Note that this does not affect pre-rendered static pages (SSG) since their data is fetched at server startup. In Incremental Static Generation (ISG), it only blocks the very first request by the first visitor before the page is cached. If you need slow dynamic fetches on dynamic routes, defer them to a `Suspense` boundary wrapping a Server Function.
693
709
 
694
- - **Arguments:** `{ params }` (The dynamic route parameters).
710
+ - **Arguments:** `params` (The dynamic route parameters).
695
711
  - **Returns:** An object with `page` and `layout` keys containing the props.
696
712
 
697
713
  ```typescript
698
714
  // src/blog/[slug]/page_functions.ts
699
715
 
700
- export async function getProps({ params }) {
716
+ export async function getProps(params) {
701
717
  // 1. Fetch data based on the URL path (e.g., /blog/my-post)
702
718
  const post = await db.getPost(params.slug);
703
719
 
@@ -866,6 +882,36 @@ export function dynamic() {
866
882
  }
867
883
  ```
868
884
 
885
+ ### 5. `validateParams` (Route Parameter Validation)
886
+
887
+ Allows validating dynamic parameters on the server prior to rendering or static generation. Returning `false` blocks rendering and returns a 404 response.
888
+
889
+ - **Arguments:** `params` (The dynamic route parameters).
890
+ - **Returns:** `boolean` or `Promise<boolean>`.
891
+
892
+ ```typescript
893
+ // src/posts/[id]/page_functions.ts
894
+ export function validateParams(params) {
895
+ // Reject route parameter and return 404 instantly if not numeric
896
+ return typeof params.id === "string" && /^\d+$/.test(params.id);
897
+ }
898
+ ```
899
+
900
+ ### 6. `allowISG` (Control On-Demand Generation)
901
+
902
+ Enables or disables Incremental Static Generation (ISG) on-demand for dynamic routes. If disabled (returns `false`), routes not pre-rendered at server startup via `getStaticPaths` will return a 404 instead of generating dynamically on-demand.
903
+
904
+ - **Returns:** `boolean` or `Promise<boolean>`.
905
+
906
+ ```typescript
907
+ // src/items/[id]/page_functions.ts
908
+ export function allowISG() {
909
+ // Only serve paths generated at startup by getStaticPaths.
910
+ // Abort on-demand generation for other IDs.
911
+ return false;
912
+ }
913
+ ```
914
+
869
915
  ## ⚡ React Compiler & Automatic Optimizations
870
916
 
871
917
  Dinou integrates the **React Compiler** (React 19+) out of the box. It analyzes your code and automatically applies fine-grained memoization to values and functions.
@@ -971,26 +1017,32 @@ return <ClientRedirect to="/" />;
971
1017
 
972
1018
  ---
973
1019
 
974
- ### 2. Hooks & Utilities (`dinou`)
1020
+ ### 2. Utilities (`dinou`)
975
1021
 
976
- Functions available in **both** Server and Client environments.
1022
+ Functions available in server and client environments.
977
1023
 
978
1024
  #### `redirect(destination)`
979
1025
 
980
- Stops execution and redirects the user.
1026
+ Immediately halts rendering/execution and redirects the user. Works in Server Components, Client Components, Server Functions, and Page Functions.
981
1027
 
982
- - **Server:** Sets HTTP 307 header (if headers not sent).
983
- - **Client:** Renders `<ClientRedirect />`.
984
- - **Usage:** Use with `return` to halt rendering.
1028
+ - **Relative Paths:** Supports relative destinations resolving directory-first (filesystem style), matching the resolution rules of `<Link>` and programmatic navigation.
1029
+ - **Server:** Sets HTTP 307 header (if headers are not sent).
1030
+ - **Client:** Renders `<ClientRedirect />` internally to trigger soft client-side transition.
1031
+ - **Usage:** Always use with `return` to immediately stop rendering:
1032
+ ```javascript
1033
+ import { redirect } from "dinou";
1034
+ if (!user) return redirect("/login");
1035
+ ```
985
1036
 
986
- ```javascript
987
- import { redirect } from "dinou";
988
- if (!user) return redirect("/login");
989
- ```
1037
+ ---
1038
+
1039
+ ### 3. Client Hooks (`dinou` - Client Components Only)
1040
+
1041
+ Hooks that can **only** be used inside Client Components (files starting with `"use client"`). In React 19, calling these hooks inside a Server Component will throw a compilation/runtime crash.
990
1042
 
991
1043
  #### `useSearchParams()`
992
1044
 
993
- Returns a standard [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object to read the query string.
1045
+ Returns a standard [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object representing the query parameters of the current URL.
994
1046
 
995
1047
  ```javascript
996
1048
  import { useSearchParams } from "dinou";
@@ -1002,46 +1054,62 @@ export default function SearchPage() {
1002
1054
  }
1003
1055
  ```
1004
1056
 
1005
- **Behavior & Static Generation (Bailout):**
1057
+ - **Static Generation (Bailout):** Accessing `useSearchParams()` in a Client Component does **NOT** trigger a static bailout at server startup. The page remains static (SSG). The server pre-renders the page with empty query parameters, and the client updates the values upon hydration in the browser.
1058
+ - **Hydration Warning:** If the initial UI depends heavily on query parameters, pass them as props from a Server Component or Page Functions instead to avoid UI flickering/hydration mismatches.
1059
+
1060
+ #### `usePathname()`
1061
+
1062
+ Returns the normalized current URL pathname as a `string` (e.g., `/blog/post-1`).
1006
1063
 
1007
- | Component Type | Behavior during Build | Result |
1008
- | :------------------- | :------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
1009
- | **Server Component** | Accessing this hook triggers a **Static Bailout**. | The page opts out of SSG and switches to **Dynamic Rendering** (SSR) on demand. |
1010
- | **Client Component** | Does **NOT** trigger a bailout. | The page remains **Static (SSG)**. The initial HTML renders with empty params, and the browser updates values upon hydration. |
1064
+ ```javascript
1065
+ import { usePathname } from "dinou";
1011
1066
 
1012
- > **⚠️ Client Component Warning:**
1013
- > If used in a Client Component on a static page, be aware of **Hydration Mismatches**. The server renders with empty params (since they don't exist at build time), but the browser renders with the real URL.
1014
- > **Recommendation:** If the initial UI depends heavily on params, pass them as props from a Server Component to force Dynamic Rendering.
1067
+ export default function PageInfo() {
1068
+ const pathname = usePathname();
1069
+ return <p>Current path: {pathname}</p>;
1070
+ }
1071
+ ```
1015
1072
 
1016
- **Common Methods:**
1073
+ #### `useRouter()`
1017
1074
 
1018
- - `.get(name)`: Returns the first value.
1019
- - `.getAll(name)`: Returns all values (useful for `?id=1&id=2`).
1020
- - `.has(name)`: Checks existence.
1021
- - `.toString()`: Returns the query string.
1075
+ Provides programmatic navigation methods.
1022
1076
 
1023
- #### `usePathname()`
1077
+ - **Methods:**
1078
+ - `.push(href)`: Navigate to a new route. Supports relative pathnames (directory-first convention).
1079
+ - `.replace(href)`: Replace the current history entry. Supports relative pathnames.
1080
+ - `.back()`: Navigate back in history.
1081
+ - `.forward()`: Navigate forward in history.
1082
+ - `.refresh()`: Soft reload. Re-fetches Server Component data without reloading the browser.
1024
1083
 
1025
- Returns the current URL pathname as a `string` (e.g., `/blog/post-1`).
1084
+ ```javascript
1085
+ import { useRouter } from "dinou";
1026
1086
 
1027
- #### `useRouter()` (Client Only)
1087
+ export default function BackButton() {
1088
+ const router = useRouter();
1089
+ return <button onClick={() => router.back()}>Back</button>;
1090
+ }
1091
+ ```
1028
1092
 
1029
- Provides programmatic navigation methods.
1093
+ #### `useNavigationLoading()`
1030
1094
 
1031
- - **Methods:** `.push(href)`, `.replace(href)`, `.back()`, `.forward()`, `.refresh()`.
1032
- - **Note:** Only works inside Client Components (`"use client"`).
1095
+ Returns a `boolean` indicating if a client-side navigation transition is currently in progress.
1033
1096
 
1034
- #### `useNavigationLoading()` (Client Only)
1097
+ ```javascript
1098
+ import { useNavigationLoading } from "dinou";
1035
1099
 
1036
- Returns a `boolean` indicating if a client-side navigation is in progress.
1100
+ export default function LoadingBar() {
1101
+ const isLoading = useNavigationLoading();
1102
+ return isLoading ? <div className="loading-spinner" /> : null;
1103
+ }
1104
+ ```
1037
1105
 
1038
1106
  ---
1039
1107
 
1040
- ### 3. Server-Only Utilities (`dinou`)
1108
+ ### 4. Server-Only Utilities (`dinou`)
1041
1109
 
1042
1110
  #### `getContext()`
1043
1111
 
1044
- Retrieves the request/response context. **Server Components Only**.
1112
+ Retrieves the request/response context. Available **only** inside server-side execution threads (Server Components, `page_functions` like `getProps`, and Server Functions).
1045
1113
 
1046
1114
  - **Returns:** `{ req, res }`.
1047
1115
  - **req:** `headers`, `cookies`, `query`, `path`, `method`.
@@ -1087,7 +1155,7 @@ export default function Page() {
1087
1155
 
1088
1156
  ---
1089
1157
 
1090
- ### 4. Page Configuration (`page_functions.ts`)
1158
+ ### 5. Page Configuration (`page_functions.ts`)
1091
1159
 
1092
1160
  Export these functions from `page_functions.{ts,js}` to configure the associated `page.tsx`.
1093
1161
 
@@ -1103,15 +1171,15 @@ export function getStaticPaths() {
1103
1171
  }
1104
1172
  ```
1105
1173
 
1106
- #### `getProps({ params })`
1174
+ #### `getProps(params)`
1107
1175
 
1108
1176
  **Async** function to fetch data on the server and pass it as props to the Page component and to the Root Layout (if exists).
1109
1177
 
1110
- - **Receives:** Object with resolved `params`.
1178
+ - **Receives:** Resolved `params` object.
1111
1179
  - **Returns:** Object with the props.
1112
1180
 
1113
1181
  ```typescript
1114
- export async function getProps({ params }) {
1182
+ export async function getProps(params) {
1115
1183
  const data = await db.getItem(params.id);
1116
1184
  return { page: { item: data }, layout: { title: data.title } }; // Available as props in page.tsx and Root Layout of this particular page.
1117
1185
  }
@@ -1141,9 +1209,34 @@ export function dynamic() {
1141
1209
  }
1142
1210
  ```
1143
1211
 
1212
+ #### `validateParams(params)`
1213
+
1214
+ Validates dynamic parameters on the server prior to rendering or static generation. Returning `false` blocks the request and returns a 404 response.
1215
+
1216
+ - **Receives:** Resolved `params` object.
1217
+ - **Returns:** `boolean` or `Promise<boolean>`.
1218
+
1219
+ ```typescript
1220
+ export function validateParams(params) {
1221
+ return /^\d+$/.test(params.id);
1222
+ }
1223
+ ```
1224
+
1225
+ #### `allowISG()`
1226
+
1227
+ Controls on-demand Incremental Static Generation (ISG) for routes not generated at server startup.
1228
+
1229
+ - **Returns:** `boolean` or `Promise<boolean>`.
1230
+
1231
+ ```typescript
1232
+ export function allowISG() {
1233
+ return false; // Only serve getStaticPaths, return 404 for others
1234
+ }
1235
+ ```
1236
+
1144
1237
  ---
1145
1238
 
1146
- ### 5. File Conventions Cheatsheet
1239
+ ### 6. File Conventions Cheatsheet
1147
1240
 
1148
1241
  Dinou recognizes specific filenames to build the routing hierarchy.
1149
1242
 
@@ -6,6 +6,37 @@ 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 Module = require("module");
10
+ const originalResolveFilename = Module._resolveFilename;
11
+ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
12
+
13
+ let reactServerPath, reactDomServerPath, reactJsxRuntimePath, reactJsxDevRuntimePath;
14
+
15
+ if (!isWebpack) {
16
+ const reactPkgJson = require.resolve("react/package.json");
17
+ reactServerPath = path.join(path.dirname(reactPkgJson), "react.react-server.js");
18
+ reactJsxRuntimePath = path.join(path.dirname(reactPkgJson), "jsx-runtime.react-server.js");
19
+ reactJsxDevRuntimePath = path.join(path.dirname(reactPkgJson), "jsx-dev-runtime.react-server.js");
20
+
21
+ const reactDomPkgJson = require.resolve("react-dom/package.json");
22
+ reactDomServerPath = path.join(path.dirname(reactDomPkgJson), "react-dom.react-server.js");
23
+ }
24
+
25
+ Module._resolveFilename = function (request, parent, isMain, options) {
26
+ if (!isWebpack) {
27
+ if (request === "react") {
28
+ return reactServerPath;
29
+ } else if (request === "react-dom") {
30
+ return reactDomServerPath;
31
+ } else if (request === "react/jsx-runtime") {
32
+ return reactJsxRuntimePath;
33
+ } else if (request === "react/jsx-dev-runtime") {
34
+ return reactJsxDevRuntimePath;
35
+ }
36
+ }
37
+ return originalResolveFilename.call(this, request, parent, isMain, options);
38
+ };
39
+
9
40
  require("./css-require-hook.js")();
10
41
 
11
42
  exports.resolve = async function resolve(specifier, context, defaultResolve) {
@@ -49,19 +80,70 @@ exports.load = async function load(url, context, defaultLoad) {
49
80
  return { format: "module", source, shortCircuit: true, url };
50
81
  }
51
82
 
52
- if (/\.(jsx|tsx|ts|js)$/.test(url)) {
83
+ const cleanUrl = url.split("?")[0];
84
+ if (/\.(jsx|tsx|ts|js)$/.test(cleanUrl)) {
53
85
  let filename;
54
86
  try {
55
87
  filename = fileURLToPath(
56
- url.startsWith("file://") ? url : pathToFileURL(url).href
88
+ cleanUrl.startsWith("file://") ? cleanUrl : pathToFileURL(cleanUrl).href
57
89
  );
58
90
  } catch (e) {
59
91
  throw e;
60
92
  }
61
93
  const rel = path.relative(process.cwd(), filename);
62
- if (ext === ".js" && !rel.startsWith("src" + path.sep))
63
- return defaultLoad(url, context, defaultLoad);
64
94
  const source = fs.readFileSync(filename, "utf-8");
95
+ const urlToReturn = pathToFileURL(filename).href;
96
+
97
+ const useClientRegex =
98
+ /^\s*(?:(?:\/\/[^\n]*\n\s*)|(?:\/\*[\s\S]*?\*\/\s*))*['"]use client['"]/;
99
+ const hasUseClient = useClientRegex.test(source);
100
+
101
+ const esmSyntaxRegex = /^(?:import|export)\b/m;
102
+ const hasESMSyntax = esmSyntaxRegex.test(source);
103
+
104
+ if (ext === ".js" && !rel.startsWith("src" + path.sep) && !hasESMSyntax) {
105
+ return defaultLoad(url, context, defaultLoad);
106
+ }
107
+
108
+ const isReactServer = process.execArgv.some(arg => arg.includes("react-server"));
109
+ if (isReactServer && hasUseClient) {
110
+ const parseExports = require("./parse-exports.js");
111
+ const exports = parseExports(source);
112
+ let newSrc = "";
113
+ if (isWebpack) {
114
+ newSrc += 'import { registerClientReference } from "react-server-dom-webpack/server";\n';
115
+ } else {
116
+ const packageJsonPath = require.resolve("@roggc/react-server-dom-esm/package.json");
117
+ const serverNodePath = path.join(path.dirname(packageJsonPath), "server.node.js");
118
+ const serverNodeUrl = pathToFileURL(serverNodePath).href;
119
+ newSrc += `import pkg from ${JSON.stringify(serverNodeUrl)};\n`;
120
+ newSrc += 'const {registerClientReference} = pkg;\n';
121
+ }
122
+ for (const name of exports) {
123
+ if (name === 'default') {
124
+ newSrc += 'export default ';
125
+ newSrc += 'registerClientReference(function() {';
126
+ newSrc += 'throw new Error(' + JSON.stringify("Attempted to call the default export of " + urlToReturn + " from the server but it's on the client.") + ');';
127
+ newSrc += '},';
128
+ newSrc += JSON.stringify(urlToReturn) + ',';
129
+ newSrc += JSON.stringify(name) + ');\n';
130
+ } else {
131
+ newSrc += 'export const ' + name + ' = ';
132
+ newSrc += 'registerClientReference(function() {';
133
+ newSrc += 'throw new Error(' + JSON.stringify("Attempted to call " + name + "() from the server but " + name + " is on the client.") + ');';
134
+ newSrc += '},';
135
+ newSrc += JSON.stringify(urlToReturn) + ',';
136
+ newSrc += JSON.stringify(name) + ');\n';
137
+ }
138
+ }
139
+ return {
140
+ format: "module",
141
+ source: newSrc,
142
+ shortCircuit: true,
143
+ url: urlToReturn,
144
+ };
145
+ }
146
+
65
147
  if (ext === ".js") {
66
148
  return {
67
149
  format: "module",
@@ -81,7 +163,7 @@ exports.load = async function load(url, context, defaultLoad) {
81
163
  ast: false,
82
164
  });
83
165
 
84
- const urlToReturn = pathToFileURL(filename).href;
166
+ // const urlToReturn = pathToFileURL(filename).href;
85
167
 
86
168
  return {
87
169
  format: "module",
@@ -7,7 +7,9 @@ const {
7
7
  } = require("./get-file-path-and-dynamic-params");
8
8
  const importModule = require("./import-module");
9
9
  const { requestStorage } = require("./request-context.js");
10
- const { setJSXJSON } = require("./jsx-json.js");
10
+
11
+ const staticRoutes = new Set();
12
+ const staticMetadata = new Map();
11
13
 
12
14
  function safeDecode(val) {
13
15
  try {
@@ -77,16 +79,6 @@ function createBailoutProxy(target, label, onBailout) {
77
79
  */
78
80
  async function buildStaticPages() {
79
81
  const srcFolder = path.resolve(process.cwd(), "src");
80
- const distFolder = path.resolve(process.cwd(), "dist");
81
-
82
- if (existsSync(distFolder)) {
83
- rmSync(distFolder, { recursive: true, force: true });
84
- console.log("Deleted existing dist folder");
85
- }
86
-
87
- if (!existsSync(distFolder)) {
88
- mkdirSync(distFolder, { recursive: true });
89
- }
90
82
 
91
83
  /**
92
84
  * Recursively traverses the page directory tree to collect all eligible static routes.
@@ -947,14 +939,12 @@ async function buildStaticPages() {
947
939
  cookies: mockRes._cookies,
948
940
  };
949
941
 
950
- // Store the serialized RSC JSON, revalidation timer, and side effects (cookies/redirects)
951
- setJSXJSON(reqPath, {
952
- jsx: serializeReactElement(jsx),
942
+ staticRoutes.add(reqPath);
943
+ staticMetadata.set(reqPath, {
953
944
  revalidate: revalidate?.(),
954
- generatedAt: Date.now(),
955
- metadata: { effects: sideEffects },
945
+ effects: sideEffects,
956
946
  });
957
- console.log(`Serialized JSX for page at ${reqPath}`);
947
+ console.log(`Registered static page at ${reqPath}`);
958
948
  } catch (err) {
959
949
  console.error(`Error building page ${segments.join("/")}:`, err);
960
950
  continue;
@@ -1227,99 +1217,29 @@ async function buildStaticPage(reqPath, isDynamic = null) {
1227
1217
  cookies: mockRes._cookies,
1228
1218
  };
1229
1219
 
1230
- setJSXJSON(reqPath, {
1231
- jsx: serializeReactElement(jsx),
1220
+ staticRoutes.add(reqPath);
1221
+ staticMetadata.set(reqPath, {
1232
1222
  revalidate: revalidate?.(),
1233
- generatedAt: Date.now(),
1234
- metadata: { effects: sideEffects },
1223
+ effects: sideEffects,
1235
1224
  });
1236
-
1237
- console.warn(`Generated serialized jsx at page: ${reqPath}`);
1225
+ console.warn(`Registered rebuilt static page at ${reqPath}`);
1238
1226
  } catch (error) {
1239
1227
  console.error(`Error building page ${reqPath}:`, error);
1240
1228
  throw error;
1241
1229
  }
1242
1230
  }
1243
1231
 
1244
- function filterProps(props_) {
1245
- if (React.isValidElement(props_)) {
1246
- return serializeReactElement(props_);
1247
- }
1248
- if (Array.isArray(props_)) {
1249
- return props_.map((item) => filterProps(item));
1250
- }
1251
- if (props_ && typeof props_ === "object") {
1252
- const props = {};
1253
- for (const [key, value] of Object.entries(props_)) {
1254
- if (!key.startsWith("_")) {
1255
- props[key] = filterProps(value);
1256
- }
1257
- }
1258
- return props;
1259
- }
1260
- return props_;
1232
+ function getStaticPaths() {
1233
+ return Array.from(staticRoutes);
1261
1234
  }
1262
1235
 
1263
- /**
1264
- * Recursively serializes a React element tree into a custom JSON format.
1265
- * This format represents the React Server Component (RSC) payload sent to the client,
1266
- * mapping HTML tags, React fragments, Suspense boundaries, and Client Component stubs.
1267
- */
1268
- function serializeReactElement(element) {
1269
- if (React.isValidElement(element)) {
1270
- let type;
1271
- let modulePath = null;
1272
- let componentName = null;
1273
- let isPackage = false;
1274
-
1275
- if (typeof element.type === "string") {
1276
- type = element.type;
1277
- } else if (element.type === Symbol.for("react.fragment")) {
1278
- type = "Fragment";
1279
- } else if (element.type === Symbol.for("react.suspense")) {
1280
- type = "Suspense";
1281
- } else {
1282
- modulePath = element.__modulePath;
1283
- if (modulePath) {
1284
- type = "__clientComponent__";
1285
- }
1286
- try {
1287
- componentName = element.type.displayName || element.type.name;
1288
- } catch (e) { }
1289
-
1290
- if (!modulePath && global.__DINOU_MODULE_MAP) {
1291
- const meta = global.__DINOU_MODULE_MAP.get(element.type);
1292
- modulePath = meta?.id;
1293
- isPackage = meta?.isPackage;
1294
- if (!isPackage && modulePath) {
1295
- type = "__clientComponent__";
1296
- }
1297
- }
1298
- }
1299
-
1300
- return {
1301
- type,
1302
- name: componentName,
1303
- isPackage,
1304
- modulePath: isPackage
1305
- ? modulePath
1306
- : modulePath
1307
- ? path.relative(process.cwd(), modulePath).split(path.sep).join("/")
1308
- : null,
1309
- props: {
1310
- ...filterProps(element.props),
1311
- children: Array.isArray(element.props.children)
1312
- ? element.props.children.map((child) => serializeReactElement(child))
1313
- : element.props.children
1314
- ? serializeReactElement(element.props.children)
1315
- : undefined,
1316
- },
1317
- };
1318
- }
1319
- return element;
1236
+ function getStaticMetadata(reqPath) {
1237
+ return staticMetadata.get(reqPath);
1320
1238
  }
1321
1239
 
1322
1240
  module.exports = {
1323
1241
  buildStaticPages,
1324
1242
  buildStaticPage,
1243
+ getStaticPaths,
1244
+ getStaticMetadata,
1325
1245
  };