main
js 130 lines 3.67 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
11 import type {DebugChannel, Response} from 'react-client/src/ReactFlightClient';
12 import type {Readable} from 'stream';
13
14 import {
15 createResponse,
16 createStreamState,
17 getRoot,
18 reportGlobalError,
19 processStringChunk,
20 processBinaryChunk,
21 close,
22 } from 'react-client/src/ReactFlightClient';
23
24 export * from './ReactFlightDOMClientEdge';
25
26 function findSourceMapURL(filename: string, environmentName: string) {
27 const devServer = parcelRequire.meta.devServer;
28 if (devServer != null) {
29 const qs = new URLSearchParams();
30 qs.set('filename', filename);
31 qs.set('env', environmentName);
32 return devServer + '/__parcel_source_map?' + qs.toString();
33 }
34 return null;
35 }
36
37 function noServerCall() {
38 throw new Error(
39 'Server Functions cannot be called during initial render. ' +
40 'This would create a fetch waterfall. Try to use a Server Component ' +
41 'to pass data to Client Components instead.',
42 );
43 }
44
45 type EncodeFormActionCallback = <A>(
46 id: any,
47 args: Promise<A>,
48 ) => ReactCustomFormAction;
49
50 export type Options = {
51 nonce?: string,
52 encodeFormAction?: EncodeFormActionCallback,
53 unstable_allowPartialStream?: boolean,
54 replayConsoleLogs?: boolean,
55 environmentName?: string,
56 startTime?: number,
57 endTime?: number,
58 // For the Node.js client we only support a single-direction debug channel.
59 debugChannel?: Readable,
60 };
61
62 function startReadingFromStream(
63 response: Response,
64 stream: Readable,
65 onEnd: () => void,
66 ): void {
67 const streamState = createStreamState(response, stream);
68
69 stream.on('data', chunk => {
70 if (typeof chunk === 'string') {
71 processStringChunk(response, streamState, chunk);
72 } else {
73 processBinaryChunk(response, streamState, chunk);
74 }
75 });
76
77 stream.on('error', error => {
78 reportGlobalError(response, error);
79 });
80
81 stream.on('end', onEnd);
82 }
83
84 export function createFromNodeStream<T>(
85 stream: Readable,
86 options?: Options,
87 ): Thenable<T> {
88 const debugChannel: void | DebugChannel =
89 __DEV__ && options && options.debugChannel !== undefined
90 ? {hasReadable: true, callback: null}
91 : undefined;
92
93 const response: Response = createResponse(
94 null, // bundlerConfig
95 null, // serverReferenceConfig
96 null, // moduleLoading
97 noServerCall,
98 options ? options.encodeFormAction : undefined,
99 options && typeof options.nonce === 'string' ? options.nonce : undefined,
100 undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
101 options && options.unstable_allowPartialStream
102 ? options.unstable_allowPartialStream
103 : false,
104 __DEV__ ? findSourceMapURL : undefined,
105 __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
106 __DEV__ && options && options.environmentName
107 ? options.environmentName
108 : undefined,
109 __DEV__ && options && options.startTime != null
110 ? options.startTime
111 : undefined,
112 __DEV__ && options && options.endTime != null ? options.endTime : undefined,
113 debugChannel,
114 );
115
116 if (__DEV__ && options && options.debugChannel) {
117 let streamEndedCount = 0;
118 const handleEnd = () => {
119 if (++streamEndedCount === 2) {
120 close(response);
121 }
122 };
123 startReadingFromStream(response, options.debugChannel, handleEnd);
124 startReadingFromStream(response, stream, handleEnd);
125 } else {
126 startReadingFromStream(response, stream, close.bind(null, response));
127 }
128
129 return getRoot(response);
130 }