301 lines
19 KiB
JavaScript
301 lines
19 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.Reconciler = undefined;
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
|
|
* 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.
|
|
*/
|
|
|
|
var _realm = require("../realm.js");
|
|
|
|
var _modules = require("../serializer/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 _errors = require("../errors.js");
|
|
|
|
var _branching = require("./branching.js");
|
|
|
|
var _components = require("./components.js");
|
|
|
|
var _errors2 = require("./errors.js");
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var Reconciler = exports.Reconciler = function () {
|
|
function Reconciler(realm, moduleTracer, statistics, reactSerializerState, simpleClassComponents) {
|
|
_classCallCheck(this, Reconciler);
|
|
|
|
this.realm = realm;
|
|
this.moduleTracer = moduleTracer;
|
|
this.statistics = statistics;
|
|
this.reactSerializerState = reactSerializerState;
|
|
this.simpleClassComponents = simpleClassComponents;
|
|
}
|
|
|
|
_createClass(Reconciler, [{
|
|
key: "render",
|
|
value: function render(componentType) {
|
|
var _this = this;
|
|
|
|
return this.realm.wrapInGlobalEnv(function () {
|
|
return (
|
|
// 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(function () {
|
|
// 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 {
|
|
var initialProps = (0, _components.getInitialProps)(_this.realm, componentType);
|
|
var initialContext = (0, _components.getInitialContext)(_this.realm, componentType);
|
|
|
|
var _renderComponent2 = _this._renderComponent(componentType, initialProps, initialContext, "ROOT", null),
|
|
result = _renderComponent2.result;
|
|
|
|
_this.statistics.optimizedTrees++;
|
|
return result;
|
|
} catch (error) {
|
|
// if there was a bail-out on the root component in this reconcilation process, then this
|
|
// should be an invariant as the user has explicitly asked for this component to get folded
|
|
if (error instanceof _errors2.ExpectedBailOut) {
|
|
var diagnostic = new _errors.CompilerDiagnostic("__registerReactComponentRoot() failed due to - " + error.message, _this.realm.currentLocation, "PP0020", "FatalError");
|
|
_this.realm.handleError(diagnostic);
|
|
throw new _errors.FatalError();
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
/*state*/null, "react component: " + componentType.getName())
|
|
);
|
|
});
|
|
}
|
|
}, {
|
|
key: "_renderComplexClassComponent",
|
|
value: function _renderComplexClassComponent(componentType, props, context, branchStatus, branchState) {
|
|
if (branchStatus !== "ROOT") {
|
|
throw new _errors2.ExpectedBailOut("only complex class components at the root of __registerReactComponentRoot() are supported");
|
|
}
|
|
// create a new instance of this React class component
|
|
var instance = (0, _components.createClassInstance)(this.realm, componentType, props, context);
|
|
// get the "render" method off the instance
|
|
var renderMethod = (0, _index2.Get)(this.realm, instance, "render");
|
|
(0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue && renderMethod.$Call, "Expected render method to be a FunctionValue with $Call method");
|
|
// the render method doesn't have any arguments, so we just assign the context of "this" to be the instance
|
|
return renderMethod.$Call(instance, []);
|
|
}
|
|
}, {
|
|
key: "_renderSimpleClassComponent",
|
|
value: function _renderSimpleClassComponent(componentType, props, context, branchStatus, branchState) {
|
|
// create a new simple instance of this React class component
|
|
var instance = (0, _components.createSimpleClassInstance)(this.realm, componentType, props, context);
|
|
// get the "render" method off the instance
|
|
var renderMethod = (0, _index2.Get)(this.realm, instance, "render");
|
|
(0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue && renderMethod.$Call, "Expected render method to be a FunctionValue with $Call method");
|
|
// the render method doesn't have any arguments, so we just assign the context of "this" to be the instance
|
|
return renderMethod.$Call(instance, []);
|
|
}
|
|
}, {
|
|
key: "_renderFunctionalComponent",
|
|
value: function _renderFunctionalComponent(componentType, props, context) {
|
|
(0, _invariant2.default)(componentType.$Call, "Expected componentType to be a FunctionValue with $Call method");
|
|
return componentType.$Call(this.realm.intrinsics.undefined, [props, context]);
|
|
}
|
|
}, {
|
|
key: "_renderComponent",
|
|
value: function _renderComponent(componentType, props, context, branchStatus, branchState) {
|
|
var value = void 0;
|
|
var 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)) {
|
|
// 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 or many method, i.e. render(), _renderHeader(), _renderFooter()
|
|
// - 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);
|
|
this.simpleClassComponents.add(value);
|
|
} 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, branchStatus, branchState);
|
|
}
|
|
} else {
|
|
value = this._renderFunctionalComponent(componentType, props, context);
|
|
}
|
|
(0, _invariant2.default)(value !== undefined);
|
|
return {
|
|
result: this._resolveDeeply(value, context, branchStatus === "ROOT" ? "NO_BRANCH" : branchStatus, branchState),
|
|
childContext: childContext
|
|
};
|
|
}
|
|
}, {
|
|
key: "_resolveDeeply",
|
|
value: function _resolveDeeply(value, context, branchStatus, branchState) {
|
|
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) {
|
|
var length = value.args.length;
|
|
if (length > 0) {
|
|
var newBranchState = new _branching.BranchState();
|
|
// TODO investigate what other kinds than "conditional" might be safe to deeply resolve
|
|
for (var i = 0; i < length; i++) {
|
|
value.args[i] = this._resolveDeeply(value.args[i], context, "NEW_BRANCH", newBranchState);
|
|
}
|
|
newBranchState.applyBranchedLogic(this.realm, this.reactSerializerState);
|
|
}
|
|
return value;
|
|
}
|
|
// TODO investigate what about other iterables type objects
|
|
if (value instanceof _index.ArrayValue) {
|
|
this._resolveFragment(value, context, branchStatus, branchState);
|
|
return value;
|
|
}
|
|
if (value instanceof _index.ObjectValue && (0, _utils.isReactElement)(value)) {
|
|
// we call value reactElement, to make it clearer what we're dealing with in this block
|
|
var reactElement = value;
|
|
var typeValue = (0, _index2.Get)(this.realm, reactElement, "type");
|
|
var propsValue = (0, _index2.Get)(this.realm, reactElement, "props");
|
|
var refValue = (0, _index2.Get)(this.realm, reactElement, "ref");
|
|
if (typeValue instanceof _index.StringValue) {
|
|
// terminal host component. Start evaluating its children.
|
|
if (propsValue instanceof _index.ObjectValue) {
|
|
var childrenProperty = propsValue.properties.get("children");
|
|
if (childrenProperty) {
|
|
var childrenPropertyDescriptor = childrenProperty.descriptor;
|
|
// if the descriptor is undefined, the property is likely deleted, if it exists
|
|
// proceed to resolve the children
|
|
if (childrenPropertyDescriptor !== undefined) {
|
|
var childrenPropertyValue = childrenPropertyDescriptor.value;
|
|
(0, _invariant2.default)(childrenPropertyValue instanceof _index.Value, "Bad \"children\" prop passed in JSXElement");
|
|
var resolvedChildren = this._resolveDeeply(childrenPropertyValue, context, branchStatus, branchState);
|
|
childrenPropertyDescriptor.value = resolvedChildren;
|
|
}
|
|
}
|
|
}
|
|
return reactElement;
|
|
}
|
|
// we do not support "ref" on <Component /> ReactElements
|
|
if (!(refValue instanceof _index.NullValue)) {
|
|
this._assignBailOutMessage(reactElement, "Bail-out: refs are not supported on <Components />");
|
|
return reactElement;
|
|
}
|
|
if (!(propsValue instanceof _index.ObjectValue || propsValue instanceof _index.AbstractObjectValue)) {
|
|
this._assignBailOutMessage(reactElement, "Bail-out: props on <Component /> was not not an ObjectValue or an AbstractValue");
|
|
return reactElement;
|
|
}
|
|
if (!(typeValue instanceof _index.ECMAScriptSourceFunctionValue)) {
|
|
this._assignBailOutMessage(reactElement, "Bail-out: type on <Component /> was not a ECMAScriptSourceFunctionValue");
|
|
return reactElement;
|
|
}
|
|
try {
|
|
var _renderComponent3 = this._renderComponent(typeValue, propsValue, context, branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus, null),
|
|
result = _renderComponent3.result;
|
|
|
|
if (result instanceof _index.UndefinedValue) {
|
|
this._assignBailOutMessage(reactElement, "Bail-out: undefined was returned from render");
|
|
if (branchStatus === "NEW_BRANCH" && branchState) {
|
|
return branchState.captureBranchedValue(typeValue, reactElement);
|
|
}
|
|
return reactElement;
|
|
}
|
|
this.statistics.inlinedComponents++;
|
|
if (branchStatus === "NEW_BRANCH" && branchState) {
|
|
return branchState.captureBranchedValue(typeValue, result);
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
// assign a bail out message
|
|
if (error instanceof _errors2.ExpectedBailOut) {
|
|
this._assignBailOutMessage(reactElement, "Bail-out: " + error.message);
|
|
} else if (error instanceof _errors.FatalError) {
|
|
this._assignBailOutMessage(reactElement, "Evaluation bail-out");
|
|
} else {
|
|
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;
|
|
}
|
|
} else {
|
|
throw new _errors2.ExpectedBailOut("unsupported value type during reconcilation");
|
|
}
|
|
}
|
|
}, {
|
|
key: "_assignBailOutMessage",
|
|
value: function _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
|
|
if (reactElement.$BailOutReason !== undefined) {
|
|
// merge bail out messages if one already exists
|
|
reactElement.$BailOutReason += ", " + message;
|
|
} else {
|
|
reactElement.$BailOutReason = message;
|
|
}
|
|
}
|
|
}, {
|
|
key: "_resolveFragment",
|
|
value: function _resolveFragment(arrayValue, context, branchStatus, branchState) {
|
|
var _this2 = this;
|
|
|
|
(0, _utils.mapOverArrayValue)(this.realm, arrayValue, function (elementValue, elementPropertyDescriptor) {
|
|
elementPropertyDescriptor.value = _this2._resolveDeeply(elementValue, context, branchStatus, branchState);
|
|
});
|
|
}
|
|
}]);
|
|
|
|
return Reconciler;
|
|
}();
|
|
//# sourceMappingURL=reconcilation.js.map
|