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