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