UNPKG

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
"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