UNPKG

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