@parcel/rsc 3.0.0-alpha.0 → 3.0.0-alpha.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/package.json +6 -3
- package/src/client.tsx +34 -11
- package/src/node.tsx +41 -15
- package/src/runtime-helpers.js +5 -7
- package/src/server.tsx +57 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parcel/rsc",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
"url": "https://github.com/parcel-bundler/parcel.git"
|
|
15
15
|
},
|
|
16
16
|
"sideEffects": false,
|
|
17
|
-
"files": [
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
18
21
|
"exports": {
|
|
19
22
|
".": {
|
|
20
23
|
"types": "./src/ssg.d.ts"
|
|
@@ -53,4 +56,4 @@
|
|
|
53
56
|
"react": "^19.1.0",
|
|
54
57
|
"react-dom": "^19.1.0"
|
|
55
58
|
}
|
|
56
|
-
}
|
|
59
|
+
}
|
package/src/client.tsx
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* @jsxRuntime automatic */
|
|
2
2
|
import {ReactNode, startTransition, useInsertionEffect} from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createFromReadableStream,
|
|
5
|
+
createFromFetch,
|
|
6
|
+
encodeReply,
|
|
7
|
+
setServerCallback as setReactServerCallback,
|
|
8
|
+
createTemporaryReferenceSet,
|
|
9
|
+
} from 'react-server-dom-parcel/client';
|
|
4
10
|
import {rscStream} from 'rsc-html-stream/client';
|
|
5
11
|
import {hydrateRoot, HydrationOptions, Root} from 'react-dom/client';
|
|
6
12
|
|
|
@@ -11,7 +17,7 @@ export function setServerCallback(cb: CallServerCallback): void {
|
|
|
11
17
|
|
|
12
18
|
// Stream in initial RSC payload embedded in the HTML.
|
|
13
19
|
let initialRSCPayload: Promise<ReactNode>;
|
|
14
|
-
function RSCRoot({value, cb}: {value?: ReactNode
|
|
20
|
+
function RSCRoot({value, cb}: {value?: ReactNode; cb?: () => void}) {
|
|
15
21
|
initialRSCPayload ??= createFromReadableStream<ReactNode>(rscStream);
|
|
16
22
|
useInsertionEffect(() => {
|
|
17
23
|
cb?.();
|
|
@@ -20,11 +26,13 @@ function RSCRoot({value, cb}: {value?: ReactNode, cb?: () => void}) {
|
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export interface HydrateOptions extends HydrationOptions {
|
|
23
|
-
callServer?: CallServerCallback
|
|
24
|
-
onHmrReload?: () => void
|
|
29
|
+
callServer?: CallServerCallback;
|
|
30
|
+
onHmrReload?: () => void;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
export function hydrate(
|
|
33
|
+
export function hydrate(
|
|
34
|
+
options?: HydrateOptions,
|
|
35
|
+
): (value: ReactNode, cb?: () => void) => void {
|
|
28
36
|
if (options?.callServer) {
|
|
29
37
|
setServerCallback(options.callServer);
|
|
30
38
|
}
|
|
@@ -43,16 +51,27 @@ export function hydrate(options?: HydrateOptions): (value: ReactNode, cb?: () =>
|
|
|
43
51
|
|
|
44
52
|
return (value: ReactNode, cb?: () => void) => {
|
|
45
53
|
startTransition(() => {
|
|
46
|
-
root?.render(
|
|
54
|
+
root?.render(
|
|
55
|
+
<RSCRoot
|
|
56
|
+
value={value}
|
|
57
|
+
cb={() => {
|
|
58
|
+
cb?.();
|
|
59
|
+
cb = undefined;
|
|
60
|
+
}}
|
|
61
|
+
/>,
|
|
62
|
+
);
|
|
47
63
|
});
|
|
48
64
|
};
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
export interface RSCRequestInit extends Omit<RequestInit, 'body'> {
|
|
52
|
-
body?: any
|
|
68
|
+
body?: any;
|
|
53
69
|
}
|
|
54
70
|
|
|
55
|
-
export async function fetchRSC<T>(
|
|
71
|
+
export async function fetchRSC<T>(
|
|
72
|
+
url: string | URL | Request,
|
|
73
|
+
options?: RSCRequestInit,
|
|
74
|
+
): Promise<T> {
|
|
56
75
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
57
76
|
const response = fetch(url, {
|
|
58
77
|
...options,
|
|
@@ -60,9 +79,13 @@ export async function fetchRSC<T>(url: string | URL | Request, options?: RSCRequ
|
|
|
60
79
|
Accept: 'text/x-component',
|
|
61
80
|
...options?.headers,
|
|
62
81
|
},
|
|
63
|
-
body:
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
body:
|
|
83
|
+
options && 'body' in options
|
|
84
|
+
? await encodeReply(options.body, {
|
|
85
|
+
temporaryReferences,
|
|
86
|
+
signal: options?.signal,
|
|
87
|
+
})
|
|
88
|
+
: undefined,
|
|
66
89
|
});
|
|
67
90
|
|
|
68
91
|
return createFromFetch<T>(response, {temporaryReferences});
|
package/src/node.tsx
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
/* @jsxRuntime automatic */
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
2
|
+
import type {IncomingMessage, ServerResponse} from 'http';
|
|
3
|
+
import type {ReadableStream as NodeReadableStream} from 'stream/web';
|
|
4
|
+
import type {ReactNode} from 'react';
|
|
5
5
|
|
|
6
6
|
import {Readable} from 'stream';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
renderToReadableStream,
|
|
9
|
+
createTemporaryReferenceSet,
|
|
10
|
+
} from 'react-server-dom-parcel/server.edge';
|
|
11
|
+
import {
|
|
12
|
+
RSCToHTMLOptions,
|
|
13
|
+
RSCOptions,
|
|
14
|
+
renderHTML as renderHTMLBase,
|
|
15
|
+
callAction as callActionBase,
|
|
16
|
+
renderDevError,
|
|
17
|
+
} from './server';
|
|
9
18
|
|
|
10
19
|
export function renderRSC(root: any, options?: RSCOptions): Readable {
|
|
11
|
-
return Readable.fromWeb(
|
|
20
|
+
return Readable.fromWeb(
|
|
21
|
+
renderToReadableStream(root, options) as NodeReadableStream,
|
|
22
|
+
);
|
|
12
23
|
}
|
|
13
24
|
|
|
14
|
-
export async function renderHTML(
|
|
25
|
+
export async function renderHTML(
|
|
26
|
+
root: any,
|
|
27
|
+
options?: RSCToHTMLOptions,
|
|
28
|
+
): Promise<Readable> {
|
|
15
29
|
let htmlStream = await renderHTMLBase(root, options);
|
|
16
30
|
return Readable.fromWeb(htmlStream as NodeReadableStream);
|
|
17
31
|
}
|
|
@@ -19,15 +33,22 @@ export async function renderHTML(root: any, options?: RSCToHTMLOptions): Promise
|
|
|
19
33
|
const temporaryReferencesSymbol = Symbol.for('temporaryReferences');
|
|
20
34
|
|
|
21
35
|
export interface RenderRequestOptions extends RSCToHTMLOptions {
|
|
22
|
-
renderError?: (err: Error) => ReactNode
|
|
36
|
+
renderError?: (err: Error) => ReactNode;
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
export async function renderRequest(
|
|
39
|
+
export async function renderRequest(
|
|
40
|
+
request: IncomingMessage,
|
|
41
|
+
response: ServerResponse,
|
|
42
|
+
root: any,
|
|
43
|
+
options?: RenderRequestOptions,
|
|
44
|
+
): Promise<void> {
|
|
26
45
|
options = {
|
|
27
46
|
...options,
|
|
28
|
-
temporaryReferences:
|
|
47
|
+
temporaryReferences:
|
|
48
|
+
options?.temporaryReferences ??
|
|
49
|
+
(request as any)[temporaryReferencesSymbol],
|
|
29
50
|
};
|
|
30
|
-
|
|
51
|
+
|
|
31
52
|
if (request.headers.accept?.includes('text/html')) {
|
|
32
53
|
try {
|
|
33
54
|
let html = await renderHTML(root, options);
|
|
@@ -38,7 +59,7 @@ export async function renderRequest(request: IncomingMessage, response: ServerRe
|
|
|
38
59
|
let error = err instanceof Error ? err : new Error(String(err));
|
|
39
60
|
let renderedError;
|
|
40
61
|
try {
|
|
41
|
-
renderedError = options?.renderError
|
|
62
|
+
renderedError = options?.renderError
|
|
42
63
|
? await renderHTMLBase(options.renderError(error), options)
|
|
43
64
|
: await renderDevError(error, options);
|
|
44
65
|
} catch {
|
|
@@ -56,7 +77,10 @@ export async function renderRequest(request: IncomingMessage, response: ServerRe
|
|
|
56
77
|
}
|
|
57
78
|
}
|
|
58
79
|
|
|
59
|
-
export async function callAction(
|
|
80
|
+
export async function callAction(
|
|
81
|
+
request: IncomingMessage,
|
|
82
|
+
id: string | null | undefined,
|
|
83
|
+
): Promise<{result: any}> {
|
|
60
84
|
(request as any)[temporaryReferencesSymbol] ??= createTemporaryReferenceSet();
|
|
61
85
|
|
|
62
86
|
let req = new Request('http://localhost' + request.url, {
|
|
@@ -64,9 +88,11 @@ export async function callAction(request: IncomingMessage, id: string | null | u
|
|
|
64
88
|
headers: request.headers as any,
|
|
65
89
|
body: Readable.toWeb(request) as ReadableStream,
|
|
66
90
|
// @ts-ignore
|
|
67
|
-
duplex: 'half'
|
|
91
|
+
duplex: 'half',
|
|
68
92
|
});
|
|
69
93
|
|
|
70
|
-
(req as any)[temporaryReferencesSymbol] = (request as any)[
|
|
94
|
+
(req as any)[temporaryReferencesSymbol] = (request as any)[
|
|
95
|
+
temporaryReferencesSymbol
|
|
96
|
+
];
|
|
71
97
|
return callActionBase(req, id);
|
|
72
98
|
}
|
package/src/runtime-helpers.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
/* eslint-env browser */
|
|
2
|
-
/* eslint-disable react/display-name */
|
|
3
|
-
|
|
4
1
|
'use parcel-rsc-runtime';
|
|
5
2
|
|
|
6
3
|
import {createElement, Fragment} from 'react';
|
|
7
4
|
|
|
8
5
|
export function stylesheetResource(href) {
|
|
9
6
|
return createElement('link', {
|
|
7
|
+
key: href,
|
|
10
8
|
rel: 'stylesheet',
|
|
11
9
|
href,
|
|
12
10
|
precedence: 'default',
|
|
@@ -14,16 +12,16 @@ export function stylesheetResource(href) {
|
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
export function modulePreloadResource(href) {
|
|
17
|
-
return createElement('link', {rel: 'modulepreload', href});
|
|
15
|
+
return createElement('link', {key: href, rel: 'modulepreload', href});
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
export function scriptPreloadResource(href) {
|
|
21
|
-
return createElement('link', {rel: 'preload', as: 'script', href});
|
|
19
|
+
return createElement('link', {key: href, rel: 'preload', as: 'script', href});
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export function wrapClientReferenceWithResources(Reference, resources) {
|
|
25
23
|
return props =>
|
|
26
|
-
createElement(Fragment, null,
|
|
24
|
+
createElement(Fragment, null, resources, createElement(Reference, props));
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
export function createResourcesProxy(
|
|
@@ -93,7 +91,7 @@ function createResourcesValueProxy(value, resources, bootstrapScript) {
|
|
|
93
91
|
apply(target, thisArg, args) {
|
|
94
92
|
let result = Reflect.apply(target, thisArg, args);
|
|
95
93
|
return isReactComponent
|
|
96
|
-
? createElement(Fragment, null,
|
|
94
|
+
? createElement(Fragment, null, resources, result)
|
|
97
95
|
: result;
|
|
98
96
|
},
|
|
99
97
|
});
|
package/src/server.tsx
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
/* @jsxRuntime automatic */
|
|
2
|
-
import type {
|
|
2
|
+
import type {ErrorInfo} from 'react-dom/client';
|
|
3
3
|
|
|
4
4
|
// Server dependencies.
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
renderToReadableStream,
|
|
7
|
+
loadServerAction,
|
|
8
|
+
decodeReply,
|
|
9
|
+
decodeAction,
|
|
10
|
+
createTemporaryReferenceSet,
|
|
11
|
+
} from 'react-server-dom-parcel/server.edge';
|
|
6
12
|
import {injectRSCPayload} from 'rsc-html-stream/server';
|
|
7
13
|
|
|
8
14
|
// Client dependencies, used for SSR.
|
|
9
15
|
// These must run in the same environment as client components (e.g. same instance of React).
|
|
10
|
-
import {createFromReadableStream} from 'react-server-dom-parcel/client.edge' with {
|
|
11
|
-
|
|
16
|
+
import {createFromReadableStream} from 'react-server-dom-parcel/client.edge' with {
|
|
17
|
+
env: 'react-client',
|
|
18
|
+
};
|
|
19
|
+
import {renderToReadableStream as renderHTMLToReadableStream} from 'react-dom/server.edge' with {
|
|
20
|
+
env: 'react-client',
|
|
21
|
+
};
|
|
12
22
|
import {ComponentType, ReactNode} from 'react' with {env: 'react-client'};
|
|
13
23
|
|
|
14
24
|
export interface RSCOptions {
|
|
15
25
|
// environmentName?: string | (() => string),
|
|
16
26
|
// filterStackFrame?: (url: string, functionName: string) => boolean,
|
|
17
|
-
identifierPrefix?: string
|
|
18
|
-
signal?: AbortSignal
|
|
19
|
-
temporaryReferences?: any
|
|
20
|
-
onError?: (error: unknown) => void
|
|
21
|
-
onPostpone?: (reason: string) => void
|
|
27
|
+
identifierPrefix?: string;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
temporaryReferences?: any;
|
|
30
|
+
onError?: (error: unknown) => void;
|
|
31
|
+
onPostpone?: (reason: string) => void;
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
export function renderRSC(root: any, options?: RSCOptions): ReadableStream {
|
|
@@ -26,17 +36,20 @@ export function renderRSC(root: any, options?: RSCOptions): ReadableStream {
|
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
export interface RSCToHTMLOptions {
|
|
29
|
-
component?: ComponentType
|
|
39
|
+
component?: ComponentType;
|
|
30
40
|
identifierPrefix?: string;
|
|
31
41
|
namespaceURI?: string;
|
|
32
42
|
nonce?: string;
|
|
33
43
|
progressiveChunkSize?: number;
|
|
34
44
|
signal?: AbortSignal;
|
|
35
|
-
temporaryReferences?: any
|
|
45
|
+
temporaryReferences?: any;
|
|
36
46
|
onError?: (error: unknown, errorInfo?: ErrorInfo) => string | void;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
export async function renderHTML(
|
|
49
|
+
export async function renderHTML(
|
|
50
|
+
root: any,
|
|
51
|
+
options?: RSCToHTMLOptions,
|
|
52
|
+
): Promise<ReadableStream> {
|
|
40
53
|
let rscStream = renderToReadableStream(root, options);
|
|
41
54
|
|
|
42
55
|
// Use client react to render the RSC payload to HTML.
|
|
@@ -57,16 +70,22 @@ export async function renderHTML(root: any, options?: RSCToHTMLOptions): Promise
|
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
export interface RenderRequestOptions extends RSCToHTMLOptions {
|
|
60
|
-
headers?: HeadersInit
|
|
61
|
-
renderError?: (err: Error) => ReactNode
|
|
73
|
+
headers?: HeadersInit;
|
|
74
|
+
renderError?: (err: Error) => ReactNode;
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
const temporaryReferencesSymbol = Symbol.for('temporaryReferences')
|
|
77
|
+
const temporaryReferencesSymbol = Symbol.for('temporaryReferences');
|
|
65
78
|
|
|
66
|
-
export async function renderRequest(
|
|
79
|
+
export async function renderRequest(
|
|
80
|
+
request: Request,
|
|
81
|
+
root: any,
|
|
82
|
+
options?: RenderRequestOptions,
|
|
83
|
+
): Promise<Response> {
|
|
67
84
|
options = {
|
|
68
85
|
...options,
|
|
69
|
-
temporaryReferences:
|
|
86
|
+
temporaryReferences:
|
|
87
|
+
options?.temporaryReferences ??
|
|
88
|
+
(request as any)[temporaryReferencesSymbol],
|
|
70
89
|
};
|
|
71
90
|
|
|
72
91
|
if (request.headers.get('Accept')?.includes('text/html')) {
|
|
@@ -75,14 +94,14 @@ export async function renderRequest(request: Request, root: any, options?: Rende
|
|
|
75
94
|
return new Response(html, {
|
|
76
95
|
headers: {
|
|
77
96
|
...options?.headers,
|
|
78
|
-
'Content-Type': 'text/html'
|
|
79
|
-
}
|
|
97
|
+
'Content-Type': 'text/html',
|
|
98
|
+
},
|
|
80
99
|
});
|
|
81
100
|
} catch (err) {
|
|
82
101
|
let error = err instanceof Error ? err : new Error(String(err));
|
|
83
102
|
let res;
|
|
84
103
|
try {
|
|
85
|
-
res = options?.renderError
|
|
104
|
+
res = options?.renderError
|
|
86
105
|
? await renderHTML(options.renderError(error), options)
|
|
87
106
|
: await renderDevError(error, options);
|
|
88
107
|
} catch {
|
|
@@ -92,8 +111,8 @@ export async function renderRequest(request: Request, root: any, options?: Rende
|
|
|
92
111
|
status: 500,
|
|
93
112
|
headers: {
|
|
94
113
|
...options?.headers,
|
|
95
|
-
'Content-Type': 'text/html'
|
|
96
|
-
}
|
|
114
|
+
'Content-Type': 'text/html',
|
|
115
|
+
},
|
|
97
116
|
});
|
|
98
117
|
}
|
|
99
118
|
} else {
|
|
@@ -101,22 +120,27 @@ export async function renderRequest(request: Request, root: any, options?: Rende
|
|
|
101
120
|
return new Response(rscStream, {
|
|
102
121
|
headers: {
|
|
103
122
|
...options?.headers,
|
|
104
|
-
'Content-Type': 'text/x-component'
|
|
105
|
-
}
|
|
123
|
+
'Content-Type': 'text/x-component',
|
|
124
|
+
},
|
|
106
125
|
});
|
|
107
126
|
}
|
|
108
127
|
}
|
|
109
128
|
|
|
110
|
-
export async function callAction(
|
|
129
|
+
export async function callAction(
|
|
130
|
+
request: Request,
|
|
131
|
+
id: string | null | undefined,
|
|
132
|
+
): Promise<{result: any}> {
|
|
111
133
|
(request as any)[temporaryReferencesSymbol] ??= createTemporaryReferenceSet();
|
|
112
134
|
|
|
113
135
|
if (id) {
|
|
114
136
|
let action = await loadServerAction(id);
|
|
115
|
-
let body = request.headers
|
|
137
|
+
let body = request.headers
|
|
138
|
+
.get('content-type')
|
|
139
|
+
?.includes('multipart/form-data')
|
|
116
140
|
? await request.formData()
|
|
117
141
|
: await request.text();
|
|
118
142
|
let args = await decodeReply<any[]>(body, {
|
|
119
|
-
temporaryReferences: (request as any)[temporaryReferencesSymbol]
|
|
143
|
+
temporaryReferences: (request as any)[temporaryReferencesSymbol],
|
|
120
144
|
});
|
|
121
145
|
|
|
122
146
|
let result = action.apply(null, args);
|
|
@@ -137,7 +161,10 @@ export async function callAction(request: Request, id: string | null | undefined
|
|
|
137
161
|
}
|
|
138
162
|
}
|
|
139
163
|
|
|
140
|
-
export async function renderDevError(
|
|
164
|
+
export async function renderDevError(
|
|
165
|
+
error: Error,
|
|
166
|
+
options: RSCToHTMLOptions,
|
|
167
|
+
): Promise<ReadableStream | string> {
|
|
141
168
|
if (process.env.NODE_ENV !== 'production') {
|
|
142
169
|
let content = (
|
|
143
170
|
<html>
|
|
@@ -163,8 +190,8 @@ export async function renderDevError(error: Error, options: RSCToHTMLOptions): P
|
|
|
163
190
|
return await renderHTML(content, {
|
|
164
191
|
...options,
|
|
165
192
|
component: {
|
|
166
|
-
bootstrapScript
|
|
167
|
-
} as any
|
|
193
|
+
bootstrapScript,
|
|
194
|
+
} as any,
|
|
168
195
|
});
|
|
169
196
|
} else {
|
|
170
197
|
return '<h1>Something went wrong!</h1>';
|