UNPKG

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