UNPKG

10.3 kBJavaScriptView Raw
1/** @license React v16.12.0
2 * react-dom-unstable-flight-server.node.development.js
3 *
4 * Copyright (c) Facebook, Inc. and its affiliates.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE file in the root directory of this source tree.
8 */
9
10'use strict';
11
12
13
14if (process.env.NODE_ENV !== "production") {
15 (function() {
16'use strict';
17
18var ReactDOMServer = require('react-dom/server');
19
20function scheduleWork(callback) {
21 setImmediate(callback);
22}
23function flushBuffered(destination) {
24 // If we don't have any more data to send right now.
25 // Flush whatever is in the buffer to the wire.
26 if (typeof destination.flush === 'function') {
27 // http.createServer response have flush(), but it has a different meaning and
28 // is deprecated in favor of flushHeaders(). Detect to avoid a warning.
29 if (typeof destination.flushHeaders !== 'function') {
30 // By convention the Zlib streams provide a flush function for this purpose.
31 destination.flush();
32 }
33 }
34}
35function beginWriting(destination) {
36 // Older Node streams like http.createServer don't have this.
37 if (typeof destination.cork === 'function') {
38 destination.cork();
39 }
40}
41function writeChunk(destination, buffer) {
42 var nodeBuffer = buffer; // close enough
43
44 return destination.write(nodeBuffer);
45}
46function completeWriting(destination) {
47 // Older Node streams like http.createServer don't have this.
48 if (typeof destination.uncork === 'function') {
49 destination.uncork();
50 }
51}
52function close(destination) {
53 destination.end();
54}
55function convertStringToBuffer(content) {
56 return Buffer.from(content, 'utf8');
57}
58
59function renderHostChildrenToString(children) {
60 // TODO: This file is used to actually implement a server renderer
61 // so we can't actually reference the renderer here. Instead, we
62 // should replace this method with a reference to Fizz which
63 // then uses this file to implement the server renderer.
64 return ReactDOMServer.renderToStaticMarkup(children);
65}
66
67// The Symbol used to tag the ReactElement-like types. If there is no native Symbol
68// nor polyfill, then a plain number is used for performance.
69var hasSymbol = typeof Symbol === 'function' && Symbol.for;
70var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
71
72
73
74
75
76 // TODO: We don't use AsyncMode or ConcurrentMode anymore. They were temporary
77// (unstable) APIs that have been removed. Can we remove the symbols?
78
79/*
80
81FLIGHT PROTOCOL GRAMMAR
82
83Response
84- JSONData RowSequence
85- JSONData
86
87RowSequence
88- Row RowSequence
89- Row
90
91Row
92- "J" RowID JSONData
93- "H" RowID HTMLData
94- "B" RowID BlobData
95- "U" RowID URLData
96- "E" RowID ErrorData
97
98RowID
99- HexDigits ":"
100
101HexDigits
102- HexDigit HexDigits
103- HexDigit
104
105HexDigit
106- 0-F
107
108URLData
109- (UTF8 encoded URL) "\n"
110
111ErrorData
112- (UTF8 encoded JSON: {message: "...", stack: "..."}) "\n"
113
114JSONData
115- (UTF8 encoded JSON) "\n"
116 - String values that begin with $ are escaped with a "$" prefix.
117 - References to other rows are encoding as JSONReference strings.
118
119JSONReference
120- "$" HexDigits
121
122HTMLData
123- ByteSize (UTF8 encoded HTML)
124
125BlobData
126- ByteSize (Binary Data)
127
128ByteSize
129- (unsigned 32-bit integer)
130*/
131// TODO: Implement HTMLData, BlobData and URLData.
132
133var stringify = JSON.stringify;
134function createRequest(model, destination) {
135 var pingedSegments = [];
136 var request = {
137 destination: destination,
138 nextChunkId: 0,
139 pendingChunks: 0,
140 pingedSegments: pingedSegments,
141 completedJSONChunks: [],
142 completedErrorChunks: [],
143 flowing: false,
144 toJSON: function (key, value) {
145 return resolveModelToJSON(request, value);
146 }
147 };
148 request.pendingChunks++;
149 var rootSegment = createSegment(request, model);
150 pingedSegments.push(rootSegment);
151 return request;
152}
153
154function attemptResolveModelComponent(element) {
155 var type = element.type;
156 var props = element.props;
157
158 if (typeof type === 'function') {
159 // This is a nested view model.
160 return type(props);
161 } else if (typeof type === 'string') {
162 // This is a host element. E.g. HTML.
163 return renderHostChildrenToString(element);
164 } else {
165 throw new Error('Unsupported type.');
166 }
167}
168
169function pingSegment(request, segment) {
170 var pingedSegments = request.pingedSegments;
171 pingedSegments.push(segment);
172
173 if (pingedSegments.length === 1) {
174 scheduleWork(function () {
175 return performWork(request);
176 });
177 }
178}
179
180function createSegment(request, model) {
181 var id = request.nextChunkId++;
182 var segment = {
183 id: id,
184 model: model,
185 ping: function () {
186 return pingSegment(request, segment);
187 }
188 };
189 return segment;
190}
191
192function serializeIDRef(id) {
193 return '$' + id.toString(16);
194}
195
196function serializeRowHeader(tag, id) {
197 return tag + id.toString(16) + ':';
198}
199
200function escapeStringValue(value) {
201 if (value[0] === '$') {
202 // We need to escape $ prefixed strings since we use that to encode
203 // references to IDs.
204 return '$' + value;
205 } else {
206 return value;
207 }
208}
209
210function resolveModelToJSON(request, value) {
211 if (typeof value === 'string') {
212 return escapeStringValue(value);
213 }
214
215 while (typeof value === 'object' && value !== null && value.$$typeof === REACT_ELEMENT_TYPE) {
216 var element = value;
217
218 try {
219 value = attemptResolveModelComponent(element);
220 } catch (x) {
221 if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
222 // Something suspended, we'll need to create a new segment and resolve it later.
223 request.pendingChunks++;
224 var newSegment = createSegment(request, element);
225 var ping = newSegment.ping;
226 x.then(ping, ping);
227 return serializeIDRef(newSegment.id);
228 } else {
229 request.pendingChunks++;
230 var errorId = request.nextChunkId++;
231 emitErrorChunk(request, errorId, x);
232 return serializeIDRef(errorId);
233 }
234 }
235 }
236
237 return value;
238}
239
240function emitErrorChunk(request, id, error) {
241 // TODO: We should not leak error messages to the client in prod.
242 // Give this an error code instead and log on the server.
243 // We can serialize the error in DEV as a convenience.
244 var message;
245 var stack = '';
246
247 try {
248 if (error instanceof Error) {
249 message = '' + error.message;
250 stack = '' + error.stack;
251 } else {
252 message = 'Error: ' + error;
253 }
254 } catch (x) {
255 message = 'An error occurred but serializing the error message failed.';
256 }
257
258 var errorInfo = {
259 message: message,
260 stack: stack
261 };
262 var row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
263 request.completedErrorChunks.push(convertStringToBuffer(row));
264}
265
266function retrySegment(request, segment) {
267 var value = segment.model;
268
269 try {
270 while (typeof value === 'object' && value !== null && value.$$typeof === REACT_ELEMENT_TYPE) {
271 // If this is a nested model, there's no need to create another chunk,
272 // we can reuse the existing one and try again.
273 var element = value;
274 segment.model = element;
275 value = attemptResolveModelComponent(element);
276 }
277
278 var json = stringify(value, request.toJSON);
279 var row;
280 var id = segment.id;
281
282 if (id === 0) {
283 row = json + '\n';
284 } else {
285 row = serializeRowHeader('J', id) + json + '\n';
286 }
287
288 request.completedJSONChunks.push(convertStringToBuffer(row));
289 } catch (x) {
290 if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
291 // Something suspended again, let's pick it back up later.
292 var ping = segment.ping;
293 x.then(ping, ping);
294 return;
295 } else {
296 // This errored, we need to serialize this error to the
297 emitErrorChunk(request, segment.id, x);
298 }
299 }
300}
301
302function performWork(request) {
303 var pingedSegments = request.pingedSegments;
304 request.pingedSegments = [];
305
306 for (var i = 0; i < pingedSegments.length; i++) {
307 var segment = pingedSegments[i];
308 retrySegment(request, segment);
309 }
310
311 if (request.flowing) {
312 flushCompletedChunks(request);
313 }
314}
315
316var reentrant = false;
317
318function flushCompletedChunks(request) {
319 if (reentrant) {
320 return;
321 }
322
323 reentrant = true;
324 var destination = request.destination;
325 beginWriting(destination);
326
327 try {
328 var jsonChunks = request.completedJSONChunks;
329 var i = 0;
330
331 for (; i < jsonChunks.length; i++) {
332 request.pendingChunks--;
333 var chunk = jsonChunks[i];
334
335 if (!writeChunk(destination, chunk)) {
336 request.flowing = false;
337 i++;
338 break;
339 }
340 }
341
342 jsonChunks.splice(0, i);
343 var errorChunks = request.completedErrorChunks;
344 i = 0;
345
346 for (; i < errorChunks.length; i++) {
347 request.pendingChunks--;
348 var _chunk = errorChunks[i];
349
350 if (!writeChunk(destination, _chunk)) {
351 request.flowing = false;
352 i++;
353 break;
354 }
355 }
356
357 errorChunks.splice(0, i);
358 } finally {
359 reentrant = false;
360 completeWriting(destination);
361 }
362
363 flushBuffered(destination);
364
365 if (request.pendingChunks === 0) {
366 // We're done.
367 close(destination);
368 }
369}
370
371function startWork(request) {
372 request.flowing = true;
373 scheduleWork(function () {
374 return performWork(request);
375 });
376}
377function startFlowing(request) {
378 request.flowing = true;
379 flushCompletedChunks(request);
380}
381
382// This file intentionally does *not* have the Flow annotation.
383// Don't add it. See `./inline-typed.js` for an explanation.
384
385function createDrainHandler(destination, request) {
386 return function () {
387 return startFlowing(request);
388 };
389}
390
391function pipeToNodeWritable(model, destination) {
392 var request = createRequest(model, destination);
393 destination.on('drain', createDrainHandler(destination, request));
394 startWork(request);
395}
396
397var ReactFlightDOMServerNode = {
398 pipeToNodeWritable: pipeToNodeWritable
399};
400
401var ReactFlightDOMServerNode$1 = Object.freeze({
402 default: ReactFlightDOMServerNode
403});
404
405var ReactFlightDOMServerNode$2 = ( ReactFlightDOMServerNode$1 && ReactFlightDOMServerNode ) || ReactFlightDOMServerNode$1;
406
407// TODO: decide on the top-level export form.
408// This is hacky but makes it work with both Rollup and Jest
409
410
411var unstableFlightServer_node = ReactFlightDOMServerNode$2.default || ReactFlightDOMServerNode$2;
412
413module.exports = unstableFlightServer_node;
414 })();
415}