dinou 1.4.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dinou/build-static-pages.js +24 -0
- package/dinou/client-error.jsx +36 -0
- package/dinou/get-error-jsx.js +122 -0
- package/dinou/get-jsx.js +12 -0
- package/dinou/render-html.js +155 -12
- package/dinou/server.js +31 -6
- package/package.json +1 -1
- package/webpack.config.js +5 -1
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ dinou main features are:
|
|
|
54
54
|
|
|
55
55
|
- Support for the use of an import alias in `tsconfig.json` or `jsconfig.json` file.
|
|
56
56
|
|
|
57
|
+
- Error handling with `error.tsx` pages, differentiationg behaviour in production and in development.
|
|
58
|
+
|
|
57
59
|
## Table of contents
|
|
58
60
|
|
|
59
61
|
- [Routing system, layouts, pages, not found pages, ...](#routing-system-layouts-pages-not-found-pages-)
|
|
@@ -98,6 +100,8 @@ dinou main features are:
|
|
|
98
100
|
|
|
99
101
|
- [Not Found Handling](#not-found-handling)
|
|
100
102
|
|
|
103
|
+
- [Error Handling](#error-handling)
|
|
104
|
+
|
|
101
105
|
- [`favicons` folder](#favicons-folder)
|
|
102
106
|
|
|
103
107
|
- [`.env` file](#env-file)
|
|
@@ -126,6 +130,10 @@ dinou main features are:
|
|
|
126
130
|
|
|
127
131
|
- If you don't want a `page` to be applied layouts define a `no_layout` file (without extension) in the same folder. A `no_layout` file, if present, also applies to the `not_found` file if present in the same folder. There exists also a `no_layout_not_found` file if you don't want a `not_found` file to be applied layouts but you do in `page` component.
|
|
128
132
|
|
|
133
|
+
- `reset_layout` file (without extension) if present in the same folder as a `layout.tsx` file, will ignore previous layouts in the layout hierarchy.
|
|
134
|
+
|
|
135
|
+
- If found any `error.tsx` (or `.jsx`) page in the route hierarchy, the more nested one will be rendered in case of error in the page. Layouts are also applied to error pages if no `no_layout` or `no_layout_error` files (without extension) exists in the folder where `error.tsx` is defined.
|
|
136
|
+
|
|
129
137
|
## page_functions.ts (or `.tsx`, `.js`, `.jsx`)
|
|
130
138
|
|
|
131
139
|
`page_functions.ts` is a file for defining four diferent possible functions. These are:
|
|
@@ -841,6 +849,8 @@ The routing system is file-based and supports static routes, dynamic routes, opt
|
|
|
841
849
|
|
|
842
850
|
- If a **`no_layout`** file exists in a directory (**without extension**), the layout hierarchy is skipped, and only the page content is rendered.
|
|
843
851
|
|
|
852
|
+
- If a **`reset_layout`** file (**without extension**) exists in a directory where a `layout.tsx` file is defined, previous layouts in the hierarchy will be ignored.
|
|
853
|
+
|
|
844
854
|
### Not Found Handling
|
|
845
855
|
|
|
846
856
|
- If no `page.tsx` is found for a route, the system looks for a `not_found.tsx` file in the route hierarchy.
|
|
@@ -855,6 +865,51 @@ The routing system is file-based and supports static routes, dynamic routes, opt
|
|
|
855
865
|
|
|
856
866
|
- Layouts are applied to `not_found.tsx` pages too, unless a `no_layout` or **`no_layout_not_found`** files (**without extension**) are found in the directory in which the `not_found.tsx` page is defined, in which case layouts will not be applied to `not_found.tsx` page.
|
|
857
867
|
|
|
868
|
+
### Error Handling
|
|
869
|
+
|
|
870
|
+
- In case of error in a page, the more nested `error.tsx` (or `.jsx`) page will rendered if exists. **If it does not exist, then in production the error will be written in the console, and in development a default error page will be rendered informing about the error message and the error stack**.
|
|
871
|
+
|
|
872
|
+
- Layouts are applied to `error.tsx` pages, if no `no_layout` or `no_layout_error` files (without extension) exists in the folder where `error.tsx` is defined.
|
|
873
|
+
|
|
874
|
+
- `error.tsx` pages are **dynamically rendered**, so avoid using server components (async functions) and fetching data in their body definition because this will delay the rendering of the page. Use `Suspense` instead if you need to fetch data.
|
|
875
|
+
|
|
876
|
+
- There not exists a `error_functions.ts` functionality, so there is no `getProps` for error pages. Again, if you need to fetch data use `Suspense`.
|
|
877
|
+
|
|
878
|
+
- The error page receives `params`, `query`, and `error`. `error` is an object with properties `message` and `stack` which are strings.
|
|
879
|
+
|
|
880
|
+
- Example:
|
|
881
|
+
|
|
882
|
+
```typescript
|
|
883
|
+
"use client";
|
|
884
|
+
|
|
885
|
+
export default function Page({
|
|
886
|
+
error: { message, stack },
|
|
887
|
+
}: {
|
|
888
|
+
error: Error;
|
|
889
|
+
}) {
|
|
890
|
+
return (
|
|
891
|
+
<main className="flex-1 flex flex-col items-center justify-center p-4">
|
|
892
|
+
<div className="max-w-md w-full text-center space-y-6">
|
|
893
|
+
<h1 className="text-3xl font-bold text-red-600">Error</h1>
|
|
894
|
+
<p className="text-lg text-gray-700">
|
|
895
|
+
An unexpected error has occurred baby. Please try again later.
|
|
896
|
+
</p>
|
|
897
|
+
<a
|
|
898
|
+
href="/"
|
|
899
|
+
className="inline-block px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
|
900
|
+
>
|
|
901
|
+
Go to Home
|
|
902
|
+
</a>
|
|
903
|
+
</div>
|
|
904
|
+
<div className="mt-6 text-sm text-gray-500">
|
|
905
|
+
<pre className="whitespace-pre-wrap break-words">{message}</pre>
|
|
906
|
+
<pre className="whitespace-pre-wrap break-words">{stack}</pre>
|
|
907
|
+
</div>
|
|
908
|
+
</main>
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
```
|
|
912
|
+
|
|
858
913
|
## `favicons` folder
|
|
859
914
|
|
|
860
915
|
If you want to show a favicon, generate one with an online tool (e.g. [favicon.io](https://favicon.io/)), unzip the downloaded folder with the favicons, paste it in the root of the project and rename it to `favicons`. Then update your `layout` or `page` to include this in the `head` tag:
|
|
@@ -382,6 +382,18 @@ async function buildStaticPages() {
|
|
|
382
382
|
}
|
|
383
383
|
jsx = React.createElement(Layout, props, jsx);
|
|
384
384
|
jsx = { ...jsx, __modulePath: layoutPath };
|
|
385
|
+
const layoutFolderPath = path.dirname(layoutPath);
|
|
386
|
+
if (
|
|
387
|
+
getFilePathAndDynamicParams(
|
|
388
|
+
[],
|
|
389
|
+
{},
|
|
390
|
+
layoutFolderPath,
|
|
391
|
+
"reset_layout",
|
|
392
|
+
false
|
|
393
|
+
)[0]
|
|
394
|
+
) {
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
385
397
|
index++;
|
|
386
398
|
}
|
|
387
399
|
}
|
|
@@ -548,6 +560,18 @@ async function buildStaticPage(reqPath) {
|
|
|
548
560
|
}
|
|
549
561
|
jsx = React.createElement(Layout, layoutProps, jsx);
|
|
550
562
|
jsx = { ...jsx, __modulePath: layoutPath };
|
|
563
|
+
const layoutFolderPath = path.dirname(layoutPath);
|
|
564
|
+
if (
|
|
565
|
+
getFilePathAndDynamicParams(
|
|
566
|
+
[],
|
|
567
|
+
{},
|
|
568
|
+
layoutFolderPath,
|
|
569
|
+
"reset_layout",
|
|
570
|
+
false
|
|
571
|
+
)[0]
|
|
572
|
+
) {
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
551
575
|
index++;
|
|
552
576
|
}
|
|
553
577
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { use } from "react";
|
|
2
|
+
import { createFromFetch } from "react-server-dom-webpack/client";
|
|
3
|
+
import { hydrateRoot } from "react-dom/client";
|
|
4
|
+
|
|
5
|
+
const cache = new Map();
|
|
6
|
+
const route = window.location.href.replace(window.location.origin, "");
|
|
7
|
+
|
|
8
|
+
function Root() {
|
|
9
|
+
let content = cache.get(route);
|
|
10
|
+
if (!content) {
|
|
11
|
+
content = createFromFetch(
|
|
12
|
+
fetch("/____rsc_payload_error____" + route, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
error: {
|
|
19
|
+
message: window.__DINOU_ERROR_MESSAGE__ || "Unknown error",
|
|
20
|
+
stack: window.__DINOU_ERROR_STACK__ || "No stack trace available",
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
cache.set(route, content);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return use(content);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
hydrateRoot(document, <Root />);
|
|
32
|
+
|
|
33
|
+
// HMR
|
|
34
|
+
if (import.meta.hot) {
|
|
35
|
+
import.meta.hot.accept();
|
|
36
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { existsSync } = require("fs");
|
|
3
|
+
const React = require("react");
|
|
4
|
+
const {
|
|
5
|
+
getFilePathAndDynamicParams,
|
|
6
|
+
} = require("./get-file-path-and-dynamic-params");
|
|
7
|
+
|
|
8
|
+
function getErrorJSX(reqPath, query, error) {
|
|
9
|
+
const srcFolder = path.resolve(process.cwd(), "src");
|
|
10
|
+
const reqSegments = reqPath.split("/").filter(Boolean);
|
|
11
|
+
const folderPath = path.join(srcFolder, ...reqSegments);
|
|
12
|
+
let pagePath;
|
|
13
|
+
if (existsSync(folderPath)) {
|
|
14
|
+
for (const ext of [".tsx", ".ts", ".jsx", ".js"]) {
|
|
15
|
+
const candidatePath = path.join(folderPath, `error${ext}`);
|
|
16
|
+
if (existsSync(candidatePath)) {
|
|
17
|
+
pagePath = candidatePath;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let dynamicParams;
|
|
22
|
+
|
|
23
|
+
if (!pagePath) {
|
|
24
|
+
const [filePath, dParams] = getFilePathAndDynamicParams(
|
|
25
|
+
reqSegments,
|
|
26
|
+
query,
|
|
27
|
+
srcFolder,
|
|
28
|
+
"error"
|
|
29
|
+
);
|
|
30
|
+
pagePath = filePath;
|
|
31
|
+
dynamicParams = dParams ?? {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let jsx;
|
|
35
|
+
|
|
36
|
+
if (!pagePath) {
|
|
37
|
+
const [errorPath, dParams] = getFilePathAndDynamicParams(
|
|
38
|
+
reqSegments,
|
|
39
|
+
query,
|
|
40
|
+
srcFolder,
|
|
41
|
+
"error",
|
|
42
|
+
true,
|
|
43
|
+
false
|
|
44
|
+
);
|
|
45
|
+
if (errorPath) {
|
|
46
|
+
pagePath = errorPath;
|
|
47
|
+
dynamicParams = dParams ?? {};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (pagePath) {
|
|
52
|
+
const pageModule = require(pagePath);
|
|
53
|
+
const Page = pageModule.default ?? pageModule;
|
|
54
|
+
jsx = React.createElement(Page, {
|
|
55
|
+
params: dynamicParams ?? {},
|
|
56
|
+
query,
|
|
57
|
+
error,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const noLayoutErrorPath = path.join(
|
|
61
|
+
pagePath.split("\\").slice(0, -1).join("\\"),
|
|
62
|
+
`no_layout_error`
|
|
63
|
+
);
|
|
64
|
+
if (existsSync(path.resolve(process.cwd(), `${noLayoutErrorPath}`))) {
|
|
65
|
+
return jsx;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
getFilePathAndDynamicParams(
|
|
70
|
+
reqSegments,
|
|
71
|
+
query,
|
|
72
|
+
srcFolder,
|
|
73
|
+
"no_layout",
|
|
74
|
+
false
|
|
75
|
+
)[0]
|
|
76
|
+
) {
|
|
77
|
+
return jsx;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const layouts = getFilePathAndDynamicParams(
|
|
81
|
+
reqSegments,
|
|
82
|
+
query,
|
|
83
|
+
srcFolder,
|
|
84
|
+
"layout",
|
|
85
|
+
true,
|
|
86
|
+
false,
|
|
87
|
+
undefined,
|
|
88
|
+
0,
|
|
89
|
+
{},
|
|
90
|
+
true
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (layouts && Array.isArray(layouts)) {
|
|
94
|
+
let index = 0;
|
|
95
|
+
for (const [layoutPath, dParams, slots] of layouts.reverse()) {
|
|
96
|
+
const layoutModule = require(layoutPath);
|
|
97
|
+
const Layout = layoutModule.default ?? layoutModule;
|
|
98
|
+
let props = { params: dParams, query, ...slots };
|
|
99
|
+
jsx = React.createElement(Layout, props, jsx);
|
|
100
|
+
const layoutFolderPath = path.dirname(layoutPath);
|
|
101
|
+
if (
|
|
102
|
+
getFilePathAndDynamicParams(
|
|
103
|
+
[],
|
|
104
|
+
{},
|
|
105
|
+
layoutFolderPath,
|
|
106
|
+
"reset_layout",
|
|
107
|
+
false
|
|
108
|
+
)[0]
|
|
109
|
+
) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
index++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return jsx;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
getErrorJSX,
|
|
122
|
+
};
|
package/dinou/get-jsx.js
CHANGED
|
@@ -129,6 +129,18 @@ async function getJSX(reqPath, query) {
|
|
|
129
129
|
props = { ...props, ...(pageFunctionsProps?.layout ?? {}) };
|
|
130
130
|
}
|
|
131
131
|
jsx = React.createElement(Layout, props, jsx);
|
|
132
|
+
const layoutFolderPath = path.dirname(layoutPath);
|
|
133
|
+
if (
|
|
134
|
+
getFilePathAndDynamicParams(
|
|
135
|
+
[],
|
|
136
|
+
{},
|
|
137
|
+
layoutFolderPath,
|
|
138
|
+
"reset_layout",
|
|
139
|
+
false
|
|
140
|
+
)[0]
|
|
141
|
+
) {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
132
144
|
index++;
|
|
133
145
|
}
|
|
134
146
|
}
|
package/dinou/render-html.js
CHANGED
|
@@ -25,14 +25,92 @@ addHook({
|
|
|
25
25
|
|
|
26
26
|
const { renderToPipeableStream } = require("react-dom/server");
|
|
27
27
|
const { getJSX, getSSGJSX } = require("./get-jsx");
|
|
28
|
+
const { getErrorJSX } = require("./get-error-jsx");
|
|
28
29
|
const { renderJSXToClientJSX } = require("./render-jsx-to-client-jsx");
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
function formatErrorHtml(error) {
|
|
32
|
+
const message = error.message || "Unknown error";
|
|
33
|
+
const stack = error.stack
|
|
34
|
+
? error.stack.replace(/\n/g, "<br>").replace(/\s/g, " ")
|
|
35
|
+
: "No stack trace available";
|
|
36
|
+
|
|
37
|
+
return `
|
|
38
|
+
<!DOCTYPE html>
|
|
39
|
+
<html lang="en">
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="UTF-8">
|
|
42
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
43
|
+
<title>Error</title>
|
|
44
|
+
<style>
|
|
45
|
+
body {
|
|
46
|
+
font-family: Arial, sans-serif;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 20px;
|
|
49
|
+
background-color: #f8f8f8;
|
|
50
|
+
color: #333;
|
|
51
|
+
}
|
|
52
|
+
.error-container {
|
|
53
|
+
max-width: 800px;
|
|
54
|
+
margin: 0 auto;
|
|
55
|
+
background-color: #fff;
|
|
56
|
+
padding: 20px;
|
|
57
|
+
border-radius: 8px;
|
|
58
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
59
|
+
}
|
|
60
|
+
.error-title {
|
|
61
|
+
color: #d32f2f;
|
|
62
|
+
font-size: 24px;
|
|
63
|
+
margin-bottom: 10px;
|
|
64
|
+
}
|
|
65
|
+
.error-message {
|
|
66
|
+
font-size: 18px;
|
|
67
|
+
margin-bottom: 20px;
|
|
68
|
+
}
|
|
69
|
+
.error-stack {
|
|
70
|
+
background-color: #f5f5f5;
|
|
71
|
+
padding: 15px;
|
|
72
|
+
border-radius: 4px;
|
|
73
|
+
font-family: Consolas, monospace;
|
|
74
|
+
font-size: 14px;
|
|
75
|
+
overflow-x: auto;
|
|
76
|
+
}
|
|
77
|
+
.error-footer {
|
|
78
|
+
margin-top: 20px;
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
color: #666;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div class="error-container">
|
|
86
|
+
<h1 class="error-title">An Error Occurred</h1>
|
|
87
|
+
<p class="error-message">${message}</p>
|
|
88
|
+
<div class="error-stack">${stack}</div>
|
|
89
|
+
</div>
|
|
90
|
+
</body>
|
|
91
|
+
</html>
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatErrorHtmlProduction(error) {
|
|
96
|
+
const escapedMessage = JSON.stringify(`Render error: ${error.message}`);
|
|
97
|
+
const escapedStack = JSON.stringify(error.stack || "");
|
|
35
98
|
|
|
99
|
+
return `
|
|
100
|
+
<!DOCTYPE html>
|
|
101
|
+
<html>
|
|
102
|
+
<head><meta charset="utf-8"></head>
|
|
103
|
+
<body>
|
|
104
|
+
<script>
|
|
105
|
+
console.error(${escapedMessage} + "\\n" + ${escapedStack});
|
|
106
|
+
</script>
|
|
107
|
+
</body>
|
|
108
|
+
</html>
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function renderToStream(reqPath, query) {
|
|
113
|
+
try {
|
|
36
114
|
const jsx = Object.keys(query).length
|
|
37
115
|
? renderJSXToClientJSX(await getJSX(reqPath, query))
|
|
38
116
|
: getSSGJSX(reqPath) ??
|
|
@@ -40,9 +118,54 @@ async function renderToStream() {
|
|
|
40
118
|
|
|
41
119
|
const stream = renderToPipeableStream(jsx, {
|
|
42
120
|
onError(error) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
121
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const errorJSX = getErrorJSX(reqPath, query, error);
|
|
125
|
+
|
|
126
|
+
if (errorJSX === undefined) {
|
|
127
|
+
process.stdout.write(
|
|
128
|
+
isProd ? formatErrorHtmlProduction(error) : formatErrorHtml(error)
|
|
129
|
+
);
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
JSON.stringify({ error: error.message, stack: error.stack })
|
|
132
|
+
);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const errorStream = renderToPipeableStream(errorJSX, {
|
|
137
|
+
onShellReady() {
|
|
138
|
+
errorStream.pipe(process.stdout);
|
|
139
|
+
},
|
|
140
|
+
onError(err) {
|
|
141
|
+
console.error("Error rendering error JSX:", err);
|
|
142
|
+
process.stdout.write(
|
|
143
|
+
isProd
|
|
144
|
+
? formatErrorHtmlProduction(error)
|
|
145
|
+
: formatErrorHtml(error)
|
|
146
|
+
);
|
|
147
|
+
process.stderr.write(
|
|
148
|
+
JSON.stringify({ error: error.message, stack: error.stack })
|
|
149
|
+
);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
},
|
|
152
|
+
bootstrapScripts: ["/error.js"],
|
|
153
|
+
bootstrapScriptContent: `window.__DINOU_ERROR_MESSAGE__=${JSON.stringify(
|
|
154
|
+
error.message || "Unknown error"
|
|
155
|
+
)};window.__DINOU_ERROR_STACK__=${JSON.stringify(
|
|
156
|
+
error.stack || "No stack trace available"
|
|
157
|
+
)};`,
|
|
158
|
+
});
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.error("Render error (no error.tsx?):", err);
|
|
161
|
+
process.stdout.write(
|
|
162
|
+
isProd ? formatErrorHtmlProduction(error) : formatErrorHtml(error)
|
|
163
|
+
);
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
JSON.stringify({ error: error.message, stack: error.stack })
|
|
166
|
+
);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
46
169
|
},
|
|
47
170
|
onShellReady() {
|
|
48
171
|
stream.pipe(process.stdout);
|
|
@@ -50,21 +173,41 @@ async function renderToStream() {
|
|
|
50
173
|
bootstrapScripts: ["/main.js"],
|
|
51
174
|
});
|
|
52
175
|
} catch (error) {
|
|
53
|
-
process.
|
|
176
|
+
process.stdout.write(formatErrorHtml(error));
|
|
177
|
+
process.stderr.write(
|
|
178
|
+
JSON.stringify({
|
|
179
|
+
error: error.message,
|
|
180
|
+
stack: error.stack,
|
|
181
|
+
})
|
|
182
|
+
);
|
|
54
183
|
process.exit(1);
|
|
55
184
|
}
|
|
56
185
|
}
|
|
57
186
|
|
|
187
|
+
const reqPath = process.argv[2];
|
|
188
|
+
const query = JSON.parse(process.argv[3]);
|
|
189
|
+
|
|
58
190
|
process.on("uncaughtException", (error) => {
|
|
59
|
-
process.
|
|
191
|
+
process.stdout.write(formatErrorHtml(error));
|
|
192
|
+
process.stderr.write(
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
error: error.message,
|
|
195
|
+
stack: error.stack,
|
|
196
|
+
})
|
|
197
|
+
);
|
|
60
198
|
process.exit(1);
|
|
61
199
|
});
|
|
62
200
|
|
|
63
201
|
process.on("unhandledRejection", (reason) => {
|
|
202
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
203
|
+
process.stdout.write(formatErrorHtml(error));
|
|
64
204
|
process.stderr.write(
|
|
65
|
-
JSON.stringify({
|
|
205
|
+
JSON.stringify({
|
|
206
|
+
error: error.message,
|
|
207
|
+
stack: error.stack,
|
|
208
|
+
})
|
|
66
209
|
);
|
|
67
210
|
process.exit(1);
|
|
68
211
|
});
|
|
69
212
|
|
|
70
|
-
renderToStream();
|
|
213
|
+
renderToStream(reqPath, query);
|
package/dinou/server.js
CHANGED
|
@@ -11,6 +11,7 @@ const webpackDevMiddleware = require("webpack-dev-middleware");
|
|
|
11
11
|
const webpackHotMiddleware = require("webpack-hot-middleware");
|
|
12
12
|
const webpackConfig = require(path.resolve(__dirname, "../webpack.config.js"));
|
|
13
13
|
const { getSSGJSXOrJSX } = require("./get-jsx.js");
|
|
14
|
+
const { getErrorJSX } = require("./get-error-jsx.js");
|
|
14
15
|
const addHook = require("./asset-require-hook.js");
|
|
15
16
|
webpackRegister();
|
|
16
17
|
const babelRegister = require("@babel/register");
|
|
@@ -37,6 +38,7 @@ addHook({
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
const app = express();
|
|
41
|
+
app.use(express.json());
|
|
40
42
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
41
43
|
|
|
42
44
|
if (isDevelopment) {
|
|
@@ -57,7 +59,26 @@ app.get(/^\/____rsc_payload____\/.*\/?$/, async (req, res) => {
|
|
|
57
59
|
const reqPath = (
|
|
58
60
|
req.path.endsWith("/") ? req.path : req.path + "/"
|
|
59
61
|
).replace("/____rsc_payload____", "");
|
|
60
|
-
jsx = await getSSGJSXOrJSX(reqPath, { ...req.query });
|
|
62
|
+
const jsx = await getSSGJSXOrJSX(reqPath, { ...req.query });
|
|
63
|
+
const manifest = readFileSync(
|
|
64
|
+
path.resolve(process.cwd(), "____public____/react-client-manifest.json"),
|
|
65
|
+
"utf8"
|
|
66
|
+
);
|
|
67
|
+
const moduleMap = JSON.parse(manifest);
|
|
68
|
+
const { pipe } = renderToPipeableStream(jsx, moduleMap);
|
|
69
|
+
pipe(res);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("Error rendering RSC:", error);
|
|
72
|
+
res.status(500).send("Internal Server Error");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
const reqPath = (
|
|
79
|
+
req.path.endsWith("/") ? req.path : req.path + "/"
|
|
80
|
+
).replace("/____rsc_payload_error____", "");
|
|
81
|
+
const jsx = await getErrorJSX(reqPath, { ...req.query }, req.body.error);
|
|
61
82
|
const manifest = readFileSync(
|
|
62
83
|
path.resolve(process.cwd(), "____public____/react-client-manifest.json"),
|
|
63
84
|
"utf8"
|
|
@@ -74,11 +95,15 @@ app.get(/^\/____rsc_payload____\/.*\/?$/, async (req, res) => {
|
|
|
74
95
|
// Render HTML via child process, returning a stream
|
|
75
96
|
function renderAppToHtml(reqPath, paramsString) {
|
|
76
97
|
return new Promise((resolve, reject) => {
|
|
77
|
-
const child = spawn(
|
|
78
|
-
|
|
79
|
-
reqPath,
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
const child = spawn(
|
|
99
|
+
"node",
|
|
100
|
+
[path.resolve(__dirname, "render-html.js"), reqPath, paramsString],
|
|
101
|
+
{
|
|
102
|
+
env: {
|
|
103
|
+
...process.env,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
);
|
|
82
107
|
|
|
83
108
|
let errorOutput = "";
|
|
84
109
|
child.stderr.on("data", (data) => {
|
package/package.json
CHANGED
package/webpack.config.js
CHANGED
|
@@ -29,10 +29,14 @@ module.exports = {
|
|
|
29
29
|
isDevelopment && "webpack-hot-middleware/client?reload=true",
|
|
30
30
|
path.resolve(__dirname, "./dinou/client.jsx"),
|
|
31
31
|
].filter(Boolean),
|
|
32
|
+
error: [
|
|
33
|
+
isDevelopment && "webpack-hot-middleware/client?reload=true",
|
|
34
|
+
path.resolve(__dirname, "./dinou/client-error.jsx"),
|
|
35
|
+
].filter(Boolean),
|
|
32
36
|
},
|
|
33
37
|
output: {
|
|
34
38
|
path: path.resolve(process.cwd(), "./____public____"),
|
|
35
|
-
filename: "
|
|
39
|
+
filename: "[name].js",
|
|
36
40
|
publicPath: "/",
|
|
37
41
|
clean: true,
|
|
38
42
|
},
|