UNPKG

prepack

Version:

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

762 lines (690 loc) 36.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Reconciler = undefined; var _realm = require("../realm.js"); var _modules = require("../utils/modules.js"); var _index = require("../values/index.js"); var _types = require("../serializer/types.js"); var _utils = require("./utils"); var _index2 = require("../methods/index.js"); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _singletons = require("../singletons.js"); var _errors = require("../errors.js"); var _branching = require("./branching.js"); var _components = require("./components.js"); var _errors2 = require("./errors.js"); var _completions = require("../completions.js"); var _logger = require("../utils/logger.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. */ class Reconciler { constructor(realm, moduleTracer, statistics, reactSerializerState, componentTreeConfig) { this.realm = realm; this.moduleTracer = moduleTracer; this.statistics = statistics; this.reactSerializerState = reactSerializerState; this.logger = moduleTracer.modules.logger; this.componentTreeState = this._createComponentTreeState(); this.alreadyEvaluatedRootNodes = new Map(); this.componentTreeConfig = componentTreeConfig; } render(componentType, props, context, isRoot, evaluatedRootNode) { const renderComponentTree = () => { // initialProps and initialContext are created from Flow types from: // - if a functional component, the 1st and 2nd paramater of function // - if a class component, use this.props and this.context // if there are no Flow types for props or context, we will throw a // FatalError, unless it's a functional component that has no paramater // i.e let MyComponent = () => <div>Hello world</div> try { let initialProps = props || (0, _components.getInitialProps)(this.realm, componentType); let initialContext = context || (0, _components.getInitialContext)(this.realm, componentType); let { result } = this._renderComponent(componentType, initialProps, initialContext, "ROOT", null, evaluatedRootNode); this.statistics.optimizedTrees++; this.alreadyEvaluatedRootNodes.set(componentType, evaluatedRootNode); return result; } catch (error) { if (error.name === "Invariant Violation") { throw error; } // if we get an error and we're not dealing with the root // rather than throw a FatalError, we log the error as a warning // and continue with the other tree roots // TODO: maybe control what levels gets treated as warning/error? if (!isRoot) { if (error instanceof _completions.AbruptCompletion) { this.logger.logWarning(componentType, `__optimizeReactComponentTree() React component tree (branch) failed due runtime runtime exception thrown`); evaluatedRootNode.status = "ABRUPT_COMPLETION"; } else { this.logger.logWarning(componentType, `__optimizeReactComponentTree() React component tree (branch) failed due to - ${error.message}`); evaluatedRootNode.message = "evaluation failed on new component tree branch"; evaluatedRootNode.status = "BAIL-OUT"; } return this.realm.intrinsics.undefined; } if (error instanceof _errors2.ExpectedBailOut) { let diagnostic = new _errors.CompilerDiagnostic(`__optimizeReactComponentTree() React component tree (root) failed due to - ${error.message}`, this.realm.currentLocation, "PP0020", "FatalError"); this.realm.handleError(diagnostic); if (this.realm.handleError(diagnostic) === "Fail") throw new _errors.FatalError(); } throw error; } }; return this.realm.wrapInGlobalEnv(() => this.realm.evaluatePure(() => // TODO: (sebmarkbage): You could use the return value of this to detect if there are any mutations on objects other // than newly created ones. Then log those to the error logger. That'll help us track violations in // components. :) this.realm.evaluateForEffects(renderComponentTree, /*state*/null, `react component: ${componentType.getName()}`))); } clearComponentTreeState() { this.componentTreeState = this._createComponentTreeState(); } _queueNewComponentTree(rootValue, evaluatedNode, nested = false, props = null, context = null) { (0, _invariant2.default)(rootValue instanceof _index.ECMAScriptSourceFunctionValue || rootValue instanceof _index.AbstractValue); this.componentTreeState.deadEnds++; this.componentTreeState.branchedComponentTrees.push({ context, evaluatedNode, nested, props, rootValue }); } _renderComplexClassComponent(componentType, props, context, classMetadata, branchStatus, branchState, 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.alreadyEvaluatedRootNodes.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(); } } 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, _invariant2.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.getValueFromRenderCall)(this.realm, renderMethod, instance, [], this.componentTreeConfig); } _renderSimpleClassComponent(componentType, props, context, branchStatus, branchState) { // 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, _invariant2.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.getValueFromRenderCall)(this.realm, renderMethod, instance, [], this.componentTreeConfig); } _renderFunctionalComponent(componentType, props, context) { return (0, _utils.getValueFromRenderCall)(this.realm, componentType, this.realm.intrinsics.undefined, [props, context], this.componentTreeConfig); } _getClassComponentMetadata(componentType, props, context) { if (this.realm.react.classComponentMetadata.has(componentType)) { let classMetadata = this.realm.react.classComponentMetadata.get(componentType); (0, _invariant2.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, branchState, evaluatedNode) { let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(this.realm, reactElement, "props"); let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NORMAL", "Context.Provider"); evaluatedNode.children.push(evaluatedChildNode); this.statistics.componentsEvaluated++; (0, _invariant2.default)(typeValue instanceof _index.ObjectValue || typeValue instanceof _index.AbstractObjectValue); const contextConsumer = (0, _index2.Get)(this.realm, typeValue, "context"); (0, _invariant2.default)(contextConsumer instanceof _index.ObjectValue || contextConsumer instanceof _index.AbstractObjectValue); let lastValueProp = (0, _utils.getProperty)(this.realm, contextConsumer, "currentValue"); this._incremementReferenceForContextNode(contextConsumer); const setContextCurrentValue = value => { if (value instanceof _index.Value) { // update the currentValue (0, _utils.setProperty)(this.realm, contextConsumer, "currentValue", value); } }; // if we have a value prop, set it if (propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue) { let valueProp = (0, _index2.Get)(this.realm, propsValue, "value"); setContextCurrentValue(valueProp); } if (this.componentTreeConfig.firstRenderOnly) { if (propsValue instanceof _index.ObjectValue) { this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode); setContextCurrentValue(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, _utils.getProperty)(this.realm, propsValue, "children"); evaluatedChildNode.status = "INLINED"; this.statistics.inlinedComponents++; return childrenValue; } return reactElement; } } let children = this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode); setContextCurrentValue(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); } _hasReferenceForContextNode(contextNode) { 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, branchState, evaluatedNode) { let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(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 let renderProp = (0, _index2.Get)(this.realm, propsValue, "children"); if (renderProp instanceof _index.ECMAScriptSourceFunctionValue && renderProp.$Call) { if (this.componentTreeConfig.firstRenderOnly) { if (typeValue instanceof _index.ObjectValue || typeValue instanceof _index.AbstractObjectValue) { // make sure this context is in our tree if (this._hasReferenceForContextNode(typeValue)) { let valueProp = (0, _index2.Get)(this.realm, typeValue, "currentValue"); let result = (0, _utils.getValueFromRenderCall)(this.realm, renderProp, this.realm.intrinsics.undefined, [valueProp], this.componentTreeConfig); this.statistics.inlinedComponents++; this.statistics.componentsEvaluated++; evaluatedChildNode.status = "INLINED"; return result; } } } // if the render prop function is self contained, we can make it a new component tree root // and this also has a nice side-effect of hoisting the function up to the top scope if ((0, _utils.isRenderPropFunctionSelfContained)(this.realm, componentType, renderProp, this.logger)) { this._queueNewComponentTree(renderProp, evaluatedChildNode, true); return; } else { // we don't have nested additional function support right now // but the render prop is likely to have references to other components // that we need to also evaluate. given we can't find those components return; } } else { this._findReactComponentTrees(propsValue, evaluatedChildNode); return; } } this.componentTreeState.deadEnds++; return; } _resolveRelayQueryRendererComponent(componentType, reactElement, evaluatedNode) { let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(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" method off the instance let renderProp = (0, _index2.Get)(this.realm, propsValue, "render"); if (renderProp instanceof _index.ECMAScriptSourceFunctionValue && renderProp.$Call) { // if the render prop function is self contained, we can make it a new component tree root // and this also has a nice side-effect of hoisting the function up to the top scope if ((0, _utils.isRenderPropFunctionSelfContained)(this.realm, componentType, renderProp, this.logger)) { this._queueNewComponentTree(renderProp, evaluatedChildNode, true); return; } else { // we don't have nested additional function support right now // but the render prop is likely to have references to other components // that we need to also evaluate. given we can't find those components this.componentTreeState.deadEnds++; return; } } else { this._findReactComponentTrees(propsValue, evaluatedChildNode); 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++; return; } _renderClassComponent(componentType, props, context, branchStatus, branchState, 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._renderSimpleClassComponent(componentType, props, context, branchStatus, branchState); } 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._renderComplexClassComponent(componentType, props, context, classMetadata, branchStatus, branchState, evaluatedNode); } return value; } _renderClassComponentForFirstRenderOnly(componentType, props, context, branchStatus, branchState, evaluatedNode) { // create a new simple instance of this React class component let instance = (0, _components.createClassInstanceForFirstRenderOnly)(this.realm, componentType, props, context); // get the "componentWillMount" and "render" methods off the instance let componentWillMount = (0, _index2.Get)(this.realm, instance, "componentWillMount"); let renderMethod = (0, _index2.Get)(this.realm, instance, "render"); if (componentWillMount instanceof _index.ECMAScriptSourceFunctionValue && componentWillMount.$Call) { componentWillMount.$Call(instance, []); } (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue); return (0, _utils.getValueFromRenderCall)(this.realm, renderMethod, instance, [], this.componentTreeConfig); } _renderComponent(componentType, props, context, branchStatus, branchState, evaluatedNode) { this.statistics.componentsEvaluated++; if ((0, _utils.valueIsKnownReactAbstraction)(this.realm, componentType)) { (0, _invariant2.default)(componentType instanceof _index.AbstractValue); this._queueNewComponentTree(componentType, evaluatedNode); evaluatedNode.status = "NEW_TREE"; evaluatedNode.message = "RelayContainer"; throw new _errors2.NewComponentTreeBranch(); } (0, _invariant2.default)(componentType instanceof _index.ECMAScriptSourceFunctionValue); 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._renderClassComponentForFirstRenderOnly(componentType, props, context, branchStatus, branchState, evaluatedNode); } else { value = this._renderClassComponent(componentType, props, context, branchStatus, branchState, evaluatedNode); } } else { value = this._renderFunctionalComponent(componentType, props, context); if ((0, _utils.valueIsFactoryClassComponent)(this.realm, value)) { (0, _invariant2.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, _invariant2.default)(value !== undefined); return { result: this._resolveDeeply(componentType, value, context, branchStatus === "ROOT" ? "NO_BRANCH" : branchStatus, branchState, evaluatedNode), childContext }; } _createComponentTreeState() { return { branchedComponentTrees: [], componentType: undefined, 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, _index2.Get)(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) { let $$typeof = (0, _index2.Get)(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"; } } return "NORMAL"; } _resolveAbstractValue(componentType, value, context, branchStatus, branchState, evaluatedNode) { let length = value.args.length; if (length > 0) { let newBranchState = new _branching.BranchState(); // TODO investigate what other kinds than "conditional" might be safe to deeply resolve for (let i = 0; i < length; i++) { value.args[i] = this._resolveDeeply(componentType, value.args[i], context, "NEW_BRANCH", newBranchState, evaluatedNode); } newBranchState.applyBranchedLogic(this.realm, this.reactSerializerState); } else { this.componentTreeState.deadEnds++; } return value; } _resolveUnknownComponentType(reactElement, evaluatedNode) { let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(this.realm, reactElement, "props"); this._findReactComponentTrees(propsValue, evaluatedNode); if (typeValue instanceof _index.AbstractValue) { this._findReactComponentTrees(typeValue, evaluatedNode); 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(reactElement, evaluatedNode) { let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(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); this._assignBailOutMessage(reactElement, bailOutMessage); return reactElement; } _resolveReactElementUndefinedRender(reactElement, evaluatedNode, branchStatus, branchState) { let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(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); if (branchStatus === "NEW_BRANCH" && branchState) { return branchState.captureBranchedValue(typeValue, reactElement); } return reactElement; } _resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) { let propsValue = (0, _index2.Get)(this.realm, reactElement, "props"); // terminal host component. Start evaluating its children. if (propsValue instanceof _index.ObjectValue && propsValue.properties.has("children")) { let childrenValue = (0, _utils.getProperty)(this.realm, propsValue, "children"); if (childrenValue instanceof _index.Value) { let resolvedChildren = this._resolveDeeply(componentType, childrenValue, context, branchStatus, branchState, evaluatedNode); // we can optimize further and flatten arrays on non-composite components if (resolvedChildren instanceof _index.ArrayValue) { resolvedChildren = (0, _utils.flattenChildren)(this.realm, resolvedChildren); } if (propsValue.properties.has("children")) { propsValue.refuseSerialization = true; _singletons.Properties.Set(this.realm, propsValue, "children", resolvedChildren, true); propsValue.refuseSerialization = false; } } } return reactElement; } _resolveFragmentComponent(componentType, reactElement, context, branchStatus, branchState, 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, branchState, evaluatedChildNode); return children; } else { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("NORMAL", "React.Fragment"); evaluatedNode.children.push(evaluatedChildNode); return this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedChildNode); } } _resolveReactElement(componentType, reactElement, context, branchStatus, branchState, evaluatedNode) { reactElement = this.componentTreeConfig.firstRenderOnly ? (0, _utils.sanitizeReactElementForFirstRenderOnly)(this.realm, reactElement) : reactElement; let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(this.realm, reactElement, "props"); let refValue = (0, _index2.Get)(this.realm, reactElement, "ref"); if (typeValue instanceof _index.StringValue) { return this._resolveReactElementHostChildren(componentType, reactElement, context, branchStatus, branchState, evaluatedNode); } // we do not support "ref" on <Component /> ReactElements if (!(refValue instanceof _index.NullValue)) { this._resolveReactElementBadRef(reactElement, evaluatedNode); } if (!(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue || propsValue instanceof _index.AbstractValue)) { this._assignBailOutMessage(reactElement, `props on <Component /> was not not an ObjectValue or an AbstractValue`); return reactElement; } let componentResolutionStrategy = this._getComponentResolutionStrategy(typeValue); try { let result; switch (componentResolutionStrategy) { case "NORMAL": { if (!(typeValue instanceof _index.ECMAScriptSourceFunctionValue || (0, _utils.valueIsKnownReactAbstraction)(this.realm, typeValue))) { return this._resolveUnknownComponentType(reactElement, evaluatedNode); } let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("INLINED", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); let render = this._renderComponent(typeValue, propsValue, context, branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus, null, evaluatedChildNode); result = render.result; this.statistics.inlinedComponents++; break; } case "FRAGMENT": { return this._resolveFragmentComponent(componentType, reactElement, context, branchStatus, branchState, evaluatedNode); } case "RELAY_QUERY_RENDERER": { (0, _invariant2.default)(typeValue instanceof _index.AbstractObjectValue); result = this._resolveRelayQueryRendererComponent(componentType, reactElement, evaluatedNode); break; } case "CONTEXT_PROVIDER": { return this._resolveContextProviderComponent(componentType, reactElement, context, branchStatus, branchState, evaluatedNode); } case "CONTEXT_CONSUMER": { result = this._resolveContextConsumerComponent(componentType, reactElement, branchState, evaluatedNode); break; } default: (0, _invariant2.default)(false, "unsupported component resolution strategy"); } if (result === undefined) { result = reactElement; } if (result instanceof _index.UndefinedValue) { return this._resolveReactElementUndefinedRender(reactElement, evaluatedNode, branchStatus, branchState); } if (branchStatus === "NEW_BRANCH" && branchState) { return branchState.captureBranchedValue(typeValue, result); } return result; } catch (error) { return this._resolveComponentResolutionFailure(error, reactElement, evaluatedNode, branchStatus, branchState); } } _resolveComponentResolutionFailure(error, reactElement, evaluatedNode, branchStatus, branchState) { if (error.name === "Invariant Violation") { throw error; } let typeValue = (0, _index2.Get)(this.realm, reactElement, "type"); let propsValue = (0, _index2.Get)(this.realm, reactElement, "props"); // assign a bail out message if (error instanceof _errors2.NewComponentTreeBranch) { // NO-OP (we don't queue a newComponentTree as this was already done) } else { // handle abrupt completions if (error instanceof _completions.AbruptCompletion) { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("ABRUPT_COMPLETION", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); } else { let evaluatedChildNode = (0, _utils.createReactEvaluatedNode)("BAIL-OUT", (0, _utils.getComponentName)(this.realm, typeValue)); evaluatedNode.children.push(evaluatedChildNode); this._queueNewComponentTree(typeValue, evaluatedChildNode); this._findReactComponentTrees(propsValue, evaluatedNode); 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 if (branchStatus === "NEW_BRANCH" && branchState) { return branchState.captureBranchedValue(typeValue, reactElement); } return reactElement; } _resolveDeeply(componentType, value, context, branchStatus, branchState, evaluatedNode) { 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; } else if (value instanceof _index.AbstractValue) { return this._resolveAbstractValue(componentType, value, context, branchStatus, branchState, evaluatedNode); } // TODO investigate what about other iterables type objects if (value instanceof _index.ArrayValue) { this._resolveArray(componentType, value, context, branchStatus, branchState, evaluatedNode); return value; } if (value instanceof _index.ObjectValue && (0, _utils.isReactElement)(value)) { return this._resolveReactElement(componentType, value, context, branchStatus, branchState, evaluatedNode); } else { throw new _errors2.ExpectedBailOut("unsupported value type during reconcilation"); } } _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, branchState, evaluatedNode) { (0, _utils.forEachArrayValue)(this.realm, arrayValue, (elementValue, elementPropertyDescriptor) => { elementPropertyDescriptor.value = this._resolveDeeply(componentType, elementValue, context, "NEW_BRANCH", branchState, evaluatedNode); }); } hasEvaluatedRootNode(componentType, evaluateNode) { if (this.alreadyEvaluatedRootNodes.has(componentType)) { let alreadyEvaluatedNode = this.alreadyEvaluatedRootNodes.get(componentType); (0, _invariant2.default)(alreadyEvaluatedNode); evaluateNode.children = alreadyEvaluatedNode.children; evaluateNode.status = alreadyEvaluatedNode.status; evaluateNode.name = alreadyEvaluatedNode.name; return true; } return false; } _findReactComponentTrees(value, evaluatedNode) { if (value instanceof _index.FunctionValue && !(value instanceof _index.ECMAScriptSourceFunctionValue)) { // TODO treat as nested additional function // until then we don't support this so we need to bail out on first render if (this.componentTreeConfig.firstRenderOnly) { throw new _errors2.ExpectedBailOut("non script function is not currently supported on first render"); } } else if (value instanceof _index.AbstractValue) { if (value.args.length > 0) { for (let arg of value.args) { this._findReactComponentTrees(arg, evaluatedNode); } } else { this.componentTreeState.deadEnds++; } } else if (value instanceof _index.ObjectValue) { for (let [propName, binding] of value.properties) { if (binding && binding.descriptor && binding.descriptor.enumerable) { this._findReactComponentTrees((0, _utils.getProperty)(this.realm, value, propName), evaluatedNode); } } } else if (value instanceof _index.ECMAScriptSourceFunctionValue || (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); } } } exports.Reconciler = Reconciler; //# sourceMappingURL=reconcilation.js.map