@samitouri / QOS-React / commits / 8af729231b

Initial Stack-Free SSR Implementation (#9673)

* Server Rendering initial commit Originally authored by spicyj, tweakes to rebase it and make it run by tomocchino. Adding ReactDOMServerRendering and a createTagMarkup utility function which will be used in it. * Fix build system, add shortcut scripts * Make more ReactServerRendering-test unit tests pass * Make ReactServerRendering-test pass with copious help from Ben This is pretty hacky and I just inlined everything, but at least I sort of understand how it works now, and all of the tests are passing. There are also only 68 tests failing in the integration test suite. * remove some unnecessary cruft * Run prettier on ReactDOMServerRendering.js * Fix more unit tests with Ben * Add support for input and textarea by copy pasting a bunch of code from ReactDOMInput/ReactDOMTextarea * Fix context unit tests with more copy paste :) * progress on select * Holy shit, 100% of ReactDOMServerIntegration tests are passing * Checkpoint to fix some of the ReactDOMComponent-test tests * Fix missing checkPropTypes * Fix some unit tests in ReactDOMComponent-test * Run prettier on everything, thanks Ben * get rid of ssr tests that are looking for component file names * add assertValidProps check * rename flattenChildren to flattenOptionChildren per Ben * Fix all the lint crap * Move things around in the file and turn ReactDOMServerRenderer into a class * remove changes I added to package.json * remove separate createOpenTagMarkup file for now since everything should be copy pasted into a single file * re-record Fiber tests * Revert ReactDOMServer.js and the fiber tests per Sebastian This also reverts the changes I made to ReactDOMComponent-test.js which removed the stack which is missing in the new server renderer" * Rename files based on feedback Moving src/renderers/dom/server/ReactDOMServerRendering.js to src/renderers/server/ReactServerRenderer.js and add src/renderers/server/ReactServer.js which makes this new codepath completely separate. * Change throw to invariant, even though we probably need to remove this at some point * fix prettier.. sigh

Tom Occhino committed May 17, 2017 at 14:53 UTC 8af729231b2e380f7d0f602109c7d6ac632a503d
5 files changed +747 -27
scripts/rollup/bundles.js
+7 -2
@@ -169,7 +169,11 @@ const bundles = [
169 sourceMap: false,
170 },
171 entry: 'src/renderers/dom/ReactDOMServer.js',
172 - externals: ['prop-types', 'prop-types/checkPropTypes'],
172 + externals: [
173 + 'prop-types',
174 + 'prop-types/checkPropTypes',
175 + 'create-react-class/factory',
176 + ],
177 fbEntry: 'src/renderers/dom/ReactDOMServer.js',
178 hasteName: 'ReactDOMServerStack',
179 isRenderer: true,
@@ -177,11 +181,12 @@ const bundles = [
181 manglePropertiesOnProd: false,
182 name: 'react-dom/server',
183 paths: [
184 + 'src/isomorphic/**/*.js',
185 'src/renderers/dom/**/*.js',
186 'src/renderers/shared/**/*.js',
182 -
187 'src/ReactVersion.js',
188 'src/shared/**/*.js',
189 + 'src/addons/**/*.js',
190 ],
191 },
192 // TODO: there is no Fiber version of ReactDOMServer.
src/renderers/__tests__/ReactComponentTreeHook-test.js
-23
@@ -223,29 +223,6 @@ describe('ReactComponentTreeHook', () => {
223 // the whole subtree automatically.
224 ReactDOM.unmountComponentAtNode(node);
225 expectWrapperTreeToEqual(null);
226 -
227 - // Server render every pair.
228 - // Ensure the tree is correct on every step.
229 - pairs.forEach(([element, expectedTree]) => {
230 - currentElement = element;
231 -
232 - // Rendering to string should not produce any entries
233 - // because ReactDebugTool purges it when the flush ends.
234 - ReactDOMServer.renderToString(<Wrapper />);
235 - expectWrapperTreeToEqual(null);
236 -
237 - // To test it, we tell the hook to ignore next purge
238 - // so the cleanup request by ReactDebugTool is ignored.
239 - // This lets us make assertions on the actual tree.
240 - ReactComponentTreeHook._preventPurging = true;
241 - ReactDOMServer.renderToString(<Wrapper />);
242 - ReactComponentTreeHook._preventPurging = false;
243 - expectWrapperTreeToEqual(expectedTree);
244 -
245 - // Purge manually since we skipped the automatic purge.
246 - ReactComponentTreeHook.purgeUnmountedComponents();
247 - expectWrapperTreeToEqual(null);
248 - });
226 }
227
228 describeStack('mount', () => {
src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js
+3 -2
@@ -23,8 +23,9 @@ function getStackAddendum(debugID) {
23 // This can only happen on Stack
24 return ReactComponentTreeHook.getStackAddendumByID(debugID);
25 } else {
26 - // This can only happen on Fiber
27 - return ReactDebugCurrentFiber.getCurrentFiberStackAddendum();
26 + // This can only happen on Fiber / Server
27 + var stack = ReactDebugCurrentFiber.getCurrentFiberStackAddendum();
28 + return stack != null ? stack : '';
29 }
30 }
31
src/renderers/server/ReactServer.js new
+28
@@ -0,0 +1,28 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactServer
10 + */
11 +
12 +'use strict';
13 +
14 +var ReactDOMInjection = require('ReactDOMInjection');
15 +var ReactDOMStackInjection = require('ReactDOMStackInjection');
16 +var ReactServerRenderer = require('ReactServerRenderer');
17 +var ReactVersion = require('ReactVersion');
18 +
19 +ReactDOMInjection.inject();
20 +ReactDOMStackInjection.inject();
21 +
22 +var ReactServer = {
23 + renderToString: ReactServerRenderer.renderToString,
24 + renderToStaticMarkup: ReactServerRenderer.renderToStaticMarkup,
25 + version: ReactVersion,
26 +};
27 +
28 +module.exports = ReactServer;
src/renderers/server/ReactServerRenderer.js new
+709
@@ -0,0 +1,709 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactServerRenderer
10 + */
11 +
12 +'use strict';
13 +
14 +var CSSPropertyOperations = require('CSSPropertyOperations');
15 +var DOMPropertyOperations = require('DOMPropertyOperations');
16 +var {registrationNameModules} = require('EventPluginRegistry');
17 +var React = require('React');
18 +var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
19 +var ReactElement = require('ReactElement');
20 +var ReactMarkupChecksum = require('ReactMarkupChecksum');
21 +
22 +var assertValidProps = require('assertValidProps');
23 +var checkPropTypes = require('prop-types/checkPropTypes');
24 +var emptyObject = require('fbjs/lib/emptyObject');
25 +var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
26 +var invariant = require('fbjs/lib/invariant');
27 +var omittedCloseTags = require('omittedCloseTags');
28 +var traverseAllChildren = require('traverseAllChildren');
29 +var warning = require('fbjs/lib/warning');
30 +
31 +if (__DEV__) {
32 + var {
33 + validateProperties: validateARIAProperties,
34 + } = require('ReactDOMInvalidARIAHook');
35 + var {
36 + validateProperties: validateInputPropertes,
37 + } = require('ReactDOMNullInputValuePropHook');
38 + var {
39 + validateProperties: validateUnknownPropertes,
40 + } = require('ReactDOMUnknownPropertyHook');
41 + var validatePropertiesInDevelopment = function(type, props) {
42 + validateARIAProperties(type, props);
43 + validateInputPropertes(type, props);
44 + validateUnknownPropertes(type, props);
45 + };
46 +}
47 +
48 +var didWarnDefaultInputValue = false;
49 +var didWarnDefaultChecked = false;
50 +var didWarnDefaultSelectValue = false;
51 +var didWarnDefaultTextareaValue = false;
52 +var didWarnInvalidOptionChildren = false;
53 +var valuePropNames = ['value', 'defaultValue'];
54 +var newlineEatingTags = {
55 + listing: true,
56 + pre: true,
57 + textarea: true,
58 +};
59 +
60 +// We accept any tag to be rendered but since this gets injected into arbitrary
61 +// HTML, we want to make sure that it's a safe tag.
62 +// http://www.w3.org/TR/REC-xml/#NT-Name
63 +var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset
64 +var validatedTagCache = {};
65 +function validateDangerousTag(tag) {
66 + if (!validatedTagCache.hasOwnProperty(tag)) {
67 + invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag);
68 + validatedTagCache[tag] = true;
69 + }
70 +}
71 +
72 +function warnNoop(
73 + publicInstance: ReactComponent<any, any, any>,
74 + callerName: string,
75 +) {
76 + if (__DEV__) {
77 + var constructor = publicInstance.constructor;
78 + warning(
79 + false,
80 + '%s(...): Can only update a mounting component. ' +
81 + 'This usually means you called %s() outside componentWillMount() on the server. ' +
82 + 'This is a no-op.\n\nPlease check the code for the %s component.',
83 + callerName,
84 + callerName,
85 + (constructor && (constructor.displayName || constructor.name)) ||
86 + 'ReactClass',
87 + );
88 + }
89 +}
90 +
91 +function shouldConstruct(Component) {
92 + return Component.prototype && Component.prototype.isReactComponent;
93 +}
94 +
95 +function getNonChildrenInnerMarkup(props) {
96 + var innerHTML = props.dangerouslySetInnerHTML;
97 + if (innerHTML != null) {
98 + if (innerHTML.__html != null) {
99 + return innerHTML.__html;
100 + }
101 + } else {
102 + var content = props.children;
103 + if (typeof content === 'string' || typeof content === 'number') {
104 + return escapeTextContentForBrowser(content);
105 + }
106 + }
107 + return null;
108 +}
109 +
110 +function flattenOptionChildren(children) {
111 + var content = '';
112 + // Flatten children and warn if they aren't strings or numbers;
113 + // invalid types are ignored.
114 + React.Children.forEach(children, function(child) {
115 + if (child == null) {
116 + return;
117 + }
118 + if (typeof child === 'string' || typeof child === 'number') {
119 + content += child;
120 + } else if (!didWarnInvalidOptionChildren) {
121 + didWarnInvalidOptionChildren = true;
122 + warning(
123 + false,
124 + 'Only strings and numbers are supported as <option> children.',
125 + );
126 + }
127 + });
128 + return content;
129 +}
130 +
131 +function maskContext(type, context) {
132 + var contextTypes = type.contextTypes;
133 + if (!contextTypes) {
134 + return emptyObject;
135 + }
136 + var maskedContext = {};
137 + for (var contextName in contextTypes) {
138 + maskedContext[contextName] = context[contextName];
139 + }
140 + return maskedContext;
141 +}
142 +
143 +function checkContextTypes(typeSpecs, values, location: string) {
144 + if (__DEV__) {
145 + checkPropTypes(
146 + typeSpecs,
147 + values,
148 + location,
149 + 'Component',
150 + () => '', // ReactDebugCurrentFrame.getStackAddendum,
151 + );
152 + }
153 +}
154 +
155 +function processContext(type, context) {
156 + var maskedContext = maskContext(type, context);
157 + if (__DEV__) {
158 + if (type.contextTypes) {
159 + checkContextTypes(type.contextTypes, maskedContext, 'context');
160 + }
161 + }
162 + return maskedContext;
163 +}
164 +
165 +var STYLE = 'style';
166 +var RESERVED_PROPS = {
167 + children: null,
168 + dangerouslySetInnerHTML: null,
169 + suppressContentEditableWarning: null,
170 +};
171 +
172 +function isCustomComponent(tagName, props) {
173 + return tagName.indexOf('-') >= 0 || props.is != null;
174 +}
175 +
176 +function createOpenTagMarkup(
177 + tagVerbatim,
178 + tagLowercase,
179 + props,
180 + makeStaticMarkup,
181 + isRootElement,
182 + domID,
183 + instForDebug,
184 +) {
185 + var ret = '<' + tagVerbatim;
186 +
187 + for (var propKey in props) {
188 + if (!props.hasOwnProperty(propKey)) {
189 + continue;
190 + }
191 + var propValue = props[propKey];
192 + if (propValue == null) {
193 + continue;
194 + }
195 + if (!registrationNameModules.hasOwnProperty(propKey)) {
196 + if (propKey === STYLE) {
197 + propValue = CSSPropertyOperations.createMarkupForStyles(
198 + propValue,
199 + instForDebug,
200 + );
201 + }
202 + var markup = null;
203 + if (isCustomComponent(tagLowercase, props)) {
204 + if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
205 + markup = DOMPropertyOperations.createMarkupForCustomAttribute(
206 + propKey,
207 + propValue,
208 + );
209 + }
210 + } else {
211 + markup = DOMPropertyOperations.createMarkupForProperty(
212 + propKey,
213 + propValue,
214 + );
215 + }
216 + if (markup) {
217 + ret += ' ' + markup;
218 + }
219 + }
220 + }
221 +
222 + // For static pages, no need to put React ID and checksum. Saves lots of
223 + // bytes.
224 + if (makeStaticMarkup) {
225 + return ret;
226 + }
227 +
228 + if (isRootElement) {
229 + ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
230 + }
231 + ret += ' ' + DOMPropertyOperations.createMarkupForID(domID);
232 + return ret;
233 +}
234 +
235 +function resolve(child, context) {
236 + // TODO: We'll need to support Arrays (and strings) after Fiber is rolled out
237 + invariant(!Array.isArray(child), 'Did not expect to receive an Array child');
238 + while (
239 + ReactElement.isValidElement(child) &&
240 + typeof child.type === 'function'
241 + ) {
242 + var Component = child.type;
243 + var publicContext = processContext(Component, context);
244 +
245 + var inst;
246 + var queue = [];
247 + var replace = false;
248 + var updater = {
249 + isMounted: function(publicInstance) {
250 + return false;
251 + },
252 + enqueueForceUpdate: function(publicInstance) {
253 + if (queue === null) {
254 + warnNoop(publicInstance, 'forceUpdate');
255 + return null;
256 + }
257 + },
258 + enqueueReplaceState: function(publicInstance, completeState) {
259 + replace = true;
260 + queue = [completeState];
261 + },
262 + enqueueSetState: function(publicInstance, partialState) {
263 + if (queue === null) {
264 + warnNoop(publicInstance, 'setState');
265 + return null;
266 + }
267 + queue.push(partialState);
268 + },
269 + };
270 +
271 + if (shouldConstruct(Component)) {
272 + inst = new Component(child.props, publicContext, updater);
273 + } else {
274 + inst = Component(child.props, publicContext, updater);
275 + if (inst == null || inst.render == null) {
276 + child = inst;
277 + continue;
278 + }
279 + }
280 +
281 + var initialState = inst.state;
282 + if (initialState === undefined) {
283 + inst.state = initialState = null;
284 + }
285 + if (inst.componentWillMount) {
286 + inst.componentWillMount();
287 + if (queue.length) {
288 + var oldQueue = queue;
289 + var oldReplace = replace;
290 + queue = null;
291 + replace = false;
292 +
293 + if (oldReplace && oldQueue.length === 1) {
294 + inst.state = oldQueue[0];
295 + } else {
296 + var nextState = oldReplace ? oldQueue[0] : inst.state;
297 + var dontMutate = true;
298 + for (var i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
299 + var partial = oldQueue[i];
300 + var partialState = typeof partial === 'function'
301 + ? partial.call(inst, nextState, child.props, publicContext)
302 + : partial;
303 + if (partialState) {
304 + if (dontMutate) {
305 + dontMutate = false;
306 + nextState = Object.assign({}, nextState, partialState);
307 + } else {
308 + Object.assign(nextState, partialState);
309 + }
310 + }
311 + }
312 + inst.state = nextState;
313 + }
314 + } else {
315 + queue = null;
316 + }
317 + }
318 + child = inst.render();
319 +
320 + var childContext = inst.getChildContext && inst.getChildContext();
321 + if (childContext) {
322 + context = Object.assign({}, context, childContext);
323 + }
324 + }
325 + return {child, context};
326 +}
327 +
328 +class ReactDOMServerRenderer {
329 + constructor(element, makeStaticMarkup) {
330 + this.stack = [
331 + {
332 + children: [element],
333 + childIndex: 0,
334 + context: emptyObject,
335 + footer: '',
336 + },
337 + ];
338 + this.idCounter = 1;
339 + this.exhausted = false;
340 + this.currentSelectValue = null;
341 + this.makeStaticMarkup = makeStaticMarkup;
342 + }
343 +
344 + read(bytes) {
345 + var out = '';
346 + while (out.length < bytes) {
347 + if (this.stack.length === 0) {
348 + this.exhausted = true;
349 + break;
350 + }
351 + var frame = this.stack[this.stack.length - 1];
352 + if (frame.childIndex >= frame.children.length) {
353 + out += frame.footer;
354 + this.stack.pop();
355 + if (frame.tag === 'select') {
356 + this.currentSelectValue = null;
357 + }
358 + continue;
359 + }
360 + var child = frame.children[frame.childIndex++];
361 + out += this.render(child, frame.context);
362 + }
363 + return out;
364 + }
365 +
366 + render(child, context) {
367 + if (typeof child === 'string' || typeof child === 'number') {
368 + if (this.makeStaticMarkup) {
369 + return escapeTextContentForBrowser('' + child);
370 + }
371 + return (
372 + '<!-- react-text: ' +
373 + this.idCounter++ +
374 + ' -->' +
375 + escapeTextContentForBrowser('' + child) +
376 + '<!-- /react-text -->'
377 + );
378 + } else {
379 + ({child, context} = resolve(child, context));
380 + if (child === null || child === false) {
381 + return '<!-- react-empty: ' + this.idCounter++ + ' -->';
382 + } else {
383 + return this.renderDOM(child, context);
384 + }
385 + }
386 + }
387 +
388 + renderDOM(element, context) {
389 + var tag = element.type.toLowerCase();
390 +
391 + if (__DEV__) {
392 + warning(
393 + tag === element.type,
394 + '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
395 + 'in React.',
396 + element.type,
397 + );
398 + }
399 +
400 + validateDangerousTag(tag);
401 +
402 + var props = element.props;
403 + if (tag === 'input') {
404 + if (__DEV__) {
405 + ReactControlledValuePropTypes.checkPropTypes(
406 + 'input',
407 + props,
408 + () => '', //getCurrentFiberStackAddendum
409 + );
410 +
411 + if (
412 + props.checked !== undefined &&
413 + props.defaultChecked !== undefined &&
414 + !didWarnDefaultChecked
415 + ) {
416 + warning(
417 + false,
418 + '%s contains an input of type %s with both checked and defaultChecked props. ' +
419 + 'Input elements must be either controlled or uncontrolled ' +
420 + '(specify either the checked prop, or the defaultChecked prop, but not ' +
421 + 'both). Decide between using a controlled or uncontrolled input ' +
422 + 'element and remove one of these props. More info: ' +
423 + 'https://fb.me/react-controlled-components',
424 + 'A component',
425 + props.type,
426 + );
427 + didWarnDefaultChecked = true;
428 + }
429 + if (
430 + props.value !== undefined &&
431 + props.defaultValue !== undefined &&
432 + !didWarnDefaultInputValue
433 + ) {
434 + warning(
435 + false,
436 + '%s contains an input of type %s with both value and defaultValue props. ' +
437 + 'Input elements must be either controlled or uncontrolled ' +
438 + '(specify either the value prop, or the defaultValue prop, but not ' +
439 + 'both). Decide between using a controlled or uncontrolled input ' +
440 + 'element and remove one of these props. More info: ' +
441 + 'https://fb.me/react-controlled-components',
442 + 'A component',
443 + props.type,
444 + );
445 + didWarnDefaultInputValue = true;
446 + }
447 + }
448 +
449 + props = Object.assign(
450 + {
451 + type: undefined,
452 + },
453 + props,
454 + {
455 + defaultChecked: undefined,
456 + defaultValue: undefined,
457 + value: props.value != null ? props.value : props.defaultValue,
458 + checked: props.checked != null ? props.checked : props.defaultChecked,
459 + },
460 + );
461 + } else if (tag === 'textarea') {
462 + if (__DEV__) {
463 + ReactControlledValuePropTypes.checkPropTypes(
464 + 'textarea',
465 + props,
466 + () => '', //getCurrentFiberStackAddendum
467 + );
468 + if (
469 + props.value !== undefined &&
470 + props.defaultValue !== undefined &&
471 + !didWarnDefaultTextareaValue
472 + ) {
473 + warning(
474 + false,
475 + 'Textarea elements must be either controlled or uncontrolled ' +
476 + '(specify either the value prop, or the defaultValue prop, but not ' +
477 + 'both). Decide between using a controlled or uncontrolled textarea ' +
478 + 'and remove one of these props. More info: ' +
479 + 'https://fb.me/react-controlled-components',
480 + );
481 + didWarnDefaultTextareaValue = true;
482 + }
483 + }
484 +
485 + var initialValue = props.value;
486 + if (initialValue == null) {
487 + var defaultValue = props.defaultValue;
488 + // TODO (yungsters): Remove support for children content in <textarea>.
489 + var textareaChildren = props.children;
490 + if (textareaChildren != null) {
491 + if (__DEV__) {
492 + warning(
493 + false,
494 + 'Use the `defaultValue` or `value` props instead of setting ' +
495 + 'children on <textarea>.',
496 + );
497 + }
498 + invariant(
499 + defaultValue == null,
500 + 'If you supply `defaultValue` on a <textarea>, do not pass children.',
501 + );
502 + if (Array.isArray(textareaChildren)) {
503 + invariant(
504 + textareaChildren.length <= 1,
505 + '<textarea> can only have at most one child.',
506 + );
507 + textareaChildren = textareaChildren[0];
508 + }
509 +
510 + defaultValue = '' + textareaChildren;
511 + }
512 + if (defaultValue == null) {
513 + defaultValue = '';
514 + }
515 + initialValue = defaultValue;
516 + }
517 +
518 + props = Object.assign({}, props, {
519 + value: undefined,
520 + children: '' + initialValue,
521 + });
522 + } else if (tag === 'select') {
523 + if (__DEV__) {
524 + ReactControlledValuePropTypes.checkPropTypes(
525 + 'select',
526 + props,
527 + () => '', // getCurrentFiberStackAddendum,
528 + );
529 +
530 + for (var i = 0; i < valuePropNames.length; i++) {
531 + var propName = valuePropNames[i];
532 + if (props[propName] == null) {
533 + continue;
534 + }
535 + var isArray = Array.isArray(props[propName]);
536 + if (props.multiple && !isArray) {
537 + warning(
538 + false,
539 + 'The `%s` prop supplied to <select> must be an array if ' +
540 + '`multiple` is true.%s',
541 + propName,
542 + '', // getDeclarationErrorAddendum(),
543 + );
544 + } else if (!props.multiple && isArray) {
545 + warning(
546 + false,
547 + 'The `%s` prop supplied to <select> must be a scalar ' +
548 + 'value if `multiple` is false.%s',
549 + propName,
550 + '', // getDeclarationErrorAddendum(),
551 + );
552 + }
553 + }
554 + }
555 +
556 + if (
557 + props.value !== undefined &&
558 + props.defaultValue !== undefined &&
559 + !didWarnDefaultSelectValue
560 + ) {
561 + warning(
562 + false,
563 + 'Select elements must be either controlled or uncontrolled ' +
564 + '(specify either the value prop, or the defaultValue prop, but not ' +
565 + 'both). Decide between using a controlled or uncontrolled select ' +
566 + 'element and remove one of these props. More info: ' +
567 + 'https://fb.me/react-controlled-components',
568 + );
569 + didWarnDefaultSelectValue = true;
570 + }
571 +
572 + this.currentSelectValue = props.value != null
573 + ? props.value
574 + : props.defaultValue;
575 + props = Object.assign({}, props, {
576 + value: undefined,
577 + });
578 + } else if (tag === 'option') {
579 + var selected = null;
580 + var selectValue = this.currentSelectValue;
581 + var optionChildren = flattenOptionChildren(props.children);
582 + if (selectValue != null) {
583 + var value;
584 + if (props.value != null) {
585 + value = props.value + '';
586 + } else {
587 + value = optionChildren;
588 + }
589 + selected = false;
590 + if (Array.isArray(selectValue)) {
591 + // multiple
592 + for (var j = 0; j < selectValue.length; j++) {
593 + if ('' + selectValue[j] === value) {
594 + selected = true;
595 + break;
596 + }
597 + }
598 + } else {
599 + selected = '' + selectValue === value;
600 + }
601 +
602 + props = Object.assign(
603 + {
604 + selected: undefined,
605 + children: undefined,
606 + },
607 + props,
608 + {
609 + selected: selected,
610 + children: optionChildren,
611 + },
612 + );
613 + }
614 + }
615 +
616 + if (__DEV__) {
617 + validatePropertiesInDevelopment(tag, props);
618 + }
619 +
620 + assertValidProps(tag, props);
621 +
622 + var out = createOpenTagMarkup(
623 + element.type,
624 + tag,
625 + props,
626 + this.makeStaticMarkup,
627 + this.stack.length === 1,
628 + this.idCounter++,
629 + null,
630 + );
631 + var footer = '';
632 + if (omittedCloseTags.hasOwnProperty(tag)) {
633 + out += '/>';
634 + } else {
635 + out += '>';
636 + footer = '</' + element.type + '>';
637 + }
638 + var children = [];
639 + var innerMarkup = getNonChildrenInnerMarkup(props);
640 + if (innerMarkup != null) {
641 + if (newlineEatingTags[tag] && innerMarkup.charAt(0) === '\n') {
642 + // text/html ignores the first character in these tags if it's a newline
643 + // Prefer to break application/xml over text/html (for now) by adding
644 + // a newline specifically to get eaten by the parser. (Alternately for
645 + // textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first
646 + // \r is normalized out by HTMLTextAreaElement#value.)
647 + // See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre>
648 + // See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions>
649 + // See: <http://www.w3.org/TR/html5/syntax.html#newlines>
650 + // See: Parsing of "textarea" "listing" and "pre" elements
651 + // from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody>
652 + out += '\n';
653 + }
654 + out += innerMarkup;
655 + } else {
656 + traverseAllChildren(props.children, function(ctx, child, name) {
657 + if (child != null) {
658 + children.push(child);
659 + }
660 + });
661 + }
662 + this.stack.push({
663 + tag,
664 + children,
665 + childIndex: 0,
666 + context: context,
667 + footer: footer,
668 + });
669 + return out;
670 + }
671 +}
672 +
673 +/**
674 + * Render a ReactElement to its initial HTML. This should only be used on the
675 + * server.
676 + * See https://facebook.github.io/react/docs/react-dom-server.html#rendertostring
677 + */
678 +function renderToString(element) {
679 + invariant(
680 + ReactElement.isValidElement(element),
681 + 'renderToString(): You must pass a valid ReactElement.',
682 + );
683 + var renderer = new ReactDOMServerRenderer(element, false);
684 + var markup = renderer.read(Infinity);
685 + markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
686 + return markup;
687 +}
688 +
689 +/**
690 + * Similar to renderToString, except this doesn't create extra DOM attributes
691 + * such as data-react-id that React uses internally.
692 + * See https://facebook.github.io/react/docs/react-dom-server.html#rendertostaticmarkup
693 + */
694 +function renderToStaticMarkup(element) {
695 + invariant(
696 + ReactElement.isValidElement(element),
697 + 'renderToStaticMarkup(): You must pass a valid ReactElement.',
698 + );
699 + var renderer = new ReactDOMServerRenderer(element, true);
700 + var markup = renderer.read(Infinity);
701 + return markup;
702 +}
703 +
704 +var ReactServerRenderer = {
705 + renderToString: renderToString,
706 + renderToStaticMarkup: renderToStaticMarkup,
707 +};
708 +
709 +module.exports = ReactServerRenderer;