@parcel/rsc 2.13.1-canary.3399 → 2.13.1-canary.3401

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/lib/node.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  /* @jsxRuntime automatic */ import type { IncomingMessage, ServerResponse } from 'http';
2
+ import type { ReactNode } from 'react';
2
3
  import { Readable } from 'stream';
3
4
  import { RSCToHTMLOptions, RSCOptions } from './server';
4
5
  export declare function renderRSC(root: any, options?: RSCOptions): Readable;
5
6
  export declare function renderHTML(root: any, options?: RSCToHTMLOptions): Promise<Readable>;
6
- export declare function renderRequest(request: IncomingMessage, response: ServerResponse, root: any, options?: RSCToHTMLOptions): Promise<void>;
7
+ export interface RenderRequestOptions extends RSCToHTMLOptions {
8
+ renderError?: (err: Error) => ReactNode;
9
+ }
10
+ export declare function renderRequest(request: IncomingMessage, response: ServerResponse, root: any, options?: RenderRequestOptions): Promise<void>;
7
11
  export declare function callAction(request: IncomingMessage, id: string | null | undefined): Promise<{
8
12
  result: any;
9
13
  }>;
package/lib/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /* @jsxRuntime automatic */ import type { ErrorInfo } from 'react-dom/client';
2
- import { ComponentType } from 'react' with {
2
+ import { ComponentType, ReactNode } from 'react' with {
3
3
  env: 'react-client'
4
4
  };
5
5
  export interface RSCOptions {
@@ -22,11 +22,13 @@ export interface RSCToHTMLOptions {
22
22
  temporaryReferences?: any;
23
23
  onError?: (error: unknown, errorInfo?: ErrorInfo) => string | void;
24
24
  }
25
- export declare function renderRSCToHTML(root: any, options?: RSCToHTMLOptions): Promise<ReadableStream>;
25
+ export declare function renderHTML(root: any, options?: RSCToHTMLOptions): Promise<ReadableStream>;
26
26
  export interface RenderRequestOptions extends RSCToHTMLOptions {
27
27
  headers?: HeadersInit;
28
+ renderError?: (err: Error) => ReactNode;
28
29
  }
29
30
  export declare function renderRequest(request: Request, root: any, options?: RenderRequestOptions): Promise<Response>;
30
31
  export declare function callAction(request: Request, id: string | null | undefined): Promise<{
31
32
  result: any;
32
33
  }>;
34
+ export declare function renderDevError(error: Error, options: RSCToHTMLOptions): Promise<ReadableStream | string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/rsc",
3
- "version": "2.13.1-canary.3399+53633df12",
3
+ "version": "2.13.1-canary.3401+b50bc79cb",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -41,5 +41,5 @@
41
41
  "react": "^19",
42
42
  "react-dom": "^19"
43
43
  },
44
- "gitHead": "53633df12ad9bba5d6ed5360e935fde8de0342fe"
44
+ "gitHead": "b50bc79cbc94fb83381e3dcfba0f8cab936f94b0"
45
45
  }
package/src/node.tsx CHANGED
@@ -1,32 +1,55 @@
1
1
  /* @jsxRuntime automatic */
2
2
  import type { IncomingMessage, ServerResponse } from 'http';
3
3
  import type { ReadableStream as NodeReadableStream } from 'stream/web';
4
+ import type { ReactNode } from 'react';
4
5
 
5
6
  import {Readable} from 'stream';
6
7
  import {renderToReadableStream, createTemporaryReferenceSet} from 'react-server-dom-parcel/server.edge';
7
- import {RSCToHTMLOptions, RSCOptions, renderRSCToHTML as renderRSCToHTMLBase, callAction as callActionBase} from './server';
8
+ import {RSCToHTMLOptions, RSCOptions, renderHTML as renderHTMLBase, callAction as callActionBase, renderDevError} from './server';
8
9
 
9
10
  export function renderRSC(root: any, options?: RSCOptions): Readable {
10
11
  return Readable.fromWeb(renderToReadableStream(root, options) as NodeReadableStream);
11
12
  }
12
13
 
13
14
  export async function renderHTML(root: any, options?: RSCToHTMLOptions): Promise<Readable> {
14
- let htmlStream = await renderRSCToHTMLBase(root, options);
15
+ let htmlStream = await renderHTMLBase(root, options);
15
16
  return Readable.fromWeb(htmlStream as NodeReadableStream);
16
17
  }
17
18
 
18
- const temporaryReferencesSymbol = Symbol.for('temporaryReferences')
19
+ const temporaryReferencesSymbol = Symbol.for('temporaryReferences');
19
20
 
20
- export async function renderRequest(request: IncomingMessage, response: ServerResponse, root: any, options?: RSCToHTMLOptions): Promise<void> {
21
+ export interface RenderRequestOptions extends RSCToHTMLOptions {
22
+ renderError?: (err: Error) => ReactNode
23
+ }
24
+
25
+ export async function renderRequest(request: IncomingMessage, response: ServerResponse, root: any, options?: RenderRequestOptions): Promise<void> {
21
26
  options = {
22
27
  ...options,
23
28
  temporaryReferences: options?.temporaryReferences ?? (request as any)[temporaryReferencesSymbol]
24
29
  };
25
30
 
26
31
  if (request.headers.accept?.includes('text/html')) {
27
- let html = await renderHTML(root, options);
28
- response.setHeader('Content-Type', 'text/html');
29
- html.pipe(response);
32
+ try {
33
+ let html = await renderHTML(root, options);
34
+ response.setHeader('Content-Type', 'text/html');
35
+ html.pipe(response);
36
+ } catch (err) {
37
+ response.statusCode = 500;
38
+ let error = err instanceof Error ? err : new Error(String(err));
39
+ let renderedError;
40
+ try {
41
+ renderedError = options?.renderError
42
+ ? await renderHTMLBase(options.renderError(error), options)
43
+ : await renderDevError(error, options);
44
+ } catch {
45
+ renderedError = '<h1>Something went wrong!</h1>';
46
+ }
47
+ if (typeof renderedError === 'string') {
48
+ response.end(renderedError);
49
+ } else {
50
+ Readable.fromWeb(renderedError as NodeReadableStream).pipe(response);
51
+ }
52
+ }
30
53
  } else {
31
54
  response.setHeader('Content-Type', 'text/x-component');
32
55
  renderRSC(root, options).pipe(response);
package/src/server.tsx CHANGED
@@ -36,7 +36,7 @@ export interface RSCToHTMLOptions {
36
36
  onError?: (error: unknown, errorInfo?: ErrorInfo) => string | void;
37
37
  }
38
38
 
39
- export async function renderRSCToHTML(root: any, options?: RSCToHTMLOptions): Promise<ReadableStream> {
39
+ export async function renderHTML(root: any, options?: RSCToHTMLOptions): Promise<ReadableStream> {
40
40
  let rscStream = renderToReadableStream(root, options);
41
41
 
42
42
  // Use client react to render the RSC payload to HTML.
@@ -50,14 +50,15 @@ export async function renderRSCToHTML(root: any, options?: RSCToHTMLOptions): Pr
50
50
 
51
51
  let htmlStream = await renderHTMLToReadableStream(<Content />, {
52
52
  ...options,
53
- bootstrapScriptContent: (options?.component as any)?.bootstrapScript
53
+ bootstrapScriptContent: (options?.component as any)?.bootstrapScript,
54
54
  });
55
55
 
56
56
  return htmlStream.pipeThrough(injectRSCPayload(s2));
57
57
  }
58
58
 
59
59
  export interface RenderRequestOptions extends RSCToHTMLOptions {
60
- headers?: HeadersInit
60
+ headers?: HeadersInit,
61
+ renderError?: (err: Error) => ReactNode
61
62
  }
62
63
 
63
64
  const temporaryReferencesSymbol = Symbol.for('temporaryReferences')
@@ -69,13 +70,32 @@ export async function renderRequest(request: Request, root: any, options?: Rende
69
70
  };
70
71
 
71
72
  if (request.headers.get('Accept')?.includes('text/html')) {
72
- let html = await renderRSCToHTML(root, options);
73
- return new Response(html, {
74
- headers: {
75
- ...options?.headers,
76
- 'Content-Type': 'text/html'
73
+ try {
74
+ let html = await renderHTML(root, options);
75
+ return new Response(html, {
76
+ headers: {
77
+ ...options?.headers,
78
+ 'Content-Type': 'text/html'
79
+ }
80
+ });
81
+ } catch (err) {
82
+ let error = err instanceof Error ? err : new Error(String(err));
83
+ let res;
84
+ try {
85
+ res = options?.renderError
86
+ ? await renderHTML(options.renderError(error), options)
87
+ : await renderDevError(error, options);
88
+ } catch {
89
+ res = '<h1>Something went wrong!</h1>';
77
90
  }
78
- });
91
+ return new Response(res, {
92
+ status: 500,
93
+ headers: {
94
+ ...options?.headers,
95
+ 'Content-Type': 'text/html'
96
+ }
97
+ });
98
+ }
79
99
  } else {
80
100
  let rscStream = renderToReadableStream(root, options);
81
101
  return new Response(rscStream, {
@@ -116,3 +136,37 @@ export async function callAction(request: Request, id: string | null | undefined
116
136
  return {result};
117
137
  }
118
138
  }
139
+
140
+ export async function renderDevError(error: Error, options: RSCToHTMLOptions): Promise<ReadableStream | string> {
141
+ if (process.env.NODE_ENV !== 'production') {
142
+ let content = (
143
+ <html>
144
+ <meta charSet="utf-8" />
145
+ <title>Error</title>
146
+ <body>
147
+ <noscript>
148
+ <h1>Error: {error.message}</h1>
149
+ <pre>{error.stack}</pre>
150
+ </noscript>
151
+ </body>
152
+ </html>
153
+ );
154
+
155
+ // Load bootstrap script containing error overlay, and re-throw server error on the client.
156
+ let bootstrapScript = (options?.component as any)?.bootstrapScript;
157
+ bootstrapScript += '.then(() => {';
158
+ bootstrapScript += `let err = new Error(${JSON.stringify(error.message)});`;
159
+ bootstrapScript += `err.stack = ${JSON.stringify(error.stack)};`;
160
+ bootstrapScript += 'throw err;';
161
+ bootstrapScript += '})';
162
+
163
+ return await renderHTML(content, {
164
+ ...options,
165
+ component: {
166
+ bootstrapScript
167
+ } as any
168
+ });
169
+ } else {
170
+ return '<h1>Something went wrong!</h1>';
171
+ }
172
+ }