UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

1,067 lines (831 loc) 53.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Reconciler = void 0; var _realm = require("../realm.js"); var _index = require("../values/index.js"); var _types = require("../serializer/types.js"); var _utils = require("./utils.js"); var _index2 = require("../methods/index.js"); var _invariant = _interopRequireDefault(require("../invariant.js")); var _singletons = require("../singletons.js"); var _errors = require("../errors.js"); var _branching = require("./branching.js"); var _completions = require("../completions.js"); var _components = require("./components.js"); var _errors2 = require("./errors.js"); var _elements = require("./elements.js"); var _logger = require("../utils/logger.js"); var _utils2 = require("../serializer/utils.js"); var _generator = require("../utils/generator.js"); var _descriptors = require("../descriptors.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * 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. */ function setContextCurrentValue(contextObject, value) { if (contextObject instanceof _index.AbstractObjectValue && !contextObject.values.isTop()) { let elements = contextObject.values.getElements(); if (elements && elements.size === 1) { for (let element of elements) { (0, _invariant.default)(element instanceof _index.ObjectValue); contextObject = element; } } else { (0, _invariant.default)(false, "TODO: deal with multiple possible context objects"); } } if (!(contextObject instanceof _index.ObjectValue)) { throw new _errors2.ExpectedBailOut("cannot set currentValue on an abstract context consumer"); } let binding = contextObject.properties.get("currentValue"); if (binding && binding.descriptor) { (0, _invariant.default)(binding.descriptor instanceof _descriptors.PropertyDescriptor); binding.descriptor.value = value; } else { (0, _invariant.default)(false, "setContextCurrentValue failed to set the currentValue"); } } function throwUnsupportedSideEffectError(msg) { throw new _errors2.UnsupportedSideEffect(msg); } class Reconciler { constructor(realm, componentTreeConfig, alreadyEvaluated, statistics, logger) { this.realm = realm; this.statistics = statistics; this.logger = logger; this.componentTreeConfig = componentTreeConfig; this.componentTreeState = this._createComponentTreeState(); this.alreadyEvaluated = alreadyEvaluated; this.branchedComponentTrees = []; } resolveReactComponentTree(componentType, props, context, evaluatedRootNode) { const resolveComponentTree = () => { try { let initialProps = props || (0, _components.getInitialProps)(this.realm, componentType, this.componentTreeConfig); let initialContext = context || (0, _components.getInitialContext)(this.realm, componentType); let { result } = this._resolveComponent(componentType, initialProps, initialContext, "ROOT", evaluatedRootNode); this.statistics.optimizedTrees++; return result; } catch (error) { if (error instanceof _completions.AbruptCompletion) throw error; this._handleComponentTreeRootFailure(error, evaluatedRootNode); // flow belives we can get here, when it should never be possible (0, _invariant.default)(false, "resolveReactComponentTree error not handled correctly"); } }; try { this.realm.react.activeReconciler = this; return this.realm.wrapInGlobalEnv(() => this.realm.evaluatePure(() => this.realm.evaluateForEffects(resolveComponentTree, /*state*/ null, `react component: ${(0, _utils.getComponentName)(this.realm, componentType)}`), /*bubbles*/ true, (sideEffectType, binding, expressionLocation) => { if (this.realm.react.failOnUnsupportedSideEffects) { (0, _utils2.handleReportedSideEffect)(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation); } })); } finally { this.realm.react.activeReconciler = undefined; } } clearComponentTreeState() { this.componentTreeState = this._createComponentTreeState(); } _queueNewComponentTree(rootValue, evaluatedNode, props = null, context = null) { if (rootValue instanceof _index.SymbolValue) { return; } (0, _invariant.default)(rootValue instanceof _index.ECMAScriptSourceFunctionValue || rootValue instanceof _index.AbstractValue); this.componentTreeState.deadEnds++; let componentType = (0, _utils.getComponentTypeFromRootValue)(this.realm, rootValue); if (componentType !== null && !this.alreadyEvaluated.has(componentType)) { this.branchedComponentTrees.push({ context, evaluatedNode, props, rootValue }); } } _resolveComplexClassComponent(componentType, props, context, classMetadata, branchStatus, evaluatedNode) { if (branchStatus !== "ROOT") { // if the tree is simple and we're not in a branch, we can make this tree complex // and make this complex component the root let evaluatedComplexNode = this.alreadyEvaluated.get(componentType); if (branchStatus === "NO_BRANCH" && this.componentTreeState.status === "SIMPLE" && evaluatedComplexNode && evaluatedComplexNode.status !== "RENDER_PROPS") { this.componentTreeState.componentType = componentType; } else { this._queueNewComponentTree(componentType, evaluatedNode); evaluatedNode.status = "NEW_TREE"; throw new _errors2.NewComponentTreeBranch(evaluatedNode); } } this.componentTreeState.status = "COMPLEX"; // create a new instance of this React class component let instance = (0, _components.createClassInstance)(this.realm, componentType, props, context, classMetadata); // get the "render" method off the instance let renderMethod = (0, _index2.Get)(this.realm, instance, "render"); (0, _invariant.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue); // the render method doesn't have any arguments, so we just assign the context of "this" to be the instance return (0, _utils.getValueFromFunctionCall)(this.realm, renderMethod, instance, []); } _resolveSimpleClassComponent(componentType, props, context, branchStatus, evaluatedNode) { // create a new simple instance of this React class component let instance = (0, _components.createSimpleClassInstance)(this.realm, componentType, props, context); // get the "render" method off the instance let renderMethod = (0, _index2.Get)(this.realm, instance, "render"); (0, _invariant.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue); // the render method doesn't have any arguments, so we just assign the context of "this" to be the instance return (0, _utils.getValueFromFunctionCall)(this.realm, renderMethod, instance, []); } _resolveFunctionalComponent(componentType, props, context, evaluatedNode) { return (0, _utils.getValueFromFunctionCall)(this.realm, componentType, this.realm.intrinsics.undefined, [props, context]); } _getClassComponentMetadata(componentType, props, context) { if (this.realm.react.classComponentMetadata.has(componentType)) { let classMetadata = this.realm.react.classComponentMetadata.get(componentType); (0, _invariant.default)(classMetadata); return classMetadata; } // get all this assignments in the constructor let classMetadata = (0, _components.evaluateClassConstructor)(this.realm, componentType, props, context); this.realm.react.classComponentMetadata.set(componentType, classMetadata); return classMetadata; } _resolveContextProviderComponent(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NORMAL", "Context.Provider"); evaluatedNode.children.push(evaluatedChildNode); this.statistics.componentsEvaluated++; (0, _invariant.default)(typeValue instanceof _index.ObjectValue || typeValue instanceof _index.AbstractObjectValue); const contextConsumer = (0, _utils.getProperty)(this.realm, typeValue, "context"); (0, _invariant.default)(contextConsumer instanceof _index.ObjectValue || contextConsumer instanceof _index.AbstractObjectValue); let lastValueProp = (0, _utils.getProperty)(this.realm, contextConsumer, "currentValue"); this._incremementReferenceForContextNode(contextConsumer); let valueProp; // if we have a value prop, set it if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) { valueProp = (0, _index2.Get)(this.realm, propsValue, "value"); setContextCurrentValue(contextConsumer, valueProp); } if (propsValue instanceof _index.ObjectValue) { // if the value is abstract, we need to keep the render prop as unless // we are in firstRenderOnly mode, where we can just inline the abstract value if (!(valueProp instanceof _index.AbstractValue) || this.componentTreeConfig.firstRenderOnly) { let resolvedReactElement = this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, evaluatedChildNode); let resolvedPropsValue = (0, _utils.getProperty)(this.realm, resolvedReactElement, "props"); (0, _invariant.default)(resolvedPropsValue instanceof _index.ObjectValue || resolvedPropsValue instanceof _index.AbstractObjectValue); (0, _invariant.default)(lastValueProp instanceof _index.Value); setContextCurrentValue(contextConsumer, lastValueProp); this._decremementReferenceForContextNode(contextConsumer); // if we no dead ends, we know the rest of the tree and can safely remove the provider if (this.componentTreeState.deadEnds === 0) { let childrenValue = (0, _index2.Get)(this.realm, resolvedPropsValue, "children"); evaluatedChildNode.status = "INLINED"; this.statistics.inlinedComponents++; return childrenValue; } return resolvedReactElement; } } let children = this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, evaluatedChildNode); setContextCurrentValue(contextConsumer, lastValueProp); this._decremementReferenceForContextNode(contextConsumer); return children; } _decremementReferenceForContextNode(contextNode) { let references = this.componentTreeState.contextNodeReferences.get(contextNode); if (!references) { references = 0; } else { references--; } this.componentTreeState.contextNodeReferences.set(contextNode, references); } _incremementReferenceForContextNode(contextNode) { let references = this.componentTreeState.contextNodeReferences.get(contextNode); if (!references) { references = 1; } else { references++; } this.componentTreeState.contextNodeReferences.set(contextNode, references); } _isContextValueKnown(contextNode) { if (this.componentTreeConfig.isRoot) { return true; } if (this.componentTreeState.contextNodeReferences.has(contextNode)) { let references = this.componentTreeState.contextNodeReferences.get(contextNode); if (!references) { return false; } return references > 0; } return false; } _resolveContextConsumerComponent(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("RENDER_PROPS", "Context.Consumer"); evaluatedNode.children.push(evaluatedChildNode); if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) { // get the "render" prop child off the instance if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("children")) { let renderProp = (0, _utils.getProperty)(this.realm, propsValue, "children"); this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); if (renderProp instanceof _index.ECMAScriptSourceFunctionValue) { if (typeValue instanceof _index.ObjectValue || typeValue instanceof _index.AbstractObjectValue) { // make sure this context is in our tree if (this._isContextValueKnown(typeValue)) { let valueProp = (0, _index2.Get)(this.realm, typeValue, "currentValue"); // if the value is abstract, we need to keep the render prop as unless // we are in firstRenderOnly mode, where we can just inline the abstract value if (!(valueProp instanceof _index.AbstractValue) || this.componentTreeConfig.firstRenderOnly) { let result = (0, _utils.getValueFromFunctionCall)(this.realm, renderProp, this.realm.intrinsics.undefined, [valueProp]); this.statistics.inlinedComponents++; this.statistics.componentsEvaluated++; evaluatedChildNode.status = "INLINED"; return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode); } } } this._evaluateNestedOptimizedFunctionAndStoreEffects(componentType, context, branchStatus, evaluatedChildNode, renderProp); return; } else { this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES", componentType, context, branchStatus); } } } this.componentTreeState.deadEnds++; return; } _resolveForwardRefComponent(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let refValue = (0, _utils.getProperty)(this.realm, reactElement, "ref"); (0, _invariant.default)(typeValue instanceof _index.AbstractObjectValue || typeValue instanceof _index.ObjectValue); let forwardedComponent = (0, _utils.getProperty)(this.realm, typeValue, "render"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("FORWARD_REF", (0, _utils.getComponentName)(this.realm, forwardedComponent)); evaluatedNode.children.push(evaluatedChildNode); (0, _invariant.default)(forwardedComponent instanceof _index.ECMAScriptSourceFunctionValue || forwardedComponent instanceof _index.BoundFunctionValue, "expect React.forwardRef() to be passed function value"); let value = (0, _utils.getValueFromFunctionCall)(this.realm, forwardedComponent, this.realm.intrinsics.undefined, [propsValue, refValue]); return this._resolveDeeply(componentType, value, context, branchStatus, evaluatedChildNode); } _resolveRelayQueryRendererComponent(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("RENDER_PROPS", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) { // get the "render" prop if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("render")) { let renderProp = (0, _utils.getProperty)(this.realm, propsValue, "render"); if (renderProp instanceof _index.ECMAScriptSourceFunctionValue) { this._evaluateNestedOptimizedFunctionAndStoreEffects(componentType, context, branchStatus, evaluatedChildNode, renderProp); } else if (renderProp instanceof _index.AbstractValue) { this._findReactComponentTrees(renderProp, evaluatedChildNode, "NESTED_CLOSURES", componentType, context, branchStatus); } } this._findReactComponentTrees(propsValue, evaluatedChildNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); return; } // this is the worst case, we were unable to find the render prop function // and won't be able to find any further components to evaluate as trees // because of that this.componentTreeState.deadEnds++; } _resolveClassComponent(componentType, props, context, branchStatus, evaluatedNode) { let value; let classMetadata = this._getClassComponentMetadata(componentType, props, context); let { instanceProperties, instanceSymbols } = classMetadata; // if there were no this assignments we can try and render it as a simple class component if (instanceProperties.size === 0 && instanceSymbols.size === 0) { // We first need to know what type of class component we're dealing with. // A "simple" class component is defined as: // // - having only a "render" method // - having no lifecycle events // - having no state // - having no instance variables // // the only things a class component should be able to access on "this" are: // - this.props // - this.context // - this._someRenderMethodX() etc // // Otherwise, the class component is a "complex" one. // To begin with, we don't know what type of component it is, so we try and render it as if it were // a simple component using the above heuristics. If an error occurs during this process, we assume // that the class wasn't simple, then try again with the "complex" heuristics. try { value = this._resolveSimpleClassComponent(componentType, props, context, branchStatus, evaluatedNode); } catch (error) { // if we get back a SimpleClassBailOut error, we know that this class component // wasn't a simple one and is likely to be a complex class component instead if (error instanceof _errors2.SimpleClassBailOut) {// the component was not simple, so we continue with complex case } else { // else we rethrow the error throw error; } } } // handle the complex class component if there is not value if (value === undefined) { value = this._resolveComplexClassComponent(componentType, props, context, classMetadata, branchStatus, evaluatedNode); } return value; } _resolveClassComponentForFirstRenderOnly(componentType, props, context, branchStatus, evaluatedNode) { // create a new simple instance of this React class component let instance = (0, _components.createClassInstanceForFirstRenderOnly)(this.realm, componentType, props, context, evaluatedNode); let getDerivedStateFromProps = (0, _index2.Get)(this.realm, componentType, "getDerivedStateFromProps"); let getSnapshotBeforeUpdate = (0, _index2.Get)(this.realm, instance, "getSnapshotBeforeUpdate"); // if either getDerivedStateFromProps or getSnapshotBeforeUpdate exist, then // we don't try and execute componentWillMount and UNSAFE_componentWillMount if (getDerivedStateFromProps !== this.realm.intrinsics.undefined || getSnapshotBeforeUpdate !== this.realm.intrinsics.undefined) { if (getDerivedStateFromProps instanceof _index.ECMAScriptSourceFunctionValue && getDerivedStateFromProps.$Call) { (0, _components.applyGetDerivedStateFromProps)(this.realm, getDerivedStateFromProps, instance, props); } } else { // get the "componentWillMount" and "render" methods off the instance let componentWillMount = (0, _index2.Get)(this.realm, instance, "componentWillMount"); if (componentWillMount instanceof _index.ECMAScriptSourceFunctionValue && componentWillMount.$Call) { componentWillMount.$Call(instance, []); } let unsafeComponentWillMount = (0, _index2.Get)(this.realm, instance, "UNSAFE_componentWillMount"); if (unsafeComponentWillMount instanceof _index.ECMAScriptSourceFunctionValue && unsafeComponentWillMount.$Call) { unsafeComponentWillMount.$Call(instance, []); } } let renderMethod = (0, _index2.Get)(this.realm, instance, "render"); (0, _invariant.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue); return (0, _utils.getValueFromFunctionCall)(this.realm, renderMethod, instance, []); } _resolveRelayContainer(reactHint, props, context, branchStatus, evaluatedNode) { evaluatedNode.status = "INLINED"; evaluatedNode.message = "RelayContainer"; (0, _invariant.default)(reactHint.firstRenderValue instanceof _index.Value); // for better serialization, ensure context has the right abstract properties defined if ((0, _utils.getProperty)(this.realm, context, "relay") === this.realm.intrinsics.undefined) { let abstractRelayContext = _index.AbstractValue.createAbstractObject(this.realm, "context.relay"); let abstractRelayEnvironment = _index.AbstractValue.createAbstractObject(this.realm, "context.relay.environment"); let abstractRelayInternal = _index.AbstractValue.createAbstractObject(this.realm, "context.relay.environment.unstable_internal"); _singletons.Properties.Set(this.realm, context, "relay", abstractRelayContext, true); _singletons.Properties.Set(this.realm, abstractRelayContext, "environment", abstractRelayEnvironment, true); _singletons.Properties.Set(this.realm, abstractRelayEnvironment, "unstable_internal", abstractRelayInternal, true); } // add contextType to this component this.componentTreeState.contextTypes.add("relay"); return this._resolveComponent(reactHint.firstRenderValue, props, context, branchStatus, evaluatedNode); } _resolveComponent(componentType, props, context, branchStatus, evaluatedNode) { if ((0, _utils.doNotOptimizeComponent)(this.realm, componentType)) { throw new _errors2.DoNotOptimize("__reactCompilerDoNotOptimize flag detected"); } this.statistics.componentsEvaluated++; if ((0, _utils.valueIsKnownReactAbstraction)(this.realm, componentType)) { (0, _invariant.default)(componentType instanceof _index.AbstractValue); let reactHint = this.realm.react.abstractHints.get(componentType); (0, _invariant.default)(reactHint); if (typeof reactHint !== "string" && reactHint.object === this.realm.fbLibraries.reactRelay && this.componentTreeConfig.firstRenderOnly) { return this._resolveRelayContainer(reactHint, props, context, branchStatus, evaluatedNode); } this._queueNewComponentTree(componentType, evaluatedNode); evaluatedNode.status = "NEW_TREE"; evaluatedNode.message = "RelayContainer"; throw new _errors2.NewComponentTreeBranch(evaluatedNode); } (0, _invariant.default)(componentType instanceof _index.ECMAScriptSourceFunctionValue || componentType instanceof _index.BoundFunctionValue); let value; let childContext = context; // first we check if it's a legacy class component if ((0, _utils.valueIsLegacyCreateClassComponent)(this.realm, componentType)) { throw new _errors2.ExpectedBailOut("components created with create-react-class are not supported"); } else if ((0, _utils.valueIsClassComponent)(this.realm, componentType)) { if (this.componentTreeConfig.firstRenderOnly) { value = this._resolveClassComponentForFirstRenderOnly(componentType, props, context, branchStatus, evaluatedNode); } else { value = this._resolveClassComponent(componentType, props, context, branchStatus, evaluatedNode); } } else { value = this._resolveFunctionalComponent(componentType, props, context, evaluatedNode); if ((0, _utils.valueIsFactoryClassComponent)(this.realm, value)) { (0, _invariant.default)(value instanceof _index.ObjectValue); if (branchStatus !== "ROOT") { throw new _errors2.ExpectedBailOut("non-root factory class components are not suppoted"); } else { // TODO support factory components return { result: value, childContext }; } } } (0, _invariant.default)(value !== undefined); return { result: this._resolveDeeply(componentType, value, context, branchStatus === "ROOT" ? "NO_BRANCH" : branchStatus, evaluatedNode), childContext }; } _createComponentTreeState() { return { componentType: undefined, contextTypes: new Set(), deadEnds: 0, status: "SIMPLE", contextNodeReferences: new Map() }; } _getComponentResolutionStrategy(value) { // check if it's a ReactRelay.QueryRenderer if (this.realm.fbLibraries.reactRelay !== undefined) { let QueryRenderer = (0, _utils.getProperty)(this.realm, this.realm.fbLibraries.reactRelay, "QueryRenderer"); if (value === QueryRenderer) { return "RELAY_QUERY_RENDERER"; } } if (value === (0, _utils.getReactSymbol)("react.fragment", this.realm)) { return "FRAGMENT"; } if ((value instanceof _index.ObjectValue || value instanceof _index.AbstractObjectValue) && value.kind !== "conditional") { let $$typeof = (0, _utils.getProperty)(this.realm, value, "$$typeof"); if ($$typeof === (0, _utils.getReactSymbol)("react.context", this.realm)) { return "CONTEXT_CONSUMER"; } if ($$typeof === (0, _utils.getReactSymbol)("react.provider", this.realm)) { return "CONTEXT_PROVIDER"; } if ($$typeof === (0, _utils.getReactSymbol)("react.forward_ref", this.realm)) { return "FORWARD_REF"; } } return "NORMAL"; } _resolveReactDomPortal(createPortalNode, args, componentType, context, branchStatus, evaluatedNode) { let [reactPortalValue, domNodeValue] = args; let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("INLINED", "ReactDOM.createPortal"); let resolvedReactPortalValue = this._resolveDeeply(componentType, reactPortalValue, context, branchStatus, evaluatedChildNode); evaluatedNode.children.push(evaluatedChildNode); if (resolvedReactPortalValue !== reactPortalValue) { this.statistics.inlinedComponents++; let reactDomValue = this.realm.fbLibraries.reactDom; (0, _invariant.default)(reactDomValue instanceof _index.ObjectValue); let reactDomPortalFunc = (0, _utils.getProperty)(this.realm, reactDomValue, "createPortal"); return _index.AbstractValue.createTemporalFromBuildFunction(this.realm, _index.ObjectValue, [reactDomPortalFunc, resolvedReactPortalValue, domNodeValue], (0, _generator.createOperationDescriptor)("REACT_TEMPORAL_FUNC"), { skipInvariant: true, isPure: true }); } return createPortalNode; } _resolveAbstractConditionalValue(componentType, condValue, consequentVal, alternateVal, context, evaluatedNode) { let value = this.realm.evaluateWithAbstractConditional(condValue, () => { return this.realm.evaluateForEffects(() => (0, _branching.wrapReactElementInBranchOrReturnValue)(this.realm, this._resolveDeeply(componentType, consequentVal, context, "NEW_BRANCH", evaluatedNode)), null, "_resolveAbstractConditionalValue consequent"); }, () => { return this.realm.evaluateForEffects(() => (0, _branching.wrapReactElementInBranchOrReturnValue)(this.realm, this._resolveDeeply(componentType, alternateVal, context, "NEW_BRANCH", evaluatedNode)), null, "_resolveAbstractConditionalValue alternate"); }); if (value instanceof _index.AbstractValue && value.kind === "conditional") { return (0, _branching.getValueWithBranchingLogicApplied)(this.realm, consequentVal, alternateVal, value); } return value; } _resolveAbstractLogicalValue(componentType, value, context, evaluatedNode) { let [leftValue, rightValue] = value.args; let operator = value.kind; (0, _invariant.default)(leftValue instanceof _index.AbstractValue); if (operator === "||") { return this._resolveAbstractConditionalValue(componentType, leftValue, leftValue, rightValue, context, evaluatedNode); } else { return this._resolveAbstractConditionalValue(componentType, leftValue, rightValue, leftValue, context, evaluatedNode); } } _resolveAbstractValue(componentType, value, context, branchStatus, evaluatedNode) { (0, _invariant.default)(this.realm.generator); // TODO investigate what other kinds than "conditional" might be safe to deeply resolve if (value.kind === "conditional") { let [condValue, consequentVal, alternateVal] = value.args; (0, _invariant.default)(condValue instanceof _index.AbstractValue); return this._resolveAbstractConditionalValue(componentType, condValue, consequentVal, alternateVal, context, evaluatedNode); } else if (value.kind === "||" || value.kind === "&&") { return this._resolveAbstractLogicalValue(componentType, value, context, evaluatedNode); } else { if (value instanceof _index.AbstractValue && this.realm.react.abstractHints.has(value)) { let reactHint = this.realm.react.abstractHints.get(value); (0, _invariant.default)(reactHint !== undefined); if (reactHint.object === this.realm.fbLibraries.reactDom && reactHint.propertyName === "createPortal") { return this._resolveReactDomPortal(value, reactHint.args, componentType, context, branchStatus, evaluatedNode); } } this.componentTreeState.deadEnds++; } return value; } _resolveUnknownComponentType(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); if (typeValue instanceof _index.AbstractValue) { this._findReactComponentTrees(typeValue, evaluatedNode, "FUNCTIONAL_COMPONENTS", componentType, context, branchStatus); return reactElement; } else { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); let bailOutMessage = `type on <Component /> was not a ECMAScriptSourceFunctionValue`; evaluatedChildNode.message = bailOutMessage; this._assignBailOutMessage(reactElement, bailOutMessage); this.componentTreeState.deadEnds++; return reactElement; } } _resolveReactElementBadRef(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); let bailOutMessage = `refs are not supported on <Components />`; evaluatedChildNode.message = bailOutMessage; this._queueNewComponentTree(typeValue, evaluatedChildNode); this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); this._assignBailOutMessage(reactElement, bailOutMessage); return reactElement; } _resolveReactElementUndefinedRender(componentType, reactElement, context, branchStatus, evaluatedNode) { let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); let bailOutMessage = `undefined was returned from render`; evaluatedChildNode.message = bailOutMessage; this._assignBailOutMessage(reactElement, bailOutMessage); this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); return reactElement; } _resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, evaluatedNode) { let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); // terminal host component. Start evaluating its children. if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("children")) { let childrenValue = (0, _index2.Get)(this.realm, propsValue, "children"); if (childrenValue instanceof _index.Value) { let resolvedChildren = this._resolveDeeply(componentType, childrenValue, context, branchStatus, evaluatedNode, false); // we can optimize further and flatten arrays on non-composite components if (resolvedChildren instanceof _index.ArrayValue && !resolvedChildren.intrinsicName) { resolvedChildren = (0, _utils.flattenChildren)(this.realm, resolvedChildren, true); } if (resolvedChildren !== childrenValue) { let newProps = (0, _utils.cloneProps)(this.realm, propsValue, resolvedChildren); // This is safe to do as we clone a new ReactElement as part of reconcilation // so we will never be mutating an object used by something else. Furthermore, // the ReactElement is "immutable" so it can never change and only React controls // this object. (0, _utils.hardModifyReactObjectPropertyBinding)(this.realm, reactElement, "props", newProps); } } } return reactElement; } _resolveFragmentComponent(componentType, reactElement, context, branchStatus, evaluatedNode) { this.statistics.componentsEvaluated++; if (this.componentTreeConfig.firstRenderOnly) { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("INLINED", "React.Fragment"); evaluatedNode.children.push(evaluatedChildNode); this.statistics.inlinedComponents++; let children = this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, evaluatedChildNode); return children; } else { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NORMAL", "React.Fragment"); evaluatedNode.children.push(evaluatedChildNode); return this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, evaluatedChildNode); } } _resolveReactElement(componentType, reactElement, context, branchStatus, evaluatedNode, needsKey) { // We create a clone of the ReactElement to be safe. This is because the same // ReactElement might be a temporal referenced in other effects and also it allows us to // easily mutate and swap the props of the ReactElement with the optimized version with // resolved/inlined children. // Note: We used to sanitize out props for firstRender here, we now do this during serialization. reactElement = (0, _utils.cloneReactElement)(this.realm, reactElement, false); let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); let refValue = (0, _utils.getProperty)(this.realm, reactElement, "ref"); let keyValue = (0, _utils.getProperty)(this.realm, reactElement, "key"); (0, _invariant.default)(!(typeValue instanceof _index.AbstractValue && typeValue.kind === "conditional"), `the reconciler should never encounter a ReactElement "type" that is conditional abstract value`); (0, _invariant.default)(!(propsValue instanceof _index.AbstractValue && propsValue.kind === "conditional"), `the reconciler should never encounter a ReactElement "props" that is conditional abstract value`); if (typeValue instanceof _index.StringValue) { return this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, evaluatedNode); } if (!(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue)) { this._assignBailOutMessage(reactElement, `props on <Component /> was not not an ObjectValue or an AbstractObjectValue`); return reactElement; } let componentResolutionStrategy = this._getComponentResolutionStrategy(typeValue); // We do not support "ref" on <Component /> ReactElements, unless it's a forwarded ref // or we are firstRenderOnly mode (in which case, we ignore the ref) if (!this.componentTreeConfig.firstRenderOnly && !(refValue instanceof _index.NullValue) && componentResolutionStrategy !== "FORWARD_REF" && // If we have an abstract value, it might mean a bad ref, but we will have // already thrown a FatalError in the createElement implementation by this // point, so if we're here, then the FatalError has been recovered explicitly !(refValue instanceof _index.AbstractValue)) { this._resolveReactElementBadRef(componentType, reactElement, context, branchStatus, evaluatedNode); } try { let result; switch (componentResolutionStrategy) { case "NORMAL": { if (!(typeValue instanceof _index.ECMAScriptSourceFunctionValue || typeValue instanceof _index.BoundFunctionValue || (0, _utils.valueIsKnownReactAbstraction)(this.realm, typeValue))) { return this._resolveUnknownComponentType(componentType, reactElement, context, branchStatus, evaluatedNode); } let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("INLINED", (0, _utils.getComponentName)(this.realm, typeValue)); let render = this._resolveComponent(typeValue, propsValue, context, branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus, evaluatedChildNode); if (this.logger !== undefined && this.realm.react.verbose && evaluatedChildNode.status === "INLINED") { this.logger.logInformation(` ✔ ${evaluatedChildNode.name} (inlined)`); } evaluatedNode.children.push(evaluatedChildNode); result = render.result; this.statistics.inlinedComponents++; break; } case "FRAGMENT": { result = this._resolveFragmentComponent(componentType, reactElement, context, branchStatus, evaluatedNode); break; } case "RELAY_QUERY_RENDERER": { (0, _invariant.default)(typeValue instanceof _index.AbstractObjectValue); result = this._resolveRelayQueryRendererComponent(componentType, reactElement, context, branchStatus, evaluatedNode); break; } case "CONTEXT_PROVIDER": { result = this._resolveContextProviderComponent(componentType, reactElement, context, branchStatus, evaluatedNode); break; } case "CONTEXT_CONSUMER": { result = this._resolveContextConsumerComponent(componentType, reactElement, context, branchStatus, evaluatedNode); break; } case "FORWARD_REF": { result = this._resolveForwardRefComponent(componentType, reactElement, context, branchStatus, evaluatedNode); break; } default: (0, _invariant.default)(false, "unsupported component resolution strategy"); } if (result === undefined) { result = reactElement; } if (result instanceof _index.UndefinedValue) { return this._resolveReactElementUndefinedRender(componentType, reactElement, context, branchStatus, evaluatedNode); } // If we have a new result and we might have a key value then wrap our inlined result in a // `<React.Fragment key={keyValue}>` so that we may maintain the key. if (!this.componentTreeConfig.firstRenderOnly && needsKey && keyValue.mightNotBeNull()) { result = (0, _elements.wrapReactElementWithKeyedFragment)(this.realm, keyValue, result); } return result; } catch (error) { if (error instanceof _completions.AbruptCompletion) throw error; return this._resolveComponentResolutionFailure(componentType, error, reactElement, context, evaluatedNode, branchStatus); } } _handleComponentTreeRootFailure(error, evaluatedRootNode) { if (error.name === "Invariant Violation") { throw error; } else if (error instanceof _errors2.ReconcilerFatalError) { throw new _errors2.ReconcilerFatalError(error.message, evaluatedRootNode); } else if (error instanceof _errors2.UnsupportedSideEffect || error instanceof _errors2.DoNotOptimize) { throw new _errors2.ReconcilerFatalError(`Failed to render React component root "${evaluatedRootNode.name}" due to ${error.message}`, evaluatedRootNode); } let message; if (error instanceof _errors2.ExpectedBailOut) { message = `Failed to optimize React component tree for "${evaluatedRootNode.name}" due to an expected bail-out: ${error.message}`; } else if (error instanceof _errors.FatalError) { message = `Failed to optimize React component tree for "${evaluatedRootNode.name}" due to a fatal error during evaluation: ${error.message}`; } else { // if we don't know what the error is, then best to rethrow throw error; } throw new _errors2.ReconcilerFatalError(message, evaluatedRootNode); } _resolveComponentResolutionFailure(componentType, error, reactElement, context, evaluatedNode, branchStatus) { if (error.name === "Invariant Violation") { throw error; } else if (error instanceof _errors2.ReconcilerFatalError) { throw error; } else if (error instanceof _errors2.UnsupportedSideEffect) { throw new _errors2.ReconcilerFatalError(`Failed to render React component "${evaluatedNode.name}" due to ${error.message}`, evaluatedNode); } else if (error instanceof _errors2.DoNotOptimize) { return reactElement; } let typeValue = (0, _utils.getProperty)(this.realm, reactElement, "type"); let propsValue = (0, _utils.getProperty)(this.realm, reactElement, "props"); // assign a bail out message if (error instanceof _errors2.NewComponentTreeBranch) { this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); evaluatedNode.children.push(error.evaluatedNode); // NO-OP (we don't queue a newComponentTree as this was already done) } else { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue)); if (this.logger !== undefined && this.realm.react.verbose) { this.logger.logInformation(` ✖ ${evaluatedChildNode.name} (bail-out)`); } evaluatedNode.children.push(evaluatedChildNode); this._queueNewComponentTree(typeValue, evaluatedChildNode); this._findReactComponentTrees(propsValue, evaluatedNode, "NORMAL_FUNCTIONS", componentType, context, branchStatus); if (error instanceof _errors2.ExpectedBailOut) { evaluatedChildNode.message = error.message; this._assignBailOutMessage(reactElement, error.message); } else if (error instanceof _errors.FatalError) { let message = "evaluation failed"; evaluatedChildNode.message = message; this._assignBailOutMessage(reactElement, message); } else { evaluatedChildNode.message = `unknown error`; throw error; } } // a child component bailed out during component folding, so return the function value and continue return reactElement; } _resolveDeeply(componentType, value, context, branchStatus, evaluatedNode, needsKey) { if (value instanceof _index.StringValue || value instanceof _index.NumberValue || value instanceof _index.BooleanValue || value instanceof _index.NullValue || value instanceof _index.UndefinedValue) { // terminal values return value; } (0, _invariant.default)(!(value instanceof _index.ObjectValue) || value._isFinal !== undefined, `An object value was detected during React reconcilation without its bindings properly applied`); if (value instanceof _index.AbstractValue) { return this._resolveAbstractValue(componentType, value, context, branchStatus, evaluatedNode); } else if (value instanceof _index.ArrayValue) { // TODO investigate what about other iterables type objects return this._resolveArray(componentType, value, context, branchStatus, evaluatedNode, needsKey); } else if (value instanceof _index.ObjectValue && (0, _utils.isReactElement)(value)) { return this._resolveReactElement(componentType, value, context, branchStatus, evaluatedNode, needsKey); } // This value is not a valid return value of a render, but given we might be // in a "&&"" condition, it may never result in a runtime error. Still, if it does // result in a runtime error, it would have been the same error before compilation. // See issue #2497 for more context. return value; } _assignBailOutMessage(reactElement, message) { // $BailOutReason is a field on ObjectValue that allows us to specify a message // that gets serialized as a comment node during the ReactElement serialization stage message = `Bail-out: ${message}`; if (reactElement.$BailOutReason !== undefined) { // merge bail out messages if one already exists reactElement.$BailOutReason += `, ${message}`; } else { reactElement.$BailOutReason = message; } } _resolveArray(componentType, arrayValue, context, branchStatus, evaluatedNode, needsKey) { 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; } else { (0, _invariant.default)(false, "TODO support other types of completion"); } (0, _invariant.default)(result instanceof _index.Value); return this._resolveDeeply(componentType, result, context, branchStatus, evaluatedNode, needsKey); }; let pureFuncCall = () => this.realm.evaluatePure(funcCall, /*bubbles*/ true, (sideEffectType, binding, expressionLocation) => (0, _utils2.handleReportedSideEffect)(throwUnsupportedSideEffectError, sideEffectType, binding, expressionLocation)); let resolvedEffects; resolvedEffects = this.realm.evaluateForEffects(pureFuncCall, /*state*/ null, `react resolve nested optimized closure`); this.statistics.optimizedNestedClosures++; nestedOptimizedFunctionEffects.set(func, resolvedEffects); this.realm.collectedNestedOptimizedFunctionEffects.set(func, resolvedEffects); } } return arrayValue; } if (needsKey !== false) needsKey = true; let children = (0, _utils.mapArrayValue)(this.realm, arrayValue, elementValue => this._resolveDeeply(componentType, elementValue, context, "NEW_BRANCH", evaluatedNode, needsKey)); children.makeFinal(); return children; } _findReactComponentTrees(value, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus) { if (value instanceof _index.AbstractValue) { if (value.args.length > 0) { for (let arg of value.args) { this._findReactComponentTrees(arg, evaluatedNode, treatFunctionsAs, componentType, context, branchStatus); } } else { this.componentTreeState.deadEnds++; } } else if ((0, _utils.valueIsKnownReactAbstraction)(this.realm, value)) { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NEW_TREE", (0, _utils.getComponentName)(this.realm, value)); evaluatedNode.children.push(evaluatedChildNode); this._queueNewComponentTree(value, evaluatedChildNode); } else if (value instanceof _index.ECMAScriptSourceFunctionValue || value instanceof _index.BoundFunctionValue) { if ((0, _utils.valueIsClassComponent)(this.realm, value) || treatFunctionsAs === "FUNCTIONAL_COMPONENTS") { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NEW_TREE", (0, _utils.getComponentName)(this.realm, value)); evaluatedNode.children.push(evaluatedChildNode); this._queueNewComponentTree(value, evaluatedChildNode); } else if (treatFunctionsAs === "NESTED_CLOSURES") { (0, _invariant.default)(componentType && context); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("RENDER_PROPS", (0, _utils.getComponentName)(this.realm, value)); this._evaluateNestedOptimizedFunctionAndStoreEffects(componentType, context, branchStatus, evaluatedChildNode, value); } } else if (value instanceof _index.ObjectValue) { if ((0, _utils.isReactElement)(value)) { let typeValue = (0, _utils.getProperty)(this.realm, value, "type