dinou 4.0.16 → 5.0.1
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 +15 -0
- package/README.md +150 -57
- package/dinou/core/babel-esm-loader.js +87 -5
- package/dinou/core/build-static-pages.js +17 -97
- package/dinou/core/generate-static-page.js +21 -3
- package/dinou/core/generate-static-pages.js +30 -41
- package/dinou/core/generate-static-rsc.js +11 -5
- package/dinou/core/generate-static-rscs.js +11 -5
- package/dinou/core/generate-static.js +2 -3
- package/dinou/core/get-jsx.js +2 -1
- package/dinou/core/get-ssg-metadata.js +5 -13
- package/dinou/core/import-module.js +25 -6
- package/dinou/core/navigation.js +42 -27
- package/dinou/core/redirect.jsx +18 -11
- package/dinou/core/register-loader.mjs +1 -0
- package/dinou/core/render-app-to-html.js +198 -31
- package/dinou/core/render-html.js +144 -26
- package/dinou/core/render-jsx-to-client-jsx.js +6 -7
- package/dinou/core/request-context.js +11 -6
- package/dinou/core/revalidating.js +29 -25
- package/dinou/core/server.js +270 -85
- package/dinou/esbuild/dev.mjs +31 -2
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +0 -40
- package/dinou/index.mjs +31 -10
- package/dinou/package.json +1 -1
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +88 -49
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +7 -9
- package/dinou/rollup/rollup.config.js +1 -2
- package/dinou/webpack/helpers/get-webpack-entries.js +0 -40
- package/package.json +2 -2
- package/dinou/core/babel-plugin-register-imports.js +0 -50
- package/dinou/core/get-ssg-jsx-or-jsx.js +0 -14
- package/dinou/core/get-ssg-jsx.js +0 -83
- package/dinou/core/jsx-json.js +0 -55
- package/dinou/core/register-hooks.js +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.1] - 2026-07-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **ESM SSR Navigation Context Resolution**: Replaced direct `require` with a dynamic loader hook (`__dinou_require__` or `module.require`) in `navigation.js` to resolve the request context inside ESM SSR execution trees (e.g., Esbuild and Rollup client bundlers), preventing `ReferenceError: require is not defined` and resolving client component hydration mismatches during route resolution.
|
|
12
|
+
|
|
13
|
+
## [5.0.0] - 2026-07-05
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **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.
|
|
17
|
+
- **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.
|
|
18
|
+
- **Anti-Bot Shield**: Integrated an in-memory Express middleware firewall to detect and block common bot scans and malicious queries instantly.
|
|
19
|
+
|
|
20
|
+
### Refactored
|
|
21
|
+
- **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.
|
|
22
|
+
|
|
8
23
|
## [4.0.16] - 2026-06-30
|
|
9
24
|
|
|
10
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
[](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:**
|
|
14
|
-
- **Server Functions:**
|
|
15
|
-
- **Automatic Static Bailout:** Static by default (SSG).
|
|
16
|
-
- **
|
|
17
|
-
- **Generation Strategies:**
|
|
18
|
-
- **Bundler Agnostic:**
|
|
19
|
-
- **
|
|
20
|
-
- **File-System Routing:**
|
|
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.
|
|
60
|
-
- [3.
|
|
61
|
-
- [4.
|
|
62
|
-
- [5.
|
|
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(
|
|
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
|
|
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:** `
|
|
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(
|
|
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.
|
|
1020
|
+
### 2. Utilities (`dinou`)
|
|
975
1021
|
|
|
976
|
-
Functions available in
|
|
1022
|
+
Functions available in server and client environments.
|
|
977
1023
|
|
|
978
1024
|
#### `redirect(destination)`
|
|
979
1025
|
|
|
980
|
-
|
|
1026
|
+
Immediately halts rendering/execution and redirects the user. Works in Server Components, Client Components, Server Functions, and Page Functions.
|
|
981
1027
|
|
|
982
|
-
- **
|
|
983
|
-
- **
|
|
984
|
-
- **
|
|
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
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
>
|
|
1067
|
+
export default function PageInfo() {
|
|
1068
|
+
const pathname = usePathname();
|
|
1069
|
+
return <p>Current path: {pathname}</p>;
|
|
1070
|
+
}
|
|
1071
|
+
```
|
|
1015
1072
|
|
|
1016
|
-
|
|
1073
|
+
#### `useRouter()`
|
|
1017
1074
|
|
|
1018
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1084
|
+
```javascript
|
|
1085
|
+
import { useRouter } from "dinou";
|
|
1026
1086
|
|
|
1027
|
-
|
|
1087
|
+
export default function BackButton() {
|
|
1088
|
+
const router = useRouter();
|
|
1089
|
+
return <button onClick={() => router.back()}>Back</button>;
|
|
1090
|
+
}
|
|
1091
|
+
```
|
|
1028
1092
|
|
|
1029
|
-
|
|
1093
|
+
#### `useNavigationLoading()`
|
|
1030
1094
|
|
|
1031
|
-
-
|
|
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
|
-
|
|
1097
|
+
```javascript
|
|
1098
|
+
import { useNavigationLoading } from "dinou";
|
|
1035
1099
|
|
|
1036
|
-
|
|
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
|
-
###
|
|
1108
|
+
### 4. Server-Only Utilities (`dinou`)
|
|
1041
1109
|
|
|
1042
1110
|
#### `getContext()`
|
|
1043
1111
|
|
|
1044
|
-
Retrieves the request/response context. **Server Components
|
|
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
|
-
###
|
|
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(
|
|
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:**
|
|
1178
|
+
- **Receives:** Resolved `params` object.
|
|
1111
1179
|
- **Returns:** Object with the props.
|
|
1112
1180
|
|
|
1113
1181
|
```typescript
|
|
1114
|
-
export async function getProps(
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
951
|
-
|
|
952
|
-
jsx: serializeReactElement(jsx),
|
|
942
|
+
staticRoutes.add(reqPath);
|
|
943
|
+
staticMetadata.set(reqPath, {
|
|
953
944
|
revalidate: revalidate?.(),
|
|
954
|
-
|
|
955
|
-
metadata: { effects: sideEffects },
|
|
945
|
+
effects: sideEffects,
|
|
956
946
|
});
|
|
957
|
-
console.log(`
|
|
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
|
-
|
|
1231
|
-
|
|
1220
|
+
staticRoutes.add(reqPath);
|
|
1221
|
+
staticMetadata.set(reqPath, {
|
|
1232
1222
|
revalidate: revalidate?.(),
|
|
1233
|
-
|
|
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
|
|
1245
|
-
|
|
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
|
-
|
|
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
|
};
|