UNPKG

17.4 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.createMarkupForCustomAttribute = createMarkupForCustomAttribute;
7exports.renderToString = renderToString;
8
9var _types = require("../../serializer/types.js");
10
11var _completions = require("../../completions.js");
12
13var _index = require("../../values/index.js");
14
15var _reconcilation = require("../reconcilation.js");
16
17var _utils = require("../utils.js");
18
19var t = _interopRequireWildcard(require("@babel/types"));
20
21var _invariant = _interopRequireDefault(require("../../invariant.js"));
22
23var _utils2 = require("./utils.js");
24
25var _domConfig = require("./dom-config.js");
26
27var _hyphenateStyleName = _interopRequireDefault(require("fbjs/lib/hyphenateStyleName"));
28
29var _singletons = require("../../singletons.js");
30
31var _generator = require("../../utils/generator.js");
32
33function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
34
35function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
36
37/**
38 * Copyright (c) 2017-present, Facebook, Inc.
39 * All rights reserved.
40 *
41 * This source code is licensed under the BSD-style license found in the
42 * LICENSE file in the root directory of this source tree. An additional grant
43 * of patent rights can be found in the PATENTS file in the same directory.
44 */
45// Warning: This code is experimental and might not fully work. There is no guarantee
46// that it is up-to-date with the current react-dom/server logic and there may also be
47// security holes in the string escaping because of this.
48// $FlowFixMe: flow complains that this isn't a module but it is, and it seems to load fine
49function renderValueWithHelper(realm, value, helper) {
50 // given we know nothing of this value, we need to escape the contents of it at runtime
51 let val = _index.AbstractValue.createFromBuildFunction(realm, _index.Value, [helper, value], (0, _generator.createOperationDescriptor)("REACT_SSR_RENDER_VALUE_HELPER"));
52
53 (0, _invariant.default)(val instanceof _index.AbstractValue);
54 return val;
55}
56
57function dangerousStyleValue(realm, name, value, isCustomProperty) {
58 let isEmpty = value === realm.intrinsics.null || value === realm.intrinsics.undefined || value instanceof _index.BooleanValue || value instanceof _index.StringValue && value.value === "";
59
60 if (isEmpty) {
61 return "";
62 }
63
64 if (!isCustomProperty && value instanceof _index.NumberValue && value.value !== 0 && !(_domConfig.isUnitlessNumber.hasOwnProperty(name) && _domConfig.isUnitlessNumber[name])) {
65 return value.value + "px";
66 }
67
68 if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
69 return ("" + value.value).trim();
70 } else {
71 (0, _invariant.default)(false, "TODO");
72 }
73}
74
75function createMarkupForCustomAttribute(realm, name, value) {
76 if (!(0, _domConfig.isAttributeNameSafe)(name) || value == null) {
77 return "";
78 }
79
80 if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
81 return name + "=" + (0, _utils2.quoteAttributeValueForBrowser)(value.value + "");
82 } else {
83 (0, _invariant.default)(false, "TODO");
84 }
85}
86
87function createMarkupForProperty(realm, name, value, htmlEscapeHelper) {
88 const propertyInfo = (0, _domConfig.getPropertyInfo)(name);
89
90 if (name !== "style" && (0, _domConfig.shouldIgnoreAttribute)(name, propertyInfo, false)) {
91 return "";
92 }
93
94 if ((0, _domConfig.shouldRemoveAttribute)(realm, name, value, propertyInfo, false)) {
95 return "";
96 }
97
98 if (propertyInfo !== null) {
99 const attributeName = propertyInfo.attributeName;
100 const {
101 type
102 } = propertyInfo;
103
104 if (type === _domConfig.BOOLEAN || type === _domConfig.OVERLOADED_BOOLEAN && value === true) {
105 return attributeName + '=""';
106 } else if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
107 return attributeName + "=" + (0, _utils2.quoteAttributeValueForBrowser)(value.value + "");
108 } else if (value instanceof _index.AbstractValue) {
109 return [attributeName + "=", renderValueWithHelper(realm, value, htmlEscapeHelper)];
110 }
111 } else if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
112 return name + "=" + (0, _utils2.quoteAttributeValueForBrowser)(value.value + "");
113 } else if (value instanceof _index.AbstractValue) {
114 return [name + '="', renderValueWithHelper(realm, value, htmlEscapeHelper), '"'];
115 }
116
117 (0, _invariant.default)(false, "TODO");
118}
119
120function createMarkupForStyles(realm, styles) {
121 let serialized = [];
122 let delimiter = "";
123
124 if (styles instanceof _index.ObjectValue && !styles.isPartialObject()) {
125 for (let [styleName, binding] of styles.properties) {
126 if (binding.descriptor !== undefined) {
127 let isCustomProperty = styleName.indexOf("--") === 0;
128 let styleValue = (0, _utils.getProperty)(realm, styles, styleName);
129
130 if (styleValue !== realm.intrinsics.null && styleValue !== realm.intrinsics.undefined) {
131 serialized.push(delimiter + (0, _hyphenateStyleName.default)(styleName) + ":");
132 serialized.push(dangerousStyleValue(realm, styleName, styleValue, isCustomProperty));
133 delimiter = ";";
134 }
135 }
136 }
137 }
138
139 if (serialized.length > 0) {
140 return renderReactNode(realm, serialized);
141 }
142
143 return realm.intrinsics.null;
144}
145
146function createOpenTagMarkup(realm, tagVerbatim, tagLowercase, propsValue, namespace, makeStaticMarkup, isRootElement, htmlEscapeHelper) {
147 let ret = ["<" + tagVerbatim];
148
149 if (propsValue instanceof _index.ObjectValue && !propsValue.isPartialObject()) {
150 for (let [propName, binding] of propsValue.properties) {
151 if (binding.descriptor !== undefined) {
152 let propValue = (0, _utils.getProperty)(realm, propsValue, propName);
153
154 if (propValue === realm.intrinsics.null || propValue === realm.intrinsics.undefined) {
155 continue;
156 }
157
158 if (propName === _domConfig.STYLE) {
159 propValue = createMarkupForStyles(realm, propValue);
160 }
161
162 let markup;
163
164 if ((0, _utils2.isCustomComponent)(realm, tagLowercase, propsValue)) {
165 if (!_domConfig.RESERVED_PROPS.has(propName)) {
166 markup = createMarkupForCustomAttribute(realm, propName, propValue);
167 }
168 } else {
169 markup = createMarkupForProperty(realm, propName, propValue, htmlEscapeHelper);
170 }
171
172 if (Array.isArray(markup)) {
173 ret.push(" ", ...markup);
174 } else if (typeof markup === "string" && markup !== "") {
175 ret.push(" " + markup);
176 } else if (markup) {
177 ret.push(" ", markup);
178 }
179 }
180 }
181 } else {
182 (0, _invariant.default)(false, "TODO");
183 } // For static pages, no need to put React ID and checksum. Saves lots of
184 // bytes.
185
186
187 if (makeStaticMarkup) {
188 return ret;
189 }
190
191 if (isRootElement) {
192 ret.push(" " + (0, _utils2.createMarkupForRoot)());
193 }
194
195 return ret;
196}
197
198function renderReactNode(realm, reactNode) {
199 let normalizedNode = (0, _utils2.normalizeNode)(realm, reactNode);
200
201 if (typeof normalizedNode === "string") {
202 return new _index.StringValue(realm, normalizedNode);
203 } else if (normalizedNode instanceof _index.AbstractValue) {
204 return normalizedNode;
205 }
206
207 (0, _invariant.default)(Array.isArray(normalizedNode));
208 let args = [];
209 let quasis = [];
210 let lastWasAbstract = false;
211
212 for (let element of normalizedNode) {
213 if (typeof element === "string") {
214 lastWasAbstract = false;
215 quasis.push(t.templateElement({
216 raw: element,
217 cooked: element
218 }));
219 } else {
220 if (lastWasAbstract) {
221 quasis.push(t.templateElement({
222 raw: "",
223 cooked: ""
224 }));
225 }
226
227 lastWasAbstract = true;
228 (0, _invariant.default)(element instanceof _index.Value);
229 args.push(element);
230 }
231 }
232
233 let val = _index.AbstractValue.createFromBuildFunction(realm, _index.StringValue, args, (0, _generator.createOperationDescriptor)("REACT_SSR_TEMPLATE_LITERAL", {
234 quasis
235 }));
236
237 (0, _invariant.default)(val instanceof _index.AbstractValue);
238 return val;
239}
240
241class ReactDOMServerRenderer {
242 constructor(realm, makeStaticMarkup) {
243 this.realm = realm;
244 this.makeStaticMarkup = makeStaticMarkup;
245 this.previousWasTextNode = false;
246 this.htmlEscapeHelper = (0, _utils2.createHtmlEscapeHelper)(realm);
247 this.arrayHelper = (0, _utils2.createArrayHelper)(realm);
248 }
249
250 render(value, namespace = "html", depth = 0) {
251 let rootReactNode = this._renderValue(value, namespace, depth);
252
253 return renderReactNode(this.realm, rootReactNode);
254 }
255
256 _renderText(value) {
257 let text = value.value + "";
258
259 if (text === "") {
260 return "";
261 }
262
263 if (this.makeStaticMarkup) {
264 return (0, _utils2.escapeHtml)(text);
265 }
266
267 if (this.previousWasTextNode) {
268 return "<!-- -->" + (0, _utils2.escapeHtml)(text);
269 }
270
271 this.previousWasTextNode = true;
272 return (0, _utils2.escapeHtml)(text);
273 }
274
275 _renderAbstractConditionalValue(condValue, consequentVal, alternateVal, namespace, depth) {
276 let val = this.realm.evaluateWithAbstractConditional(condValue, () => {
277 return this.realm.evaluateForEffects(() => this.render(consequentVal, namespace, depth), null, "_renderAbstractConditionalValue consequent");
278 }, () => {
279 return this.realm.evaluateForEffects(() => this.render(alternateVal, namespace, depth), null, "_renderAbstractConditionalValue consequent");
280 });
281 return (0, _utils2.convertValueToNode)(val);
282 }
283
284 _renderAbstractValue(value, namespace, depth) {
285 if (value.kind === "conditional") {
286 let [condValue, consequentVal, alternateVal] = value.args;
287 (0, _invariant.default)(condValue instanceof _index.AbstractValue);
288 return this._renderAbstractConditionalValue(condValue, consequentVal, alternateVal, namespace, depth);
289 } else {
290 return renderValueWithHelper(this.realm, value, this.htmlEscapeHelper);
291 }
292 }
293
294 _renderArrayValue(value, namespace, depth) {
295 if (_index.ArrayValue.isIntrinsicAndHasWidenedNumericProperty(value)) {
296 let arrayHint = this.realm.react.arrayHints.get(value);
297
298 if (arrayHint !== undefined) {
299 return renderValueWithHelper(this.realm, value, this.arrayHelper);
300 }
301 }
302
303 let elements = [];
304 (0, _utils.forEachArrayValue)(this.realm, value, elementValue => {
305 let renderedElement = this._renderValue(elementValue, namespace, depth);
306
307 if (Array.isArray(renderedElement)) {
308 elements.push(...renderedElement);
309 } else {
310 elements.push(renderedElement);
311 }
312 }); // $FlowFixMe: flow gets confused here
313
314 return elements;
315 }
316
317 _renderReactElement(reactElement, namespace, depth) {
318 let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
319 let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
320 (0, _invariant.default)(propsValue instanceof _index.AbstractObjectValue || propsValue instanceof _index.ObjectValue);
321
322 if (typeValue instanceof _index.StringValue) {
323 let type = typeValue.value;
324 let tag = type.toLowerCase();
325
326 if (tag === "input") {
327 (0, _invariant.default)(false, "TODO");
328 } else if (tag === "textarea") {
329 (0, _invariant.default)(false, "TODO");
330 } else if (tag === "select") {
331 (0, _invariant.default)(false, "TODO");
332 } else if (tag === "option") {
333 (0, _invariant.default)(false, "TODO");
334 }
335
336 let out = createOpenTagMarkup(this.realm, type, tag, propsValue, namespace, this.makeStaticMarkup, depth === 0, this.htmlEscapeHelper);
337 let footer = "";
338
339 if (_domConfig.omittedCloseTags.has(tag)) {
340 out.push("/>");
341 } else {
342 out.push(">");
343 footer = "</" + type + ">";
344 }
345
346 let innerMarkup = (0, _utils2.getNonChildrenInnerMarkup)(this.realm, propsValue);
347
348 if (innerMarkup instanceof _index.StringValue) {
349 if (_domConfig.newlineEatingTags[tag] && innerMarkup.value.charAt(0) === "\n") {
350 out.push("\n");
351 }
352
353 out.push(innerMarkup.value);
354 } else if (innerMarkup instanceof _index.ObjectValue) {
355 (0, _invariant.default)(false, "TODO");
356 } else {
357 this.previousWasTextNode = false;
358 let childrenValue = (0, _utils.getProperty)(this.realm, propsValue, "children");
359
360 let childrenOut = this._renderValue(childrenValue, namespace, depth + 1);
361
362 if (Array.isArray(childrenOut)) {
363 out.push(...childrenOut);
364 } else {
365 out.push(childrenOut);
366 }
367 }
368
369 out.push(footer);
370 this.previousWasTextNode = false;
371 return out;
372 } else if (typeValue instanceof _index.SymbolValue && typeValue === (0, _utils.getReactSymbol)("react.fragment", this.realm)) {
373 let childrenValue = (0, _utils.getProperty)(this.realm, propsValue, "children");
374
375 let childrenOut = this._renderValue(childrenValue, namespace, depth + 1);
376
377 let out = [];
378
379 if (Array.isArray(childrenOut)) {
380 out.push(...childrenOut);
381 } else {
382 out.push(childrenOut);
383 }
384
385 this.previousWasTextNode = false;
386 return out;
387 } else {
388 (0, _invariant.default)(false, "TODO");
389 }
390 }
391
392 _renderValue(value, namespace, depth) {
393 if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
394 return this._renderText(value);
395 } else if (value instanceof _index.ObjectValue && (0, _utils.isReactElement)(value)) {
396 return this._renderReactElement(value, namespace, depth);
397 } else if (value instanceof _index.AbstractValue) {
398 return this._renderAbstractValue(value, namespace, depth);
399 } else if (value instanceof _index.ArrayValue) {
400 return this._renderArrayValue(value, namespace, depth);
401 } else if (value instanceof _index.BooleanValue || value instanceof _index.UndefinedValue || value instanceof _index.NullValue) {
402 return "";
403 }
404
405 (0, _invariant.default)(false, "TODO");
406 }
407
408}
409
410function handleNestedOptimizedFunctions(realm, reconciler, staticMarkup) {
411 for (let _ref of reconciler.nestedOptimizedClosures) {
412 let {
413 func,
414 evaluatedNode,
415 componentType,
416 context
417 } = _ref;
418
419 if (reconciler.hasEvaluatedNestedClosure(func)) {
420 continue;
421 }
422
423 if (func instanceof _index.ECMAScriptSourceFunctionValue && reconciler.hasEvaluatedRootNode(func, evaluatedNode)) {
424 continue;
425 }
426
427 let closureEffects = reconciler.resolveNestedOptimizedClosure(func, [], componentType, context, evaluatedNode);
428 let closureEffectsRenderedToString = realm.evaluateForEffectsWithPriorEffects([closureEffects], () => {
429 let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
430 (0, _invariant.default)(closureEffects.result instanceof _completions.SimpleNormalCompletion);
431 return serverRenderer.render(closureEffects.result.value);
432 }, "handleNestedOptimizedFunctions");
433 realm.react.optimizedNestedClosuresToWrite.push({
434 effects: closureEffectsRenderedToString,
435 func
436 });
437 }
438}
439
440function renderToString(realm, reactElement, staticMarkup) {
441 let reactStatistics = new _types.ReactStatistics();
442 let reconciler = new _reconcilation.Reconciler(realm, {
443 firstRenderOnly: true,
444 isRoot: true,
445 modelString: undefined
446 }, reactStatistics);
447 let typeValue = (0, _utils.getProperty)(realm, reactElement, "type");
448 let propsValue = (0, _utils.getProperty)(realm, reactElement, "props");
449 let evaluatedRootNode = (0, _utils.createReactEvaluatedNode)("ROOT", (0, _utils.getComponentName)(realm, typeValue));
450 (0, _invariant.default)(typeValue instanceof _index.ECMAScriptSourceFunctionValue);
451
452 if (propsValue instanceof _index.AbstractValue && !(propsValue instanceof _index.AbstractObjectValue)) {
453 propsValue = _singletons.To.ToObject(realm, propsValue);
454 }
455
456 (0, _invariant.default)(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue);
457 let effects = reconciler.resolveReactComponentTree(typeValue, propsValue, null, evaluatedRootNode);
458 (0, _invariant.default)(realm.generator); // create a single regex used for the escape functions
459 // by hoisting it, it gets cached by the VM JITs
460
461 realm.generator.emitStatement([], (0, _generator.createOperationDescriptor)("REACT_SSR_REGEX_CONSTANT"));
462 (0, _invariant.default)(realm.generator);
463 realm.generator.emitStatement([], (0, _generator.createOperationDescriptor)("REACT_SSR_PREV_TEXT_NODE"));
464 (0, _invariant.default)(effects);
465 realm.applyEffects(effects);
466 (0, _invariant.default)(effects.result instanceof _completions.SimpleNormalCompletion);
467 let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
468 let renderValue = serverRenderer.render(effects.result.value);
469 handleNestedOptimizedFunctions(realm, reconciler, staticMarkup);
470 return renderValue;
471}
472//# sourceMappingURL=rendering.js.map
\No newline at end of file