prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
504 lines (398 loc) • 19.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createMarkupForCustomAttribute = createMarkupForCustomAttribute;
exports.renderToString = renderToString;
var _types = require("../../serializer/types.js");
var _completions = require("../../completions.js");
var _index = require("../../values/index.js");
var _reconcilation = require("../reconcilation.js");
var _utils = require("../utils.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _invariant = _interopRequireDefault(require("../../invariant.js"));
var _utils2 = require("./utils.js");
var _domConfig = require("./dom-config.js");
var _hyphenateStyleName = _interopRequireDefault(require("fbjs/lib/hyphenateStyleName"));
var _singletons = require("../../singletons.js");
var _generator = require("../../utils/generator.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _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; } }
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// Warning: This code is experimental and might not fully work. There is no guarantee
// that it is up-to-date with the current react-dom/server logic and there may also be
// security holes in the string escaping because of this.
// $FlowFixMe: flow complains that this isn't a module but it is, and it seems to load fine
function renderValueWithHelper(realm, value, helper) {
// given we know nothing of this value, we need to escape the contents of it at runtime
let val = _index.AbstractValue.createFromBuildFunction(realm, _index.Value, [helper, value], (0, _generator.createOperationDescriptor)("REACT_SSR_RENDER_VALUE_HELPER"));
(0, _invariant.default)(val instanceof _index.AbstractValue);
return val;
}
function dangerousStyleValue(realm, name, value, isCustomProperty) {
let isEmpty = value === realm.intrinsics.null || value === realm.intrinsics.undefined || value instanceof _index.BooleanValue || value instanceof _index.StringValue && value.value === "";
if (isEmpty) {
return "";
}
if (!isCustomProperty && value instanceof _index.NumberValue && value.value !== 0 && !(_domConfig.isUnitlessNumber.hasOwnProperty(name) && _domConfig.isUnitlessNumber[name])) {
return value.value + "px";
}
if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
return ("" + value.value).trim();
} else {
(0, _invariant.default)(false, "TODO");
}
}
function createMarkupForCustomAttribute(realm, name, value) {
if (!(0, _domConfig.isAttributeNameSafe)(name) || value == null) {
return "";
}
if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
return name + "=" + (0, _utils2.quoteAttributeValueForBrowser)(value.value + "");
} else {
(0, _invariant.default)(false, "TODO");
}
}
function createMarkupForProperty(realm, name, value, htmlEscapeHelper) {
const propertyInfo = (0, _domConfig.getPropertyInfo)(name);
if (name !== "style" && (0, _domConfig.shouldIgnoreAttribute)(name, propertyInfo, false)) {
return "";
}
if ((0, _domConfig.shouldRemoveAttribute)(realm, name, value, propertyInfo, false)) {
return "";
}
if (propertyInfo !== null) {
const attributeName = propertyInfo.attributeName;
const {
type
} = propertyInfo;
if (type === _domConfig.BOOLEAN || type === _domConfig.OVERLOADED_BOOLEAN && value === true) {
return attributeName + '=""';
} else if (value instanceof _index.StringValue || value instanceof _index.NumberValue || value instanceof _index.BooleanValue) {
// $FlowFixMe: Flow complains about booleans being converted to strings, which is the intention
return attributeName + "=" + (0, _utils2.quoteAttributeValueForBrowser)(value.value + "");
} else if (value instanceof _index.AbstractValue) {
return [attributeName + "=", renderValueWithHelper(realm, value, htmlEscapeHelper)];
}
} else if (value instanceof _index.StringValue || value instanceof _index.NumberValue || value instanceof _index.BooleanValue) {
// $FlowFixMe: Flow complains about booleans being converted to strings, which is the intention
return name + "=" + (0, _utils2.quoteAttributeValueForBrowser)(value.value + "");
} else if (value instanceof _index.AbstractValue) {
return [name + '="', renderValueWithHelper(realm, value, htmlEscapeHelper), '"'];
}
(0, _invariant.default)(false, "TODO");
}
function createMarkupForStyles(realm, styles) {
let serialized = [];
let delimiter = "";
if (styles instanceof _index.ObjectValue && !styles.isPartialObject()) {
for (let [styleName, binding] of styles.properties) {
if (binding.descriptor !== undefined) {
let isCustomProperty = styleName.indexOf("--") === 0;
let styleValue = (0, _utils.getProperty)(realm, styles, styleName);
if (styleValue !== realm.intrinsics.null && styleValue !== realm.intrinsics.undefined) {
serialized.push(delimiter + (0, _hyphenateStyleName.default)(styleName) + ":");
serialized.push(dangerousStyleValue(realm, styleName, styleValue, isCustomProperty));
delimiter = ";";
}
}
}
}
if (serialized.length > 0) {
return renderReactNode(realm, serialized);
}
return realm.intrinsics.null;
}
function createOpenTagMarkup(realm, tagVerbatim, tagLowercase, propsValue, namespace, makeStaticMarkup, isRootElement, htmlEscapeHelper) {
let ret = ["<" + tagVerbatim];
if (propsValue instanceof _index.ObjectValue && !propsValue.isPartialObject()) {
for (let [propName, binding] of propsValue.properties) {
if (binding.descriptor !== undefined) {
let propValue = (0, _utils.getProperty)(realm, propsValue, propName);
if (propValue === realm.intrinsics.null || propValue === realm.intrinsics.undefined) {
continue;
}
if (propName === _domConfig.STYLE) {
propValue = createMarkupForStyles(realm, propValue);
}
let markup;
if ((0, _utils2.isCustomComponent)(realm, tagLowercase, propsValue)) {
if (!_domConfig.RESERVED_PROPS.has(propName)) {
markup = createMarkupForCustomAttribute(realm, propName, propValue);
}
} else {
markup = createMarkupForProperty(realm, propName, propValue, htmlEscapeHelper);
}
if (Array.isArray(markup)) {
ret.push(" ", ...markup);
} else if (typeof markup === "string" && markup !== "") {
ret.push(" " + markup);
} else if (markup) {
ret.push(" ", markup);
}
}
}
} else {
(0, _invariant.default)(false, "TODO");
} // For static pages, no need to put React ID and checksum. Saves lots of
// bytes.
if (makeStaticMarkup) {
return ret;
}
if (isRootElement) {
ret.push(" " + (0, _utils2.createMarkupForRoot)());
}
return ret;
}
function renderReactNode(realm, reactNode) {
let normalizedNode = (0, _utils2.normalizeNode)(realm, reactNode);
if (typeof normalizedNode === "string") {
return new _index.StringValue(realm, normalizedNode);
} else if (normalizedNode instanceof _index.AbstractValue) {
return normalizedNode;
}
(0, _invariant.default)(Array.isArray(normalizedNode));
let args = [];
let quasis = [];
let lastWasAbstract = false;
for (let element of normalizedNode) {
if (typeof element === "string") {
lastWasAbstract = false;
quasis.push(t.templateElement({
raw: element,
cooked: element
}));
} else {
if (lastWasAbstract) {
quasis.push(t.templateElement({
raw: "",
cooked: ""
}));
}
lastWasAbstract = true;
(0, _invariant.default)(element instanceof _index.Value);
args.push(element);
}
}
let val = _index.AbstractValue.createFromBuildFunction(realm, _index.StringValue, args, (0, _generator.createOperationDescriptor)("REACT_SSR_TEMPLATE_LITERAL", {
quasis
}));
(0, _invariant.default)(val instanceof _index.AbstractValue);
return val;
}
class ReactDOMServerRenderer {
constructor(realm, makeStaticMarkup) {
this.realm = realm;
this.makeStaticMarkup = makeStaticMarkup;
this.previousWasTextNode = false;
this.htmlEscapeHelper = (0, _utils2.createHtmlEscapeHelper)(realm);
this.arrayHelper = (0, _utils2.createArrayHelper)(realm);
}
render(value, namespace = "html", depth = 0) {
let rootReactNode = this._renderValue(value, namespace, depth);
return renderReactNode(this.realm, rootReactNode);
}
_renderText(value) {
let text = value.value + "";
if (text === "") {
return "";
}
if (this.makeStaticMarkup) {
return (0, _utils2.escapeHtml)(text);
}
if (this.previousWasTextNode) {
return "<!-- -->" + (0, _utils2.escapeHtml)(text);
}
this.previousWasTextNode = true;
return (0, _utils2.escapeHtml)(text);
}
_renderAbstractConditionalValue(condValue, consequentVal, alternateVal, namespace, depth) {
let val = this.realm.evaluateWithAbstractConditional(condValue, () => {
return this.realm.evaluateForEffects(() => this.render(consequentVal, namespace, depth), null, "_renderAbstractConditionalValue consequent");
}, () => {
return this.realm.evaluateForEffects(() => this.render(alternateVal, namespace, depth), null, "_renderAbstractConditionalValue consequent");
});
return (0, _utils2.convertValueToNode)(val);
}
_renderAbstractValue(value, namespace, depth) {
if (value.kind === "conditional") {
let [condValue, consequentVal, alternateVal] = value.args;
(0, _invariant.default)(condValue instanceof _index.AbstractValue);
return this._renderAbstractConditionalValue(condValue, consequentVal, alternateVal, namespace, depth);
} else {
return renderValueWithHelper(this.realm, value, this.htmlEscapeHelper);
}
}
_renderArrayValue(arrayValue, namespace, depth) {
if (_index.ArrayValue.isIntrinsicAndHasWidenedNumericProperty(arrayValue)) {
let nestedOptimizedFunctionEffects = arrayValue.nestedOptimizedFunctionEffects;
if (nestedOptimizedFunctionEffects !== undefined) {
for (let [func, effects] of nestedOptimizedFunctionEffects) {
let funcCall = () => {
let result = effects.result;
this.realm.applyEffects(effects);
if (result instanceof _completions.SimpleNormalCompletion) {
result = result.value;
}
(0, _invariant.default)(result instanceof _index.Value);
return this.render(result, namespace, depth);
};
let pureFuncCall = () => this.realm.evaluatePure(funcCall,
/*bubbles*/
true, () => {
(0, _invariant.default)(false, "SSR _renderArrayValue side-effect should have been caught in main React reconciler");
});
let resolvedEffects;
resolvedEffects = this.realm.evaluateForEffects(pureFuncCall,
/*state*/
null, `react SSR resolve nested optimized closure`);
nestedOptimizedFunctionEffects.set(func, resolvedEffects);
this.realm.collectedNestedOptimizedFunctionEffects.set(func, resolvedEffects);
}
return renderValueWithHelper(this.realm, arrayValue, this.arrayHelper);
}
}
let elements = [];
(0, _utils.forEachArrayValue)(this.realm, arrayValue, elementValue => {
let renderedElement = this._renderValue(elementValue, namespace, depth);
if (Array.isArray(renderedElement)) {
elements.push(...renderedElement);
} else {
elements.push(renderedElement);
}
}); // $FlowFixMe: flow gets confused here
return elements;
}
_renderReactElement(reactElement, namespace, depth) {
let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type");
let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props");
(0, _invariant.default)(propsValue instanceof _index.AbstractObjectValue || propsValue instanceof _index.ObjectValue);
if (typeValue instanceof _index.StringValue) {
let type = typeValue.value;
let tag = type.toLowerCase();
if (tag === "input") {
let defaultValueProp = (0, _utils.getProperty)(this.realm, propsValue, "defaultValue");
let defaultCheckedProp = (0, _utils.getProperty)(this.realm, propsValue, "defaultChecked");
let valueProp = (0, _utils.getProperty)(this.realm, propsValue, "value");
let checkedProp = (0, _utils.getProperty)(this.realm, propsValue, "checked");
let newProps = new _index.ObjectValue(this.realm, this.realm.intrinsics.ObjectPrototype);
_singletons.Properties.Set(this.realm, newProps, "type", this.realm.intrinsics.undefined, true);
let inputProps = new _index.ObjectValue(this.realm, this.realm.intrinsics.ObjectPrototype);
_singletons.Properties.Set(this.realm, inputProps, "defaultChecked", this.realm.intrinsics.undefined, true);
_singletons.Properties.Set(this.realm, inputProps, "defaultValue", this.realm.intrinsics.undefined, true);
_singletons.Properties.Set(this.realm, inputProps, "value", valueProp !== this.realm.intrinsics.null ? valueProp : defaultValueProp, true);
_singletons.Properties.Set(this.realm, inputProps, "checked", checkedProp !== this.realm.intrinsics.null ? checkedProp : defaultCheckedProp, true);
(0, _utils.applyObjectAssignConfigsForReactElement)(this.realm, newProps, [propsValue, inputProps]);
propsValue = newProps;
} else if (tag === "textarea") {
let initialValue = (0, _utils.getProperty)(this.realm, propsValue, "value");
if (initialValue === this.realm.intrinsics.null) {
(0, _invariant.default)(false, "TODO");
}
let newProps = new _index.ObjectValue(this.realm, this.realm.intrinsics.ObjectPrototype);
let textareaProps = new _index.ObjectValue(this.realm, this.realm.intrinsics.ObjectPrototype);
_singletons.Properties.Set(this.realm, textareaProps, "value", this.realm.intrinsics.undefined, true);
_singletons.Properties.Set(this.realm, textareaProps, "children", initialValue, true);
(0, _utils.applyObjectAssignConfigsForReactElement)(this.realm, newProps, [propsValue, textareaProps]);
propsValue = newProps;
} else if (tag === "select") {
(0, _invariant.default)(false, "TODO");
} else if (tag === "option") {
(0, _invariant.default)(false, "TODO");
}
let out = createOpenTagMarkup(this.realm, type, tag, propsValue, namespace, this.makeStaticMarkup, depth === 0, this.htmlEscapeHelper);
let footer = "";
if (_domConfig.omittedCloseTags.has(tag)) {
out.push("/>");
} else {
out.push(">");
footer = "</" + type + ">";
}
let innerMarkup = (0, _utils2.getNonChildrenInnerMarkup)(this.realm, propsValue);
if (innerMarkup instanceof _index.StringValue) {
if (_domConfig.newlineEatingTags[tag] && innerMarkup.value.charAt(0) === "\n") {
out.push("\n");
}
out.push(innerMarkup.value);
} else if (innerMarkup instanceof _index.ObjectValue) {
(0, _invariant.default)(false, "TODO");
} else {
this.previousWasTextNode = false;
let childrenValue = (0, _utils.getProperty)(this.realm, propsValue, "children");
let childrenOut = this._renderValue(childrenValue, namespace, depth + 1);
if (Array.isArray(childrenOut)) {
out.push(...childrenOut);
} else {
out.push(childrenOut);
}
}
out.push(footer);
this.previousWasTextNode = false;
return out;
} else if (typeValue instanceof _index.SymbolValue && typeValue === (0, _utils.getReactSymbol)("react.fragment", this.realm)) {
let childrenValue = (0, _utils.getProperty)(this.realm, propsValue, "children");
let childrenOut = this._renderValue(childrenValue, namespace, depth + 1);
let out = [];
if (Array.isArray(childrenOut)) {
out.push(...childrenOut);
} else {
out.push(childrenOut);
}
this.previousWasTextNode = false;
return out;
} else {
(0, _invariant.default)(false, "TODO");
}
}
_renderValue(value, namespace, depth) {
if (value instanceof _index.StringValue || value instanceof _index.NumberValue) {
return this._renderText(value);
} else if (value instanceof _index.ObjectValue && (0, _utils.isReactElement)(value)) {
return this._renderReactElement(value, namespace, depth);
} else if (value instanceof _index.AbstractValue) {
return this._renderAbstractValue(value, namespace, depth);
} else if (value instanceof _index.ArrayValue) {
return this._renderArrayValue(value, namespace, depth);
} else if (value instanceof _index.BooleanValue || value instanceof _index.UndefinedValue || value instanceof _index.NullValue) {
return "";
}
(0, _invariant.default)(false, "TODO");
}
}
function renderToString(realm, reactElement, staticMarkup) {
let reactStatistics = new _types.ReactStatistics();
let alreadyEvaluated = new Map();
let reconciler = new _reconcilation.Reconciler(realm, {
firstRenderOnly: true,
isRoot: true,
modelString: undefined
}, alreadyEvaluated, reactStatistics);
let typeValue = (0, _utils.getProperty)(realm, reactElement, "type");
let propsValue = (0, _utils.getProperty)(realm, reactElement, "props");
let evaluatedRootNode = (0, _utils.createReactEvaluatedNode)("ROOT", (0, _utils.getComponentName)(realm, typeValue));
(0, _invariant.default)(typeValue instanceof _index.ECMAScriptSourceFunctionValue);
if (propsValue instanceof _index.AbstractValue && !(propsValue instanceof _index.AbstractObjectValue)) {
propsValue = _singletons.To.ToObject(realm, propsValue);
}
(0, _invariant.default)(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue);
let effects = reconciler.resolveReactComponentTree(typeValue, propsValue, null, evaluatedRootNode);
(0, _invariant.default)(realm.generator); // create a single regex used for the escape functions
// by hoisting it, it gets cached by the VM JITs
realm.generator.emitStatement([], (0, _generator.createOperationDescriptor)("REACT_SSR_REGEX_CONSTANT"));
(0, _invariant.default)(realm.generator);
realm.generator.emitStatement([], (0, _generator.createOperationDescriptor)("REACT_SSR_PREV_TEXT_NODE"));
(0, _invariant.default)(effects);
realm.applyEffects(effects);
(0, _invariant.default)(effects.result instanceof _completions.SimpleNormalCompletion);
let serverRenderer = new ReactDOMServerRenderer(realm, staticMarkup);
let renderValue = serverRenderer.render(effects.result.value);
return renderValue;
}
//# sourceMappingURL=rendering.js.map