Re-factor of the React createElement logic

Summary:
Release notes: none

Now that we have snapshotting from Object.assign and ToObject for abstracts, we can now upgrade the ReactElement creation logic, simplifying a bunch of routes which actually resulted in a bunch of components inlining in tests where they didn't before. Furthermore, it allows us to tighten up the Flow typings in the React reconciler and tidy up some of the checks for `key`s or `ref`s logic, which was a bit hard to read before and didn't take into account objects with multiple values in their values domain. This also fixes a bug with abstract conditional component types not getting the deafultProps values correctly.
Closes https://github.com/facebook/prepack/pull/1996

Differential Revision: D8172083

Pulled By: trueadm

fbshipit-source-id: 0a8a1e0631883c92e914aba1308a109b3afda137
This commit is contained in:
Dominic Gannaway 2018-05-25 18:15:52 -07:00 committed by Facebook Github Bot
parent 62636d4b2a
commit 47f84b405a
13 changed files with 688 additions and 305 deletions

View File

@ -1023,17 +1023,11 @@ ReactStatistics {
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "refs are not supported on <Components />",
"name": "ClassComponent",
"status": "BAIL-OUT",
},
Object {
"children": Array [],
"message": "",
"name": "ClassComponent",
"status": "BAIL-OUT",
"status": "NEW_TREE",
},
],
"message": "",
@ -1478,6 +1472,36 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Dynamic ReactElement type #3 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Dynamic ReactElement type 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -2280,6 +2304,23 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple 16 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -2686,6 +2727,8 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, JSX output Functional component folding Unsafe spread 1`] = `"Failed to optimize React component tree for \\"App\\" due to a fatal error during evaluation: A fatal error occurred while prepacking."`;
exports[`Test React with JSX input, JSX output Render props React Context 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -4770,17 +4813,11 @@ ReactStatistics {
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "refs are not supported on <Components />",
"name": "ClassComponent",
"status": "BAIL-OUT",
},
Object {
"children": Array [],
"message": "",
"name": "ClassComponent",
"status": "BAIL-OUT",
"status": "NEW_TREE",
},
],
"message": "",
@ -5225,6 +5262,36 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Dynamic ReactElement type #3 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Dynamic ReactElement type 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -6027,6 +6094,23 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple 16 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -6433,6 +6517,8 @@ ReactStatistics {
}
`;
exports[`Test React with JSX input, create-element output Functional component folding Unsafe spread 1`] = `"Failed to optimize React component tree for \\"App\\" due to a fatal error during evaluation: A fatal error occurred while prepacking."`;
exports[`Test React with JSX input, create-element output Render props React Context 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -8517,17 +8603,11 @@ ReactStatistics {
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "refs are not supported on <Components />",
"name": "ClassComponent",
"status": "BAIL-OUT",
},
Object {
"children": Array [],
"message": "",
"name": "ClassComponent",
"status": "BAIL-OUT",
"status": "NEW_TREE",
},
],
"message": "",
@ -8972,6 +9052,36 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Dynamic ReactElement type #3 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Dynamic ReactElement type 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -9774,6 +9884,23 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple 16 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -10004,16 +10131,30 @@ ReactStatistics {
exports[`Test React with create-element input, JSX output Functional component folding Simple with multiple JSX spreads #2 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "IWantThisToBeInlined",
"status": "INLINED",
},
],
"message": "",
"name": "Button",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
@ -10021,16 +10162,23 @@ ReactStatistics {
exports[`Test React with create-element input, JSX output Functional component folding Simple with multiple JSX spreads #3 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Button",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
@ -10089,16 +10237,30 @@ ReactStatistics {
exports[`Test React with create-element input, JSX output Functional component folding Simple with multiple JSX spreads 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "IWantThisToBeInlined",
"status": "INLINED",
},
],
"message": "",
"name": "Button",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
@ -10145,6 +10307,23 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, JSX output Functional component folding Unsafe spread 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, JSX output Render props React Context 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -12229,17 +12408,11 @@ ReactStatistics {
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "refs are not supported on <Components />",
"name": "ClassComponent",
"status": "BAIL-OUT",
},
Object {
"children": Array [],
"message": "",
"name": "ClassComponent",
"status": "BAIL-OUT",
"status": "NEW_TREE",
},
],
"message": "",
@ -12684,6 +12857,36 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Dynamic ReactElement type #3 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Foo",
"status": "INLINED",
},
Object {
"children": Array [],
"message": "",
"name": "Bar",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Dynamic ReactElement type 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -13486,6 +13689,23 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple 16 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Simple children 1`] = `
ReactStatistics {
"componentsEvaluated": 3,
@ -13716,16 +13936,30 @@ ReactStatistics {
exports[`Test React with create-element input, create-element output Functional component folding Simple with multiple JSX spreads #2 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "IWantThisToBeInlined",
"status": "INLINED",
},
],
"message": "",
"name": "Button",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
@ -13733,16 +13967,23 @@ ReactStatistics {
exports[`Test React with create-element input, create-element output Functional component folding Simple with multiple JSX spreads #3 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"componentsEvaluated": 2,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "Button",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"inlinedComponents": 1,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
@ -13801,16 +14042,30 @@ ReactStatistics {
exports[`Test React with create-element input, create-element output Functional component folding Simple with multiple JSX spreads 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"componentsEvaluated": 3,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"children": Array [
Object {
"children": Array [
Object {
"children": Array [],
"message": "",
"name": "IWantThisToBeInlined",
"status": "INLINED",
},
],
"message": "",
"name": "Button",
"status": "INLINED",
},
],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"inlinedComponents": 2,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
@ -13857,6 +14112,23 @@ ReactStatistics {
}
`;
exports[`Test React with create-element input, create-element output Functional component folding Unsafe spread 1`] = `
ReactStatistics {
"componentsEvaluated": 1,
"evaluatedRootNodes": Array [
Object {
"children": Array [],
"message": "",
"name": "App",
"status": "ROOT",
},
],
"inlinedComponents": 0,
"optimizedNestedClosures": 0,
"optimizedTrees": 1,
}
`;
exports[`Test React with create-element input, create-element output Render props React Context 1`] = `
ReactStatistics {
"componentsEvaluated": 3,

View File

@ -59,7 +59,7 @@ let prepackOptions = {
errorsCaptured.push(diag);
if (diag.severity !== "Warning") {
if (diag.errorCode === "PP0025") {
// recover from `unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`
// recover from `unable to evaluate "key" and "ref" on a ReactElement
return "Recover";
}
return "Fail";

View File

@ -63,12 +63,18 @@ MockURI.prototype.makeString = function() {
};
function runTestSuite(outputJsx, shouldTranspileSource) {
let checkForReconcilerFatalError = false;
let checkForPartialKeyOrRefError = false;
let errorsCaptured = [];
let reactTestRoot = path.join(__dirname, "../test/react/");
let prepackOptions = {
errorHandler: diag => {
errorsCaptured.push(diag);
if (diag.severity !== "Warning" && diag.severity !== "Information") {
if (diag.errorCode === "PP0025" && !checkForPartialKeyOrRefError) {
// recover from `unable to evaluate "key" and "ref" on a ReactElement
return "Recover";
}
return "Fail";
}
return "Recover";
@ -85,8 +91,6 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
stripFlow: true,
};
let checkForReconcilerFatalError = false;
async function expectReconcilerFatalError(func) {
checkForReconcilerFatalError = true;
try {
@ -99,6 +103,18 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
}
}
async function expectPartialKeyOrRefError(func) {
checkForPartialKeyOrRefError = true;
try {
await func();
} catch (e) {
expect(e.__isReconcilerFatalError).toBe(true);
expect(e.message).toMatchSnapshot();
} finally {
checkForPartialKeyOrRefError = false;
}
}
function compileSourceWithPrepack(source) {
let code = `(function(){${source}})()`;
let serialized;
@ -106,7 +122,7 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
try {
serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions);
} catch (e) {
if (e.__isReconcilerFatalError && checkForReconcilerFatalError) {
if (e.__isReconcilerFatalError && (checkForReconcilerFatalError || checkForPartialKeyOrRefError)) {
throw e;
}
errorsCaptured.forEach(error => {
@ -344,6 +360,10 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
await runTest(directory, "simple-15.js");
});
it("Simple 16", async () => {
await runTest(directory, "simple-16.js");
});
it("__reactCompilerDoNotOptimize", async () => {
await runTest(directory, "do-not-optimize.js");
});
@ -404,6 +424,12 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
await runTest(directory, "refs3.js");
});
it("Unsafe spread", async () => {
await expectPartialKeyOrRefError(async () => {
await runTest(directory, "unsafe-spread.js");
});
});
it("Simple with abstract props", async () => {
await runTest(directory, "simple-with-abstract-props.js");
});
@ -575,6 +601,10 @@ function runTestSuite(outputJsx, shouldTranspileSource) {
it("Dynamic ReactElement type #2", async () => {
await runTest(directory, "dynamic-type2.js");
});
it("Dynamic ReactElement type #3", async () => {
await runTest(directory, "dynamic-type3.js");
});
});
describe("Class component folding", () => {

View File

@ -25,20 +25,21 @@ import type {
import {
AbstractObjectValue,
ArrayValue,
ECMAScriptFunctionValue,
StringValue,
Value,
NumberValue,
ObjectValue,
FunctionValue,
AbstractValue,
} from "../values/index.js";
import { convertJSXExpressionToIdentifier } from "../react/jsx.js";
import * as t from "babel-types";
import { Get } from "../methods/index.js";
import { Create, Environment, Properties } from "../singletons.js";
import { Create, Environment, Properties, To } from "../singletons.js";
import invariant from "../invariant.js";
import { createReactElement } from "../react/elements.js";
import { objectHasNoPartialKeyAndRef, deleteRefAndKeyFromProps } from "../react/utils.js";
import { flagPropsWithNoPartialKeyOrRef, hasNoPartialKeyOrRef } from "../react/utils.js";
import { FatalError } from "../errors.js";
// taken from Babel
function cleanJSXElementLiteralChild(child: string): null | string {
@ -149,9 +150,9 @@ function evaluateJSXChildren(
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): ArrayValue | Value {
): ArrayValue | Value | void {
if (children.length === 0) {
return realm.intrinsics.undefined;
return undefined;
}
if (children.length === 1) {
let singleChild = evaluateJSXValue(children[0], strictCode, env, realm);
@ -208,29 +209,16 @@ function evaluateJSXAttributes(
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): ObjectValue | AbstractValue {
): ObjectValue | AbstractObjectValue {
let config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
// start by having key and ref deleted, if they actually exist, they will be added later
deleteRefAndKeyFromProps(realm, config);
let abstractPropsArgs = [];
let containsAbstractSpreadAttribute = false;
let mayContainRefOrKey = false;
let attributesAssigned = 0;
let abstractSpreadCount = 0;
let safeAbstractSpreadCount = 0;
let spreadValue;
let keyValue;
let refValue;
const setConfigProperty = (name: string, value: Value): void => {
invariant(config instanceof ObjectValue);
if (name === "key") {
keyValue = value;
mayContainRefOrKey = true;
} else if (name === "ref") {
refValue = value;
mayContainRefOrKey = true;
}
Properties.Set(realm, config, name, value, true);
attributesAssigned++;
};
for (let astAttribute of astAttributes) {
@ -251,20 +239,20 @@ function evaluateJSXAttributes(
}
}
} else {
keyValue = undefined;
refValue = undefined;
containsAbstractSpreadAttribute = true;
invariant(spreadValue instanceof AbstractValue || spreadValue instanceof ObjectValue);
abstractSpreadCount++;
if (spreadValue instanceof AbstractValue && !(spreadValue instanceof AbstractObjectValue)) {
spreadValue = To.ToObject(realm, spreadValue);
}
invariant(spreadValue instanceof AbstractObjectValue || spreadValue instanceof ObjectValue);
if (!objectHasNoPartialKeyAndRef(realm, spreadValue)) {
mayContainRefOrKey = true;
if (hasNoPartialKeyOrRef(realm, spreadValue)) {
safeAbstractSpreadCount++;
}
if (!isObjectEmpty(config)) {
abstractPropsArgs.push(config);
}
abstractPropsArgs.push(spreadValue);
config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
deleteRefAndKeyFromProps(realm, config);
}
break;
default:
@ -272,70 +260,55 @@ function evaluateJSXAttributes(
}
}
if (containsAbstractSpreadAttribute) {
// if we haven't assigned any attributes and we are dealing with a single
// spread attribute, we can just make the spread object the props
if (
attributesAssigned === 0 &&
((spreadValue instanceof ObjectValue && spreadValue.isPartialObject()) || spreadValue instanceof AbstractValue)
) {
// the spread is partial, so we can re-use that value
config = spreadValue;
if (config instanceof ObjectValue || config instanceof AbstractObjectValue) {
// as we're applying a spread, the config needs to be simple/partial
config.makePartial();
config.makeSimple();
}
} else {
// we create an abstract Object.assign() to deal with the fact that we don't what
// the props are because they contain abstract spread attributes that we can't
// evaluate ahead of time
// push the current config
if (config.properties.size > 0) {
abstractPropsArgs.push(config);
}
// create a new config object that will be the target of the Object.assign
config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
// as this is "config that is abstract, we need to make it partial and simple
config.makePartial();
config.makeSimple();
// get the global Object.assign
let globalObj = Get(realm, realm.$GlobalObject, "Object");
invariant(globalObj instanceof ObjectValue);
let objAssign = Get(realm, globalObj, "assign");
invariant(realm.generator);
if (abstractSpreadCount > 0) {
// we create an abstract Object.assign() to deal with the fact that we don't what
// the props are because they contain abstract spread attributes that we can't
// evaluate ahead of time
// push the current config
abstractPropsArgs.push(config);
invariant(realm.generator);
AbstractValue.createTemporalFromBuildFunction(
realm,
FunctionValue,
[objAssign, config, ...abstractPropsArgs],
([methodNode, ..._args]) => {
return t.callExpression(methodNode, ((_args: any): Array<any>));
}
);
if (!mayContainRefOrKey) {
deleteRefAndKeyFromProps(realm, config);
} else {
if (keyValue !== undefined) {
setConfigProperty("key", keyValue);
}
if (refValue !== undefined) {
setConfigProperty("ref", refValue);
// create a new config object that will be the target of the Object.assign
config = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
// ensure the config partial
config.makePartial();
// get the global Object.assign
let globalObj = Get(realm, realm.$GlobalObject, "Object");
invariant(globalObj instanceof ObjectValue);
let objAssign = Get(realm, globalObj, "assign");
invariant(objAssign instanceof ECMAScriptFunctionValue);
let objectAssignCall = objAssign.$Call;
invariant(objectAssignCall !== undefined);
try {
objectAssignCall(realm.intrinsics.undefined, [config, ...abstractPropsArgs]);
if (safeAbstractSpreadCount === abstractSpreadCount) {
flagPropsWithNoPartialKeyOrRef(realm, config);
}
} catch (e) {
if (realm.isInPureScope() && e instanceof FatalError) {
let flagProps = hasNoPartialKeyOrRef(realm, config);
config = AbstractValue.createTemporalFromBuildFunction(
realm,
ObjectValue,
[objAssign, config, ...abstractPropsArgs],
([methodNode, ..._args]) => {
return t.callExpression(methodNode, ((_args: any): Array<any>));
}
);
invariant(config instanceof AbstractObjectValue);
if (flagProps) {
flagPropsWithNoPartialKeyOrRef(realm, config);
}
}
}
}
invariant(config instanceof ObjectValue || config instanceof AbstractValue);
invariant(config instanceof ObjectValue || config instanceof AbstractObjectValue);
return config;
}
export default function(
ast: BabelNodeJSXElement,
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): ObjectValue {
export default function(ast: BabelNodeJSXElement, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value {
invariant(realm.react.enabled, "JSXElements can only be evaluated with the reactEnabled option");
let openingElement = ast.openingElement;
let type = evaluateJSXIdentifier(openingElement.name, strictCode, env, realm);

View File

@ -18,14 +18,13 @@ import {
Value,
AbstractObjectValue,
AbstractValue,
NullValue,
FunctionValue,
NumberValue,
} from "../../values/index.js";
import { Environment } from "../../singletons.js";
import { createReactHintObject, getReactSymbol, isReactElement } from "../../react/utils.js";
import { createReactElement } from "../../react/elements.js";
import { Properties, Create } from "../../singletons.js";
import { Properties, Create, To } from "../../singletons.js";
import { renderToString } from "../../react/experimental-server-rendering/rendering.js";
import * as t from "babel-types";
import invariant from "../../invariant";
@ -499,16 +498,14 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV
if (config === realm.intrinsics.undefined || config === realm.intrinsics.null || config === undefined) {
config = new ObjectValue(realm, realm.intrinsics.ObjectPrototype);
}
invariant(
config instanceof ObjectValue ||
config instanceof AbstractObjectValue ||
config instanceof AbstractValue ||
config instanceof NullValue
);
if (config instanceof AbstractValue && !(config instanceof AbstractObjectValue)) {
config = To.ToObject(realm, config);
}
invariant(config instanceof ObjectValue || config instanceof AbstractObjectValue);
if (Array.isArray(children)) {
if (children.length === 0) {
children = realm.intrinsics.undefined;
children = undefined;
} else if (children.length === 1) {
children = children[0];
} else {
@ -522,7 +519,6 @@ export function createMockReact(realm: Realm, reactRequireName: string): ObjectV
children.makeFinal();
}
}
invariant(children instanceof Value);
return createReactElement(realm, type, config, children);
}
);

View File

@ -23,7 +23,7 @@ import {
} from "../values/index.js";
import * as t from "babel-types";
import type { BabelNodeIdentifier } from "babel-types";
import { valueIsClassComponent, deleteRefAndKeyFromProps, getProperty, getValueFromFunctionCall } from "./utils";
import { flagPropsWithNoPartialKeyOrRef, getProperty, getValueFromFunctionCall, valueIsClassComponent } from "./utils";
import { ExpectedBailOut, SimpleClassBailOut } from "./errors.js";
import { Get, Construct } from "../methods/index.js";
import { Properties } from "../singletons.js";
@ -64,9 +64,9 @@ export function getInitialProps(
}
}
let value = AbstractValue.createAbstractObject(realm, propsName || "props");
// props objects don't have a key and ref, so we remove them
deleteRefAndKeyFromProps(realm, value);
invariant(value instanceof AbstractObjectValue);
flagPropsWithNoPartialKeyOrRef(realm, value);
value.makeFinal();
return value;
}

View File

@ -14,16 +14,15 @@ import {
AbstractValue,
AbstractObjectValue,
ArrayValue,
ECMAScriptFunctionValue,
NumberValue,
Value,
ObjectValue,
FunctionValue,
NullValue,
Value,
} from "../values/index.js";
import { Create, Properties } from "../singletons.js";
import invariant from "../invariant.js";
import { Get } from "../methods/index.js";
import { getProperty, getReactSymbol, objectHasNoPartialKeyAndRef, deleteRefAndKeyFromProps } from "./utils.js";
import { flagPropsWithNoPartialKeyOrRef, getProperty, getReactSymbol, hasNoPartialKeyOrRef } from "./utils.js";
import * as t from "babel-types";
import { computeBinary } from "../evaluators/BinaryExpression.js";
import { CompilerDiagnostic, FatalError } from "../errors.js";
@ -31,23 +30,44 @@ import { CompilerDiagnostic, FatalError } from "../errors.js";
function createPropsObject(
realm: Realm,
type: Value,
config: ObjectValue | AbstractValue | AbstractObjectValue | NullValue,
children: Value
) {
let defaultProps = type instanceof ObjectValue ? Get(realm, type, "defaultProps") : realm.intrinsics.undefined;
config: ObjectValue | AbstractObjectValue,
children: void | Value
): { key: Value, ref: Value, props: ObjectValue | AbstractObjectValue } {
let defaultProps =
type instanceof ObjectValue || type instanceof AbstractObjectValue
? Get(realm, type, "defaultProps")
: realm.intrinsics.undefined;
let props = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
// start by having key and ref deleted, if they actually exist, they will be add later
deleteRefAndKeyFromProps(realm, props);
let key = realm.intrinsics.null;
let ref = realm.intrinsics.null;
if (!hasNoPartialKeyOrRef(realm, config)) {
// if either are abstract, this will impact the reconcilation process
// and ultimately prevent us from folding ReactElements properly
let diagnostic = new CompilerDiagnostic(
`unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`,
realm.currentLocation,
"PP0025",
"FatalError"
);
realm.handleError(diagnostic);
if (realm.handleError(diagnostic) === "Fail") throw new FatalError();
}
let possibleKey = Get(realm, config, "key");
if (possibleKey !== realm.intrinsics.null && possibleKey !== realm.intrinsics.undefined) {
key = computeBinary(realm, "+", realm.intrinsics.emptyString, possibleKey);
}
let possibleRef = Get(realm, config, "ref");
if (possibleRef !== realm.intrinsics.null && possibleRef !== realm.intrinsics.undefined) {
ref = possibleRef;
}
const setProp = (name: string, value: Value): void => {
if (name === "key" && value !== realm.intrinsics.null) {
key = computeBinary(realm, "+", realm.intrinsics.emptyString, value);
} else if (name === "ref") {
ref = value;
} else if (name !== "__self" && name !== "__source") {
invariant(props instanceof ObjectValue);
if (name !== "__self" && name !== "__source" && name !== "key" && name !== "ref") {
invariant(props instanceof ObjectValue || props instanceof AbstractObjectValue);
Properties.Set(realm, props, name, value, true);
}
};
@ -64,68 +84,48 @@ function createPropsObject(
if (
(config instanceof AbstractObjectValue && config.isPartialObject()) ||
config instanceof AbstractValue ||
(config instanceof ObjectValue && config.isPartialObject() && config.isSimpleObject())
) {
// if we have defaultProps, we need to create a new merge of the objects along with our config
if (defaultProps !== realm.intrinsics.undefined || children !== realm.intrinsics.undefined) {
if (objectHasNoPartialKeyAndRef(realm, config)) {
let args = [];
if (defaultProps !== realm.intrinsics.undefined) {
args.push(defaultProps);
}
args.push(config);
// create a new props object that will be the target of the Object.assign
props = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
// as this is "props" that is abstract, we need to make it partial and simple
props.makePartial();
props.makeSimple();
// props objects also don't have a key and ref, so we remove them
deleteRefAndKeyFromProps(realm, props);
let args = [];
if (defaultProps !== realm.intrinsics.undefined) {
args.push(defaultProps);
}
args.push(config);
// create a new props object that will be the target of the Object.assign
props = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
// get the global Object.assign
let globalObj = Get(realm, realm.$GlobalObject, "Object");
invariant(globalObj instanceof ObjectValue);
let objAssign = Get(realm, globalObj, "assign");
invariant(realm.generator);
// get the global Object.assign
let globalObj = Get(realm, realm.$GlobalObject, "Object");
invariant(globalObj instanceof ObjectValue);
let objAssign = Get(realm, globalObj, "assign");
invariant(objAssign instanceof ECMAScriptFunctionValue);
let objectAssignCall = objAssign.$Call;
invariant(objectAssignCall !== undefined);
AbstractValue.createTemporalFromBuildFunction(
if (children !== undefined) {
let childrenObject = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
Properties.Set(realm, props, "children", children, true);
args.push(childrenObject);
}
try {
objectAssignCall(realm.intrinsics.undefined, [props, ...args]);
} catch (e) {
if (realm.isInPureScope() && e instanceof FatalError) {
props = AbstractValue.createTemporalFromBuildFunction(
realm,
FunctionValue,
ObjectValue,
[objAssign, props, ...args],
([methodNode, ..._args]) => {
return t.callExpression(methodNode, ((_args: any): Array<any>));
}
);
} else {
// if either are abstract, this will impact the reconcilation process
// and ultimately prevent us from folding ReactElements properly
let diagnostic = new CompilerDiagnostic(
`unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`,
realm.currentLocation,
"PP0025",
"FatalError"
);
realm.handleError(diagnostic);
if (realm.handleError(diagnostic) === "Fail") throw new FatalError();
}
if (children !== realm.intrinsics.undefined) {
setProp("children", children);
}
} else {
// as the config is partial and simple, we don't know about its prototype or properties
// we don't have to worry about non-enumerable properties as its properties will never
// be serialized, rather this object will be serialized as a spread.
props = config;
// if there are any properties that do exist, it's because we know for sure they exist
// i.e. they were added on as part of snapshotting or at the end of a spread, like
// {...foo, ...bar, x: 5}
applyProperties();
}
} else {
applyProperties();
if (children !== realm.intrinsics.undefined) {
if (children !== undefined) {
setProp("children", children);
}
@ -137,21 +137,61 @@ function createPropsObject(
}
}
}
} else if (defaultProps instanceof AbstractObjectValue) {
invariant(false, "TODO: we need to eventually support this");
}
}
if (props instanceof ObjectValue || props instanceof AbstractObjectValue) {
// ensure the props is marked as final
invariant(props instanceof ObjectValue || props instanceof AbstractObjectValue);
// We know the props has no keys because if it did it would have thrown above
// so we can remove them the props we create.
flagPropsWithNoPartialKeyOrRef(realm, props);
// If the object is an object or abstract object value that has a backing object
// value for a template, we can make them final. We can't make abstract values
// final though but that's okay. They also can't be havoced.
if (props instanceof ObjectValue || (props instanceof AbstractObjectValue && !props.values.isTop())) {
props.makeFinal();
}
return { key, props, ref };
}
function splitReactElementsByConditionalType(
realm: Realm,
condValue: AbstractValue,
consequentVal: Value,
alternateVal: Value,
config: ObjectValue | AbstractObjectValue,
children: void | Value
): Value {
return realm.evaluateWithAbstractConditional(
condValue,
() => {
return realm.evaluateForEffects(
() => createReactElement(realm, consequentVal, config, children),
null,
"splitReactElementsByConditionalType consequent"
);
},
() => {
return realm.evaluateForEffects(
() => createReactElement(realm, alternateVal, config, children),
null,
"splitReactElementsByConditionalType alternate"
);
}
);
}
export function createReactElement(
realm: Realm,
type: Value,
config: ObjectValue | AbstractValue | AbstractObjectValue | NullValue,
children: Value
) {
config: ObjectValue | AbstractObjectValue,
children: void | Value
): Value {
if (type instanceof AbstractValue && type.kind === "conditional") {
let [condValue, consequentVal, alternateVal] = type.args;
invariant(condValue instanceof AbstractValue);
return splitReactElementsByConditionalType(realm, condValue, consequentVal, alternateVal, config, children);
}
let { key, props, ref } = createPropsObject(realm, type, config, children);
return createInternalReactElement(realm, type, key, ref, props);
}
@ -161,8 +201,8 @@ export function createInternalReactElement(
type: Value,
key: Value,
ref: Value,
props: ObjectValue | AbstractValue
) {
props: ObjectValue | AbstractObjectValue
): ObjectValue {
let obj = Create.ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
Create.CreateDataPropertyOrThrow(realm, obj, "$$typeof", getReactSymbol("react.element", realm));
Create.CreateDataPropertyOrThrow(realm, obj, "type", type);

View File

@ -316,7 +316,7 @@ export class Reconciler {
_resolveComplexClassComponent(
componentType: ECMAScriptSourceFunctionValue,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
classMetadata: ClassComponentMetadata,
branchStatus: BranchStatusEnum,
@ -352,7 +352,7 @@ export class Reconciler {
_resolveSimpleClassComponent(
componentType: ECMAScriptSourceFunctionValue,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null,
@ -369,7 +369,7 @@ export class Reconciler {
_resolveFunctionalComponent(
componentType: ECMAScriptSourceFunctionValue,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
evaluatedNode: ReactEvaluatedNode
) {
@ -378,7 +378,7 @@ export class Reconciler {
_getClassComponentMetadata(
componentType: ECMAScriptSourceFunctionValue,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue
): ClassComponentMetadata {
if (this.realm.react.classComponentMetadata.has(componentType)) {
@ -607,7 +607,7 @@ export class Reconciler {
_resolveClassComponent(
componentType: ECMAScriptSourceFunctionValue,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null,
@ -674,7 +674,7 @@ export class Reconciler {
_resolveClassComponentForFirstRenderOnly(
componentType: ECMAScriptSourceFunctionValue,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null,
@ -715,7 +715,7 @@ export class Reconciler {
_resolveRelayContainer(
reactHint: ReactHint,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null,
@ -743,7 +743,7 @@ export class Reconciler {
_resolveComponent(
componentType: Value,
props: ObjectValue | AbstractValue | AbstractObjectValue,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null,
@ -922,7 +922,7 @@ export class Reconciler {
return this.realm.evaluateForEffects(
() => this._resolveDeeply(componentType, alternateVal, context, "NEW_BRANCH", newBranchState, evaluatedNode),
null,
"_resolveAbstractConditionalValue consequent"
"_resolveAbstractConditionalValue alternate"
);
}
);
@ -980,51 +980,6 @@ export class Reconciler {
return value;
}
_resolveBranchedComponentType(
componentType: Value,
reactElement: ObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null,
evaluatedNode: ReactEvaluatedNode
): Value {
let typeValue = getProperty(this.realm, reactElement, "type");
let propsValue = getProperty(this.realm, reactElement, "props");
let keyValue = getProperty(this.realm, reactElement, "key");
let refValue = getProperty(this.realm, reactElement, "ref");
const resolveBranch = (abstract: AbstractValue): Value => {
invariant(abstract.kind === "conditional", "the reconciler tried to resolve a non conditional abstract");
let condition = abstract.args[0];
invariant(condition instanceof AbstractValue);
let left = abstract.args[1];
let right = abstract.args[2];
if (left instanceof AbstractValue && left.kind === "conditional") {
left = resolveBranch(left);
} else {
invariant(propsValue instanceof ObjectValue || propsValue instanceof AbstractValue);
left = createInternalReactElement(this.realm, left, keyValue, refValue, propsValue);
}
if (right instanceof AbstractValue && right.kind === "conditional") {
right = resolveBranch(right);
} else {
invariant(propsValue instanceof ObjectValue || propsValue instanceof AbstractValue);
right = createInternalReactElement(this.realm, right, keyValue, refValue, propsValue);
}
return AbstractValue.createFromConditionalOp(this.realm, condition, left, right);
};
invariant(typeValue instanceof AbstractValue);
return this._resolveDeeply(
componentType,
resolveBranch(typeValue),
context,
branchStatus,
branchState,
evaluatedNode
);
}
_resolveUnknownComponentType(reactElement: ObjectValue, evaluatedNode: ReactEvaluatedNode) {
let typeValue = getProperty(this.realm, reactElement, "type");
let propsValue = getProperty(this.realm, reactElement, "props");
@ -1195,20 +1150,24 @@ export class Reconciler {
evaluatedNode
);
}
if (
!(
propsValue instanceof ObjectValue ||
propsValue instanceof AbstractObjectValue ||
propsValue instanceof AbstractValue
)
) {
this._assignBailOutMessage(reactElement, `props on <Component /> was not not an ObjectValue or an AbstractValue`);
if (!(propsValue instanceof ObjectValue || propsValue instanceof 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
if (!(refValue instanceof NullValue) && componentResolutionStrategy !== "FORWARD_REF") {
// We do not support "ref" on <Component /> ReactElements, unless it's a forwarded ref
if (
!(refValue instanceof 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 AbstractValue)
) {
this._resolveReactElementBadRef(reactElement, evaluatedNode);
}
try {
@ -1217,13 +1176,9 @@ export class Reconciler {
switch (componentResolutionStrategy) {
case "NORMAL": {
if (typeValue instanceof AbstractValue && typeValue.kind === "conditional") {
return this._resolveBranchedComponentType(
componentType,
reactElement,
context,
branchStatus,
branchState,
evaluatedNode
invariant(
false,
"We should never have a component type that is condition, it should be handled in the createReactElement logic"
);
}
if (

View File

@ -543,26 +543,38 @@ export function getComponentTypeFromRootValue(realm: Realm, value: Value): ECMAS
}
}
// props should never have "ref" or "key" properties, as they're part of ReactElement
// object instead. to ensure that we can give this hint, we create them and then
// delete them, so their descriptor is left undefined. we use this knowledge later
// to ensure that when dealing with creating ReactElements with partial config,
// we don't have to bail out becuase "config" may or may not have "key" or/and "ref"
export function deleteRefAndKeyFromProps(realm: Realm, props: ObjectValue | AbstractObjectValue): void {
setProperty(props, "ref", realm.intrinsics.undefined);
deleteProperty(props, "ref");
setProperty(props, "key", realm.intrinsics.undefined);
deleteProperty(props, "key");
export function flagPropsWithNoPartialKeyOrRef(realm: Realm, props: ObjectValue | AbstractObjectValue): void {
realm.react.propsWithNoPartialKeyOrRef.add(props);
}
export function objectHasNoPartialKeyAndRef(
realm: Realm,
object: ObjectValue | AbstractValue | AbstractObjectValue
): boolean {
if (object instanceof AbstractValue) {
export function hasNoPartialKeyOrRef(realm: Realm, props: ObjectValue | AbstractObjectValue): boolean {
if (realm.react.propsWithNoPartialKeyOrRef.has(props)) {
return true;
}
return !(Get(realm, object, "key") instanceof AbstractValue || Get(realm, object, "ref") instanceof AbstractValue);
if (props instanceof ObjectValue && !props.isPartialObject()) {
return true;
}
if (props instanceof AbstractObjectValue) {
if (props.values.isTop()) {
return false;
}
let elements = props.values.getElements();
if (elements.size === 1) {
props = Array.from(elements)[0];
} else {
for (let element of elements) {
let wasSafe = hasNoPartialKeyOrRef(realm, element);
if (!wasSafe) {
return false;
}
}
return true;
}
}
if (props instanceof ObjectValue && props.properties.has("key") && props.properties.has("ref")) {
return true;
}
return false;
}
function recursivelyFlattenArray(realm: Realm, array, targetArray): void {
@ -630,6 +642,9 @@ export function deleteProperty(object: ObjectValue | AbstractObjectValue, proper
let elements = object.values.getElements();
if (elements && elements.size > 0) {
object = Array.from(elements)[0];
} else {
// intentionally left in
invariant(false, "TODO: should we hit this?");
}
invariant(object instanceof ObjectValue);
}
@ -659,6 +674,9 @@ export function setProperty(
let elements = object.values.getElements();
if (elements && elements.size > 0) {
object = Array.from(elements)[0];
} else {
// intentionally left in
invariant(false, "TODO: should we hit this?");
}
invariant(object instanceof ObjectValue);
}
@ -710,8 +728,12 @@ export function getProperty(
return realm.intrinsics.undefined;
}
let elements = object.values.getElements();
if (elements && elements.size > 0) {
invariant(elements);
if (elements.size > 0) {
object = Array.from(elements)[0];
} else {
// intentionally left in
invariant(false, "TODO: should we hit this?");
}
invariant(object instanceof ObjectValue);
}

View File

@ -255,6 +255,7 @@ export class Realm {
hoistableReactElements: new WeakMap(),
noopFunction: undefined,
output: opts.reactOutput || "create-element",
propsWithNoPartialKeyOrRef: new WeakSet(),
reactElements: new WeakSet(),
symbols: new Map(),
verbose: opts.reactVerbose || false,
@ -342,6 +343,7 @@ export class Realm {
hoistableReactElements: WeakMap<ObjectValue, boolean>,
noopFunction: void | ECMAScriptSourceFunctionValue,
output?: ReactOutputTypes,
propsWithNoPartialKeyOrRef: WeakSet<ObjectValue | AbstractObjectValue>,
reactElements: WeakSet<ObjectValue>,
symbols: Map<ReactSymbolTypes, SymbolValue>,
verbose: boolean,

View File

@ -0,0 +1,42 @@
var React = require('react');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function Foo(props) {
return <span>{props.name}</span>;
}
Foo.defaultProps = {
name: "Hello world 1",
};
function Bar(props) {
return <div>{props.name}</div>;
}
Bar.defaultProps = {
name: "Hello world 2",
};
function App(props) {
let Type = props.switch ? Foo : Bar;
return <Type />
}
App.getTrials = function(renderer, Root) {
let results = [];
renderer.update(<Root switch={false} />);
results.push(['render with dynamic type', renderer.toJSON()]);
renderer.update(<Root switch={true} />);
results.push(['render with dynamic type update', renderer.toJSON()]);
renderer.update(<Root switch={false} />);
results.push(['render with dynamic type update', renderer.toJSON()]);
return results;
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;

View File

@ -0,0 +1,21 @@
var React = require('React');
// the JSX transform converts to React, so we need to add it back in
this['React'] = React;
function App(props) {
return <div children={'hi'}>{undefined}</div>
}
App.getTrials = function(renderer, Root) {
renderer.update(<Root />);
return [['undefined children', renderer.toJSON()]];
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App, {
firstRenderOnly: true,
});
}
module.exports = App;

View File

@ -0,0 +1,30 @@
var React = require('react');
this['React'] = React;
class Wat extends React.Component {
render() {
return <div {...this.props} />;
}
}
function App(props) {
return <Wat {...props.inner} />;
}
App.getTrials = function(renderer, Root) {
var obj;
function ref(inst) {
obj = inst;
}
renderer.update(<Root inner={{className: 'foo', ref}} />);
return [
['simple render with jsx spread 6', renderer.toJSON()],
['type', Object.keys(obj).join(',')]
];
};
if (this.__optimizeReactComponentTree) {
__optimizeReactComponentTree(App);
}
module.exports = App;