mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-08-17 02:20:31 +03:00
Add React functional component folding
Summary: Release note: Adds experimental React functional component folding optimizations This PR is stacked upon PRs #1118 and #1117. Thus, those PRs should be merged before this PR is merged to reduce noise in the diff. This PR adds a new React Reconciler into Prepack's serialization process, so that React components trees can be folded/inlined into a single component at build time. To fold a component tree, it must be explicitly done via `__registerReactComponentRoot(nameOfComponent)`. This PR only attempts to fold React functional components, not React ES2015 class components (that will come in another PR at a later date). Furthermore, the `props` parameter on a root component must contain Flow type annotations (otherwise we will have no idea what the values might be). Support flow `propTypes` might also be an addition, but not for this PR. If the reconciler comes across a component that it cannot fold/inline, it will "bail-out" and try and continue the process without that particular component being folded into the tree. An example of how this all works (input): ```jsx function App(props: {title: string}) { return ( <div> <ChildComponent title={props.title} /> </div> ); } function ChildComponent(props) { return ( <span> <SubChildComponent {...props} /> </span> ); } function SubChildComponent(props) { return <span>{props.title.toString()}</span> } __registerReactComponentRoot(App); global.App = App; ``` Output: ```jsx (function () { "use strict"; var _$1 = this; var _0 = function (props) { var _$0 = props.title; return <div><span><span>{_$0}</span></span></div>; }; _$1.App = _0; }).call(this); ``` Closes https://github.com/facebook/prepack/pull/1120 Differential Revision: D6237333 Pulled By: trueadm fbshipit-source-id: b58c7d8979ca79a766bb2ee2eb01a380d37c3101
This commit is contained in:
parent
eebba38633
commit
810056d1ec
0
.watchmanconfig
Normal file
0
.watchmanconfig
Normal file
@ -42,7 +42,7 @@
|
|||||||
"test-node-cli-mode": "bash < scripts/test-node-cli-mode.sh",
|
"test-node-cli-mode": "bash < scripts/test-node-cli-mode.sh",
|
||||||
"test-std-in": "bash < scripts/test-std-in.sh",
|
"test-std-in": "bash < scripts/test-std-in.sh",
|
||||||
"test-react": "jest scripts/test-react",
|
"test-react": "jest scripts/test-react",
|
||||||
"test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-internal && yarn test-react",
|
"test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-react && yarn test-internal",
|
||||||
"test-coverage-most": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/remap-istanbul -i coverage.most/coverage.json -o coverage-sourcemapped -t html",
|
"test-coverage-most": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/remap-istanbul -i coverage.most/coverage.json -o coverage-sourcemapped -t html",
|
||||||
"test-all-coverage": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover -- ./lib/test262-runner.js --timeout 50 --singleThreaded && ./node_modules/.bin/remap-istanbul -i coverage/coverage.json -i coverage.most/coverage.json -o coverage-sourcemapped -t html",
|
"test-all-coverage": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover -- ./lib/test262-runner.js --timeout 50 --singleThreaded && ./node_modules/.bin/remap-istanbul -i coverage/coverage.json -i coverage.most/coverage.json -o coverage-sourcemapped -t html",
|
||||||
"repl": "node lib/repl-cli.js",
|
"repl": "node lib/repl-cli.js",
|
||||||
|
@ -41,7 +41,7 @@ exec("flow check --profile", function(error, stdout, stderr) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
console.log("Biggest cycle: " + cycle_len);
|
console.log("Biggest cycle: " + cycle_len);
|
||||||
let MAX_CYCLE_LEN = 59;
|
let MAX_CYCLE_LEN = 61;
|
||||||
if (cycle_len > MAX_CYCLE_LEN) {
|
if (cycle_len > MAX_CYCLE_LEN) {
|
||||||
console.log("Error: You increased cycle length from the previous high of " + MAX_CYCLE_LEN);
|
console.log("Error: You increased cycle length from the previous high of " + MAX_CYCLE_LEN);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -35,15 +35,11 @@ let prepackOptions = {
|
|||||||
function compileSourceWithPrepack(source) {
|
function compileSourceWithPrepack(source) {
|
||||||
let code = `(function(){${source}})()`;
|
let code = `(function(){${source}})()`;
|
||||||
let serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions);
|
let serialized = prepackSources([{ filePath: "", fileContents: code, sourceMapContents: "" }], prepackOptions);
|
||||||
// add the React require back in, as we've removed it with our Prepack mock
|
|
||||||
// the regex checks for any Prepack variable that matches "_$**any digit**.React"
|
|
||||||
let compiledSource = serialized.code.replace(/_\$[\d].React/, "React = require('react')");
|
|
||||||
if (serialized == null || serialized.reactStatistics == null) {
|
if (serialized == null || serialized.reactStatistics == null) {
|
||||||
throw new Error("React test runner failed during serialization");
|
throw new Error("React test runner failed during serialization");
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
// replace the code to put back the generator (Prepack doesn't serialize them yet)
|
compiledSource: serialized.code,
|
||||||
compiledSource,
|
|
||||||
statistics: serialized.reactStatistics,
|
statistics: serialized.reactStatistics,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -145,6 +141,10 @@ describe("Test React", () => {
|
|||||||
await runTest(directory, "type-change.js");
|
await runTest(directory, "type-change.js");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Component type same", async () => {
|
||||||
|
await runTest(directory, "type-same.js");
|
||||||
|
});
|
||||||
|
|
||||||
it("Dynamic props", async () => {
|
it("Dynamic props", async () => {
|
||||||
await runTest(directory, "dynamic-props.js");
|
await runTest(directory, "dynamic-props.js");
|
||||||
});
|
});
|
||||||
|
123
src/flow/abstractObjectFactories.js
Normal file
123
src/flow/abstractObjectFactories.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { Realm } from "../realm.js";
|
||||||
|
import buildExpressionTemplate from "../utils/builder.js";
|
||||||
|
import { ObjectCreate, ArrayCreate } from "../methods/index.js";
|
||||||
|
import { ValuesDomain } from "../domains/index.js";
|
||||||
|
import { Value, AbstractValue, ObjectValue, ArrayValue } from "../values/index.js";
|
||||||
|
import invariant from "../invariant.js";
|
||||||
|
import { type ObjectTypeTemplate } from "./utils.js";
|
||||||
|
|
||||||
|
export function createObject(realm: Realm, shape: ObjectTypeTemplate | null, name: string | null): ObjectValue {
|
||||||
|
let obj = ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
||||||
|
if (shape != null) {
|
||||||
|
// to get around Flow complaining that shape could be null
|
||||||
|
let shapeThatIsNotNull = shape;
|
||||||
|
Object.keys(shape).forEach((id: string) => {
|
||||||
|
let value = shapeThatIsNotNull[id];
|
||||||
|
invariant(value instanceof Value, "creation of object failed due to object containing non-value properties");
|
||||||
|
obj.$Set(id, value, obj);
|
||||||
|
if (name !== null) {
|
||||||
|
value.intrinsicName = `${name}.${id}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (name !== null) {
|
||||||
|
obj.intrinsicName = name;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createArray(realm: Realm, name: string | null): ArrayValue {
|
||||||
|
let obj = ArrayCreate(realm, 0, realm.intrinsics.ArrayPrototype);
|
||||||
|
if (name !== null) {
|
||||||
|
obj.intrinsicName = name;
|
||||||
|
}
|
||||||
|
return ((obj: any): ArrayValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createAbstractArray(realm: Realm, name: string): AbstractValue {
|
||||||
|
let value = AbstractValue.createFromTemplate(realm, buildExpressionTemplate(name), ArrayValue, [], name);
|
||||||
|
value.intrinsicName = name;
|
||||||
|
let template = createArray(realm, name);
|
||||||
|
template.makePartial();
|
||||||
|
template.makeSimple();
|
||||||
|
value.values = new ValuesDomain(new Set([template]));
|
||||||
|
realm.rebuildNestedProperties(value, name);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createAbstractObject(
|
||||||
|
realm: Realm,
|
||||||
|
name: string | null,
|
||||||
|
objectTypes: ObjectTypeTemplate | null
|
||||||
|
): AbstractValue {
|
||||||
|
if (name === null) {
|
||||||
|
name = "unknown";
|
||||||
|
}
|
||||||
|
let value = AbstractValue.createFromTemplate(realm, buildExpressionTemplate(name), ObjectValue, [], name);
|
||||||
|
value.intrinsicName = name;
|
||||||
|
let template = createObject(realm, objectTypes, name);
|
||||||
|
template.makePartial();
|
||||||
|
template.makeSimple();
|
||||||
|
value.values = new ValuesDomain(new Set([template]));
|
||||||
|
realm.rebuildNestedProperties(value, name);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAbstractObject(
|
||||||
|
realm: Realm,
|
||||||
|
name: string | null,
|
||||||
|
objectTypes: ObjectTypeTemplate | null | string
|
||||||
|
): ObjectValue | AbstractValue {
|
||||||
|
if (typeof objectTypes === "string") {
|
||||||
|
invariant(
|
||||||
|
objectTypes === "empty" || objectTypes === "object",
|
||||||
|
`Expected an object or a string of "empty" or "object" for createAbstractObject() paramater "objectTypes"`
|
||||||
|
);
|
||||||
|
return _createAbstractObject(realm, name, null);
|
||||||
|
}
|
||||||
|
if (objectTypes !== null) {
|
||||||
|
let propTypeObject = {};
|
||||||
|
let objTypes = objectTypes;
|
||||||
|
invariant(objTypes);
|
||||||
|
Object.keys(objTypes).forEach(key => {
|
||||||
|
let value = objTypes[key];
|
||||||
|
let propertyName = name !== null ? `${name}.${key}` : key;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
if (value === "array") {
|
||||||
|
propTypeObject[key] = _createAbstractArray(realm, propertyName);
|
||||||
|
} else if (value === "object") {
|
||||||
|
propTypeObject[key] = _createAbstractObject(realm, propertyName, null);
|
||||||
|
} else {
|
||||||
|
propTypeObject[key] = createAbstractByType(realm, value, propertyName);
|
||||||
|
}
|
||||||
|
} else if (typeof value === "object" && value !== null) {
|
||||||
|
propTypeObject[key] = createAbstractObject(realm, propertyName, value);
|
||||||
|
} else {
|
||||||
|
invariant(false, `Unknown propType value of "${value}" for "${key}"`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return _createAbstractObject(realm, name, propTypeObject);
|
||||||
|
} else {
|
||||||
|
return _createAbstractObject(realm, name, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAbstractByType(realm: Realm, typeNameString: string, name: string): Value {
|
||||||
|
let type = Value.getTypeFromName(typeNameString);
|
||||||
|
invariant(type !== undefined, "createAbstractByType() cannot be undefined");
|
||||||
|
let value = AbstractValue.createFromTemplate(realm, buildExpressionTemplate(name), type, [], name);
|
||||||
|
value.intrinsicName = name;
|
||||||
|
return value;
|
||||||
|
}
|
@ -9,10 +9,73 @@
|
|||||||
|
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
import type { typeAnnotation } from "babel-types";
|
||||||
|
import invariant from "../invariant.js";
|
||||||
import traverse from "babel-traverse";
|
import traverse from "babel-traverse";
|
||||||
import { BabelNode } from "babel-types";
|
import { BabelNode } from "babel-types";
|
||||||
import * as t from "babel-types";
|
import * as t from "babel-types";
|
||||||
|
|
||||||
|
export type ObjectTypeTemplate = {
|
||||||
|
[key: string]: string | ObjectTypeTemplate,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function flowAnnotationToObjectTypeTemplate(annotation: typeAnnotation): string | ObjectTypeTemplate {
|
||||||
|
if (annotation.type === "TypeAnnotation") {
|
||||||
|
return flowAnnotationToObjectTypeTemplate(annotation.typeAnnotation);
|
||||||
|
} else if (annotation.type === "GenericTypeAnnotation") {
|
||||||
|
if (annotation.id.type === "Identifier") {
|
||||||
|
let identifier = annotation.id.name;
|
||||||
|
|
||||||
|
switch (identifier) {
|
||||||
|
case "Function":
|
||||||
|
return "function";
|
||||||
|
case "Object":
|
||||||
|
return "object";
|
||||||
|
case "Array":
|
||||||
|
return "array";
|
||||||
|
case "any":
|
||||||
|
case "empty":
|
||||||
|
return "empty";
|
||||||
|
default:
|
||||||
|
// get the Flow type
|
||||||
|
invariant(false, "Flow types are currently not supported");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invariant(false, "unknown generic Flow type annotation node");
|
||||||
|
}
|
||||||
|
} else if (annotation.type === "EmptyTypeAnnotation") {
|
||||||
|
return "empty";
|
||||||
|
} else if (annotation.type === "BooleanTypeAnnotation") {
|
||||||
|
return "boolean";
|
||||||
|
} else if (annotation.type === "StringTypeAnnotation") {
|
||||||
|
return "string";
|
||||||
|
} else if (annotation.type === "NumberTypeAnnotation") {
|
||||||
|
return "number";
|
||||||
|
} else if (annotation.type === "FunctionTypeAnnotation") {
|
||||||
|
return "function";
|
||||||
|
} else if (annotation.type === "ArrayTypeAnnotation") {
|
||||||
|
return "array";
|
||||||
|
} else if (annotation.type === "ObjectTypeAnnotation") {
|
||||||
|
let obj = {};
|
||||||
|
annotation.properties.forEach(property => {
|
||||||
|
if (property.type === "ObjectTypeProperty") {
|
||||||
|
if (property.key.type === "Identifier") {
|
||||||
|
obj[property.key.name] = flowAnnotationToObjectTypeTemplate(property.value);
|
||||||
|
} else {
|
||||||
|
invariant(false, "only Identifier nodes are supported in ObjectTypeProperty keys");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invariant(false, "only ObjectTypeProperty properties are supported in ObjectTypeAnnotation");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
} else if (annotation.type === "AnyTypeAnnotation") {
|
||||||
|
return "empty";
|
||||||
|
} else {
|
||||||
|
invariant(false, "unknown Flow type annotation node");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Taken directly from Babel:
|
// Taken directly from Babel:
|
||||||
// https://github.com/babel/babel/blob/cde005422701a69ff21044c138c29a5ad23b6d0a/packages/babel-plugin-transform-flow-strip-types/src/index.js#L32-L107
|
// https://github.com/babel/babel/blob/cde005422701a69ff21044c138c29a5ad23b6d0a/packages/babel-plugin-transform-flow-strip-types/src/index.js#L32-L107
|
||||||
// Copyright 2015-present Sebastian McKenzie / Babel project (https://github.com/babel)
|
// Copyright 2015-present Sebastian McKenzie / Babel project (https://github.com/babel)
|
||||||
|
49
src/intrinsics/react-mocks/global.js
vendored
49
src/intrinsics/react-mocks/global.js
vendored
@ -10,10 +10,11 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import type { Realm } from "../../realm.js";
|
import type { Realm } from "../../realm.js";
|
||||||
import { AbstractValue, NativeFunctionValue, ObjectValue, Value } from "../../values/index.js";
|
import { AbstractValue, NativeFunctionValue, Value, StringValue } from "../../values/index.js";
|
||||||
import { ObjectCreate, CreateDataPropertyOrThrow, GetValue } from "../../methods/index.js";
|
import { ObjectCreate } from "../../methods/index.js";
|
||||||
import buildExpressionTemplate from "../../utils/builder.js";
|
import buildExpressionTemplate from "../../utils/builder.js";
|
||||||
import { createMockReactComponent, createMockReactCloneElement } from "./mocks.js";
|
import { createMockReact } from "./mocks.js";
|
||||||
|
import invariant from "../../invariant";
|
||||||
|
|
||||||
export default function(realm: Realm): void {
|
export default function(realm: Realm): void {
|
||||||
let global = realm.$GlobalObject;
|
let global = realm.$GlobalObject;
|
||||||
@ -31,41 +32,25 @@ export default function(realm: Realm): void {
|
|||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
});
|
});
|
||||||
// require("SomeModule") support (makes them abstract)
|
|
||||||
|
// apply React mock (for now just React.Component)
|
||||||
|
global.$DefineOwnProperty("require", {
|
||||||
|
value: new NativeFunctionValue(realm, "global.require", "require", 0, (context, [requireNameVal]) => {
|
||||||
|
invariant(requireNameVal instanceof StringValue);
|
||||||
|
if (requireNameVal.value === "react" || requireNameVal.value === "React") {
|
||||||
|
return createMockReact(realm);
|
||||||
|
}
|
||||||
|
let requireName = `require("${requireNameVal.value}")`;
|
||||||
let type = Value.getTypeFromName("function");
|
let type = Value.getTypeFromName("function");
|
||||||
let requireValue = AbstractValue.createFromTemplate(
|
let requireValue = AbstractValue.createFromTemplate(
|
||||||
realm,
|
realm,
|
||||||
buildExpressionTemplate("require"),
|
buildExpressionTemplate(requireName),
|
||||||
((type: any): typeof Value),
|
((type: any): typeof Value),
|
||||||
[],
|
[],
|
||||||
"require"
|
requireName
|
||||||
);
|
);
|
||||||
requireValue.intrinsicName = "require";
|
requireValue.intrinsicName = requireName;
|
||||||
global.$DefineOwnProperty("require", {
|
return requireValue;
|
||||||
value: requireValue,
|
|
||||||
writable: true,
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
// apply React mock (for now just React.Component)
|
|
||||||
global.$DefineOwnProperty("__createReactMock", {
|
|
||||||
value: new NativeFunctionValue(realm, "global.__createReactMock", "__createReactMock", 0, (context, []) => {
|
|
||||||
// React object
|
|
||||||
let reactValue = ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
|
|
||||||
reactValue.intrinsicName = "React";
|
|
||||||
// React.Component
|
|
||||||
let reactComponent = GetValue(realm, realm.$GlobalEnv.evaluate(createMockReactComponent(), false));
|
|
||||||
reactComponent.intrinsicName = "React.Component";
|
|
||||||
let prototypeValue = ((reactComponent: any): ObjectValue).properties.get("prototype");
|
|
||||||
if (prototypeValue && prototypeValue.descriptor) {
|
|
||||||
((prototypeValue.descriptor.value: any): Value).intrinsicName = `React.Component.prototype`;
|
|
||||||
}
|
|
||||||
CreateDataPropertyOrThrow(realm, reactValue, "Component", reactComponent);
|
|
||||||
// React.cloneElement
|
|
||||||
let reactCloneElement = GetValue(realm, realm.$GlobalEnv.evaluate(createMockReactCloneElement(), false));
|
|
||||||
reactCloneElement.intrinsicName = "React.cloneElement";
|
|
||||||
CreateDataPropertyOrThrow(realm, reactValue, "cloneElement", reactCloneElement);
|
|
||||||
return reactValue;
|
|
||||||
}),
|
}),
|
||||||
writable: true,
|
writable: true,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
|
48
src/intrinsics/react-mocks/mocks.js
vendored
48
src/intrinsics/react-mocks/mocks.js
vendored
@ -9,28 +9,30 @@
|
|||||||
|
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
import type { Realm } from "../../realm.js";
|
||||||
import { parseExpression } from "babylon";
|
import { parseExpression } from "babylon";
|
||||||
|
import { ObjectValue } from "../../values/index.js";
|
||||||
|
import { Get, GetValue } from "../../methods/index.js";
|
||||||
|
import invariant from "../../invariant";
|
||||||
|
|
||||||
// this a mock of React.Component, to be used for tests
|
let reactCode = `
|
||||||
export function createMockReactComponent() {
|
{
|
||||||
let componentCode = `
|
Component: class Component {
|
||||||
class Component {
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
this.props = props || {};
|
this.props = props || {};
|
||||||
this.context = context || {};
|
this.context = context || {};
|
||||||
this.refs = {};
|
this.refs = {};
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
isReactComponent() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
getChildContext() {}
|
getChildContext() {}
|
||||||
}
|
},
|
||||||
`;
|
createElement: function() {
|
||||||
return parseExpression(componentCode, { plugins: ["flow"] });
|
// TODO
|
||||||
}
|
},
|
||||||
|
cloneElement(element, config, children) {
|
||||||
// this a mock of React.Component, to be used for tests
|
|
||||||
export function createMockReactCloneElement() {
|
|
||||||
let cloneElementCode = `
|
|
||||||
function cloneElement(element, config, children) {
|
|
||||||
var propName;
|
var propName;
|
||||||
var RESERVED_PROPS = {
|
var RESERVED_PROPS = {
|
||||||
key: true,
|
key: true,
|
||||||
@ -91,7 +93,25 @@ export function createMockReactCloneElement() {
|
|||||||
props: props,
|
props: props,
|
||||||
_owner: owner,
|
_owner: owner,
|
||||||
};
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
return parseExpression(cloneElementCode, { plugins: ["flow"] });
|
let reactAst = parseExpression(reactCode, { plugins: ["flow"] });
|
||||||
|
|
||||||
|
export function createMockReact(realm: Realm): ObjectValue {
|
||||||
|
let reactValue = GetValue(realm, realm.$GlobalEnv.evaluate(reactAst, false));
|
||||||
|
reactValue.intrinsicName = `require("react")`;
|
||||||
|
invariant(reactValue instanceof ObjectValue);
|
||||||
|
|
||||||
|
let reactComponentValue = Get(realm, reactValue, "Component");
|
||||||
|
reactComponentValue.intrinsicName = `require("react").Component`;
|
||||||
|
invariant(reactComponentValue instanceof ObjectValue);
|
||||||
|
|
||||||
|
let reactComponentPrototypeValue = Get(realm, reactComponentValue, "prototype");
|
||||||
|
reactComponentPrototypeValue.intrinsicName = `require("react").Component.prototype`;
|
||||||
|
|
||||||
|
let reactCloneElementValue = Get(realm, reactValue, "cloneElement");
|
||||||
|
reactCloneElementValue.intrinsicName = `require("react").cloneElement`;
|
||||||
|
|
||||||
|
return reactValue;
|
||||||
}
|
}
|
||||||
|
101
src/react/branching.js
Normal file
101
src/react/branching.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { Realm } from "../realm.js";
|
||||||
|
import {
|
||||||
|
ECMAScriptSourceFunctionValue,
|
||||||
|
Value,
|
||||||
|
UndefinedValue,
|
||||||
|
StringValue,
|
||||||
|
NumberValue,
|
||||||
|
BooleanValue,
|
||||||
|
NullValue,
|
||||||
|
AbstractValue,
|
||||||
|
ArrayValue,
|
||||||
|
ObjectValue,
|
||||||
|
} from "../values/index.js";
|
||||||
|
import { type ReactSerializerState } from "../serializer/types.js";
|
||||||
|
import { isReactElement, addKeyToReactElement, mapOverArrayValue } from "./utils";
|
||||||
|
import { ExpectedBailOut } from "./reconcilation.js";
|
||||||
|
|
||||||
|
// Branch status is used for when Prepack returns an abstract value from a render
|
||||||
|
// that results in a conditional path occuring. This can be problematic for reconcilation
|
||||||
|
// as the reconciler then needs to understand if this is the start of a new branch, or if
|
||||||
|
// it's actually deep into an existing branch. If it's a new branch, we need to apply
|
||||||
|
// keys to the root JSX element so that it keeps it identity (because we're folding trees).
|
||||||
|
// Furthermore, we also need to bail-out of folding class components where they have lifecycle
|
||||||
|
// events, as we can't merge lifecycles of mutliple trees when branched reliably
|
||||||
|
export type BranchStatusEnum = "NO_BRANCH" | "NEW_BRANCH" | "BRANCH";
|
||||||
|
|
||||||
|
// Branch state is used to capture branched ReactElements so they can be analyzed and compared
|
||||||
|
// once all branches have been processed. This allows us to add keys to the respective ReactElement
|
||||||
|
// objects depending on various heuristics (if they have the same "type" for example)
|
||||||
|
// A new branch state is created on a branch status of "NEW_BRANCH" and is reset to null once the branch is no
|
||||||
|
// longer new
|
||||||
|
export class BranchState {
|
||||||
|
constructor() {
|
||||||
|
this._branchesToValidate = [];
|
||||||
|
}
|
||||||
|
_applyBranchedLogicValue(realm: Realm, reactSerializerState: ReactSerializerState, value: Value): void {
|
||||||
|
if (
|
||||||
|
value instanceof StringValue ||
|
||||||
|
value instanceof NumberValue ||
|
||||||
|
value instanceof BooleanValue ||
|
||||||
|
value instanceof NullValue ||
|
||||||
|
value instanceof UndefinedValue
|
||||||
|
) {
|
||||||
|
// terminal values
|
||||||
|
} else if (value instanceof ObjectValue && isReactElement(value)) {
|
||||||
|
addKeyToReactElement(realm, reactSerializerState, value);
|
||||||
|
} else if (value instanceof ArrayValue) {
|
||||||
|
mapOverArrayValue(realm, value, elementValue => {
|
||||||
|
this._applyBranchedLogicValue(realm, reactSerializerState, elementValue);
|
||||||
|
});
|
||||||
|
} else if (value instanceof AbstractValue) {
|
||||||
|
let length = value.args.length;
|
||||||
|
if (length > 0) {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
this._applyBranchedLogicValue(realm, reactSerializerState, value.args[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ExpectedBailOut("Unsupported value encountered when applying branched logic to values");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyBranchedLogic(realm: Realm, reactSerializerState: ReactSerializerState): void {
|
||||||
|
let reactElementType;
|
||||||
|
let applyBranchedLogic = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < this._branchesToValidate.length; i++) {
|
||||||
|
let { type } = this._branchesToValidate[i];
|
||||||
|
if (reactElementType === undefined) {
|
||||||
|
reactElementType = type;
|
||||||
|
} else if (type !== reactElementType) {
|
||||||
|
// the types of the ReactElements do not match, so apply branch logic
|
||||||
|
applyBranchedLogic = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (applyBranchedLogic) {
|
||||||
|
for (let i = 0; i < this._branchesToValidate.length; i++) {
|
||||||
|
this._applyBranchedLogicValue(realm, reactSerializerState, this._branchesToValidate[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captureBranchedValue(type: StringValue | ECMAScriptSourceFunctionValue, value: Value): Value {
|
||||||
|
this._branchesToValidate.push({ type, value });
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
_branchesToValidate: Array<{
|
||||||
|
type: StringValue | ECMAScriptSourceFunctionValue,
|
||||||
|
value: Value,
|
||||||
|
}>;
|
||||||
|
}
|
315
src/react/reconcilation.js
Normal file
315
src/react/reconcilation.js
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { Realm, type Effects } from "../realm.js";
|
||||||
|
import { ModuleTracer } from "../serializer/modules.js";
|
||||||
|
import {
|
||||||
|
ECMAScriptSourceFunctionValue,
|
||||||
|
Value,
|
||||||
|
UndefinedValue,
|
||||||
|
StringValue,
|
||||||
|
NumberValue,
|
||||||
|
BooleanValue,
|
||||||
|
NullValue,
|
||||||
|
AbstractValue,
|
||||||
|
ArrayValue,
|
||||||
|
ObjectValue,
|
||||||
|
} from "../values/index.js";
|
||||||
|
import { ReactStatistics, type ReactSerializerState } from "../serializer/types.js";
|
||||||
|
import { isReactElement, valueIsClassComponent, mapOverArrayValue } from "./utils";
|
||||||
|
import { Get } from "../methods/index.js";
|
||||||
|
import invariant from "../invariant.js";
|
||||||
|
import { flowAnnotationToObjectTypeTemplate } from "../flow/utils.js";
|
||||||
|
import * as t from "babel-types";
|
||||||
|
import type { BabelNodeIdentifier } from "babel-types";
|
||||||
|
import { createAbstractObject } from "../flow/abstractObjectFactories.js";
|
||||||
|
import { CompilerDiagnostic, FatalError } from "../errors.js";
|
||||||
|
import { BranchState, type BranchStatusEnum } from "./branching.js";
|
||||||
|
|
||||||
|
// ExpectedBailOut is like an error, that gets thrown during the reconcilation phase
|
||||||
|
// allowing the reconcilation to continue on other branches of the tree, the message
|
||||||
|
// given to ExpectedBailOut will be assigned to the value.$BailOutReason property and serialized
|
||||||
|
// as a comment in the output source to give the user hints as to what they need to do
|
||||||
|
// to fix the bail-out case
|
||||||
|
export class ExpectedBailOut {
|
||||||
|
message: string;
|
||||||
|
constructor(message: string) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialProps(realm: Realm, componentType: ECMAScriptSourceFunctionValue): ObjectValue | AbstractValue {
|
||||||
|
let propsName = null;
|
||||||
|
let propTypes = null;
|
||||||
|
if (valueIsClassComponent(realm, componentType)) {
|
||||||
|
// it's a class component, so we need to check the type on for props of the component prototype
|
||||||
|
// as we don't support class components yet, throw a fatal error
|
||||||
|
throw new ExpectedBailOut("class components not yet supported");
|
||||||
|
} else {
|
||||||
|
// otherwise it's a functional component, where the first paramater of the function is "props" (if it exists)
|
||||||
|
if (componentType.$FormalParameters.length > 0) {
|
||||||
|
let firstParam = componentType.$FormalParameters[0];
|
||||||
|
if (t.isIdentifier(firstParam)) {
|
||||||
|
propsName = ((firstParam: any): BabelNodeIdentifier).name;
|
||||||
|
}
|
||||||
|
let propsTypeAnnotation = firstParam.typeAnnotation !== undefined && firstParam.typeAnnotation;
|
||||||
|
// we expect that if there's a props paramater, it should always have Flow annotations
|
||||||
|
if (!propsTypeAnnotation) {
|
||||||
|
throw new ExpectedBailOut(`root component missing Flow type annotations for the "props" paramater`);
|
||||||
|
}
|
||||||
|
propTypes = flowAnnotationToObjectTypeTemplate(propsTypeAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createAbstractObject(realm, propsName, propTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialContext(realm: Realm, componentType: ECMAScriptSourceFunctionValue): ObjectValue | AbstractValue {
|
||||||
|
let contextName = null;
|
||||||
|
let contextTypes = null;
|
||||||
|
if (valueIsClassComponent(realm, componentType)) {
|
||||||
|
// it's a class component, so we need to check the type on for context of the component prototype
|
||||||
|
// as we don't support class components yet, throw a fatal error
|
||||||
|
throw new ExpectedBailOut("class components not yet supported");
|
||||||
|
} else {
|
||||||
|
// otherwise it's a functional component, where the second paramater of the function is "context" (if it exists)
|
||||||
|
if (componentType.$FormalParameters.length > 1) {
|
||||||
|
let secondParam = componentType.$FormalParameters[1];
|
||||||
|
if (t.isIdentifier(secondParam)) {
|
||||||
|
contextName = ((secondParam: any): BabelNodeIdentifier).name;
|
||||||
|
}
|
||||||
|
let contextTypeAnnotation = secondParam.typeAnnotation !== undefined && secondParam.typeAnnotation;
|
||||||
|
// we expect that if there's a context param, it should always have Flow annotations
|
||||||
|
if (!contextTypeAnnotation) {
|
||||||
|
throw new ExpectedBailOut(`root component missing Flow type annotations for the "context" paramater`);
|
||||||
|
}
|
||||||
|
contextTypes = flowAnnotationToObjectTypeTemplate(contextTypeAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createAbstractObject(realm, contextName, contextTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Reconciler {
|
||||||
|
constructor(
|
||||||
|
realm: Realm,
|
||||||
|
moduleTracer: ModuleTracer,
|
||||||
|
statistics: ReactStatistics,
|
||||||
|
reactSerializerState: ReactSerializerState
|
||||||
|
) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.moduleTracer = moduleTracer;
|
||||||
|
this.statistics = statistics;
|
||||||
|
this.reactSerializerState = reactSerializerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
realm: Realm;
|
||||||
|
moduleTracer: ModuleTracer;
|
||||||
|
statistics: ReactStatistics;
|
||||||
|
reactSerializerState: ReactSerializerState;
|
||||||
|
|
||||||
|
render(componentType: ECMAScriptSourceFunctionValue): Effects {
|
||||||
|
return this.realm.wrapInGlobalEnv(() =>
|
||||||
|
// 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(() => {
|
||||||
|
// initialProps and initialContext are created from Flow types from:
|
||||||
|
// - if a functional component, the 1st and 2nd paramater of function
|
||||||
|
// - if a class component, use this.props and this.context
|
||||||
|
// if there are no Flow types for props or context, we will throw a
|
||||||
|
// FatalError, unless it's a functional component that has no paramater
|
||||||
|
// i.e let MyComponent = () => <div>Hello world</div>
|
||||||
|
try {
|
||||||
|
let initialProps = getInitialProps(this.realm, componentType);
|
||||||
|
let initialContext = getInitialContext(this.realm, componentType);
|
||||||
|
let { result } = this._renderAsDeepAsPossible(componentType, initialProps, initialContext, "NO_BRANCH", null);
|
||||||
|
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 ExpectedBailOut) {
|
||||||
|
let diagnostic = new CompilerDiagnostic(
|
||||||
|
`__registerReactComponentRoot() failed due to - ${error.message}`,
|
||||||
|
this.realm.currentLocation,
|
||||||
|
"PP0019",
|
||||||
|
"FatalError"
|
||||||
|
);
|
||||||
|
this.realm.handleError(diagnostic);
|
||||||
|
throw new FatalError();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_renderAsDeepAsPossible(
|
||||||
|
componentType: ECMAScriptSourceFunctionValue,
|
||||||
|
props: ObjectValue | AbstractValue,
|
||||||
|
context: ObjectValue | AbstractValue,
|
||||||
|
branchStatus: BranchStatusEnum,
|
||||||
|
branchState: BranchState | null
|
||||||
|
) {
|
||||||
|
let { value, childContext } = this._renderOneLevel(componentType, props, context);
|
||||||
|
let result = this._resolveDeeply(value, childContext, branchStatus, branchState);
|
||||||
|
return {
|
||||||
|
result,
|
||||||
|
childContext,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_renderOneLevel(
|
||||||
|
componentType: ECMAScriptSourceFunctionValue,
|
||||||
|
props: ObjectValue | AbstractValue,
|
||||||
|
context: ObjectValue | AbstractValue
|
||||||
|
) {
|
||||||
|
if (valueIsClassComponent(this.realm, componentType)) {
|
||||||
|
// for now we don't support class components, so we throw a ExpectedBailOut
|
||||||
|
throw new ExpectedBailOut("class components not yet supported");
|
||||||
|
} else {
|
||||||
|
invariant(componentType.$Call, "Expected componentType to be a FunctionValue with $Call method");
|
||||||
|
let value = componentType.$Call(this.realm.intrinsics.undefined, [props, context]);
|
||||||
|
return { value, childContext: context };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_resolveDeeply(
|
||||||
|
value: Value,
|
||||||
|
context: ObjectValue | AbstractValue,
|
||||||
|
branchStatus: BranchStatusEnum,
|
||||||
|
branchState: BranchState | null
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
value instanceof StringValue ||
|
||||||
|
value instanceof NumberValue ||
|
||||||
|
value instanceof BooleanValue ||
|
||||||
|
value instanceof NullValue ||
|
||||||
|
value instanceof UndefinedValue
|
||||||
|
) {
|
||||||
|
// terminal values
|
||||||
|
return value;
|
||||||
|
} else if (value instanceof AbstractValue) {
|
||||||
|
let length = value.args.length;
|
||||||
|
if (length > 0) {
|
||||||
|
let newBranchState = new BranchState();
|
||||||
|
// TODO investigate what other kinds than "conditional" might be safe to deeply resolve
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
value.args[i] = this._resolveDeeply(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 ArrayValue) {
|
||||||
|
this._resolveFragment(value, context, branchStatus, branchState);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value instanceof ObjectValue && isReactElement(value)) {
|
||||||
|
// we call value reactElement, to make it clearer what we're dealing with in this block
|
||||||
|
let reactElement = value;
|
||||||
|
let typeValue = Get(this.realm, reactElement, "type");
|
||||||
|
let propsValue = Get(this.realm, reactElement, "props");
|
||||||
|
let refValue = Get(this.realm, reactElement, "ref");
|
||||||
|
if (typeValue instanceof StringValue) {
|
||||||
|
// terminal host component. Start evaluating its children.
|
||||||
|
if (propsValue instanceof ObjectValue) {
|
||||||
|
let childrenProperty = propsValue.properties.get("children");
|
||||||
|
if (childrenProperty) {
|
||||||
|
let childrenPropertyDescriptor = childrenProperty.descriptor;
|
||||||
|
// if the descriptor is undefined, the property is likely deleted, if it exists
|
||||||
|
// proceed to resolve the children
|
||||||
|
if (childrenPropertyDescriptor !== undefined) {
|
||||||
|
let childrenPropertyValue = childrenPropertyDescriptor.value;
|
||||||
|
invariant(childrenPropertyValue instanceof Value, `Bad "children" prop passed in JSXElement`);
|
||||||
|
let resolvedChildren = this._resolveDeeply(childrenPropertyValue, context, branchStatus, branchState);
|
||||||
|
childrenPropertyDescriptor.value = resolvedChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reactElement;
|
||||||
|
}
|
||||||
|
// we do not support "ref" on <Component /> ReactElements
|
||||||
|
if (!(refValue instanceof NullValue)) {
|
||||||
|
this._assignBailOutMessage(reactElement, `Bail-out: refs are not supported on <Components />`);
|
||||||
|
return reactElement;
|
||||||
|
}
|
||||||
|
if (!(propsValue instanceof ObjectValue || propsValue instanceof AbstractValue)) {
|
||||||
|
this._assignBailOutMessage(
|
||||||
|
reactElement,
|
||||||
|
`Bail-out: props on <Component /> was not not an ObjectValue or an AbstractValue`
|
||||||
|
);
|
||||||
|
return reactElement;
|
||||||
|
}
|
||||||
|
if (!(typeValue instanceof ECMAScriptSourceFunctionValue)) {
|
||||||
|
this._assignBailOutMessage(
|
||||||
|
reactElement,
|
||||||
|
`Bail-out: type on <Component /> was not a ECMAScriptSourceFunctionValue`
|
||||||
|
);
|
||||||
|
return reactElement;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let { result } = this._renderAsDeepAsPossible(
|
||||||
|
typeValue,
|
||||||
|
propsValue,
|
||||||
|
context,
|
||||||
|
branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
if (result instanceof 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 ExpectedBailOut) {
|
||||||
|
this._assignBailOutMessage(reactElement, "Bail-out: " + error.message);
|
||||||
|
} else if (error instanceof 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 ExpectedBailOut("unsupported value type during reconcilation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_assignBailOutMessage(reactElement: ObjectValue, message: string): void {
|
||||||
|
// $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_resolveFragment(
|
||||||
|
arrayValue: ArrayValue,
|
||||||
|
context: ObjectValue | AbstractValue,
|
||||||
|
branchStatus: BranchStatusEnum,
|
||||||
|
branchState: BranchState | null
|
||||||
|
) {
|
||||||
|
mapOverArrayValue(this.realm, arrayValue, (elementValue, elementPropertyDescriptor) => {
|
||||||
|
elementPropertyDescriptor.value = this._resolveDeeply(elementValue, context, branchStatus, branchState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,21 @@
|
|||||||
|
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
import { Realm } from "../realm.js";
|
||||||
import type { BabelNode, BabelNodeJSXIdentifier } from "babel-types";
|
import type { BabelNode, BabelNodeJSXIdentifier } from "babel-types";
|
||||||
import { Value, ObjectValue, SymbolValue } from "../values/index.js";
|
import {
|
||||||
|
Value,
|
||||||
|
NumberValue,
|
||||||
|
ObjectValue,
|
||||||
|
SymbolValue,
|
||||||
|
FunctionValue,
|
||||||
|
StringValue,
|
||||||
|
ArrayValue,
|
||||||
|
} from "../values/index.js";
|
||||||
import { Get } from "../methods/index.js";
|
import { Get } from "../methods/index.js";
|
||||||
|
import { computeBinary } from "../evaluators/BinaryExpression.js";
|
||||||
|
import { type ReactSerializerState } from "../serializer/types.js";
|
||||||
|
import invariant from "../invariant.js";
|
||||||
|
|
||||||
export function isReactElement(val: Value): boolean {
|
export function isReactElement(val: Value): boolean {
|
||||||
if (val instanceof ObjectValue && val.properties.has("$$typeof")) {
|
if (val instanceof ObjectValue && val.properties.has("$$typeof")) {
|
||||||
@ -33,6 +45,35 @@ export function isReactComponent(name: string) {
|
|||||||
return name.length > 0 && name[0] === name[0].toUpperCase();
|
return name.length > 0 && name[0] === name[0].toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function valueIsClassComponent(realm: Realm, value: Value) {
|
||||||
|
if (!(value instanceof FunctionValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.$Prototype instanceof ObjectValue) {
|
||||||
|
let prototype = Get(realm, value.$Prototype, "prototype");
|
||||||
|
if (prototype instanceof ObjectValue) {
|
||||||
|
return prototype.properties.has("isReactComponent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addKeyToReactElement(
|
||||||
|
realm: Realm,
|
||||||
|
reactSerializerState: ReactSerializerState,
|
||||||
|
reactElement: ObjectValue
|
||||||
|
): void {
|
||||||
|
// we need to apply a key when we're branched
|
||||||
|
let currentKeyValue = Get(realm, reactElement, "key") || realm.intrinsics.null;
|
||||||
|
let uniqueKey = getUniqueReactElementKey("", reactSerializerState.usedReactElementKeys);
|
||||||
|
let newKeyValue = new StringValue(realm, uniqueKey);
|
||||||
|
if (currentKeyValue !== realm.intrinsics.null) {
|
||||||
|
newKeyValue = computeBinary(realm, "+", currentKeyValue, newKeyValue);
|
||||||
|
}
|
||||||
|
// TODO: This might not be safe in DEV because these objects are frozen (Object.freeze).
|
||||||
|
// We should probably go behind the scenes in this case to by-pass that.
|
||||||
|
reactElement.$Set("key", newKeyValue, reactElement);
|
||||||
|
}
|
||||||
// we create a unique key for each JSXElement to prevent collisions
|
// we create a unique key for each JSXElement to prevent collisions
|
||||||
// otherwise React will detect a missing/conflicting key at runtime and
|
// otherwise React will detect a missing/conflicting key at runtime and
|
||||||
// this can break the reconcilation of JSXElements in arrays
|
// this can break the reconcilation of JSXElements in arrays
|
||||||
@ -47,3 +88,19 @@ export function getUniqueReactElementKey(index?: string, usedReactElementKeys: S
|
|||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a helper function to map over ArrayValues
|
||||||
|
export function mapOverArrayValue(realm: Realm, arrayValue: ArrayValue, mapFunc: Function): void {
|
||||||
|
let lengthValue = Get(realm, arrayValue, "length");
|
||||||
|
invariant(lengthValue instanceof NumberValue, "Invalid length on ArrayValue during reconcilation");
|
||||||
|
let length = lengthValue.value;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
let elementProperty = arrayValue.properties.get("" + i);
|
||||||
|
let elementPropertyDescriptor = elementProperty && elementProperty.descriptor;
|
||||||
|
invariant(elementPropertyDescriptor, `Invalid ArrayValue[${i}] descriptor`);
|
||||||
|
let elementValue = elementPropertyDescriptor.value;
|
||||||
|
if (elementValue instanceof Value) {
|
||||||
|
mapFunc(elementValue, elementPropertyDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -830,7 +830,13 @@ export class ResidualHeapSerializer {
|
|||||||
let openingElement = t.jSXOpeningElement(identifier, (attributes: any), children.length === 0);
|
let openingElement = t.jSXOpeningElement(identifier, (attributes: any), children.length === 0);
|
||||||
let closingElement = t.jSXClosingElement(identifier);
|
let closingElement = t.jSXClosingElement(identifier);
|
||||||
|
|
||||||
return t.jSXElement(openingElement, closingElement, children, children.length === 0);
|
let jsxElement = t.jSXElement(openingElement, closingElement, children, children.length === 0);
|
||||||
|
// if there has been a bail-out, we create an inline BlockComment node before the JSX element
|
||||||
|
if (val.$BailOutReason !== undefined) {
|
||||||
|
// $BailOutReason contains an optional string of what to print out in the comment
|
||||||
|
jsxElement.leadingComments = [({ type: "BlockComment", value: `${val.$BailOutReason}` }: any)];
|
||||||
|
}
|
||||||
|
return jsxElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
_serializeValueMap(val: ObjectValue): BabelNodeExpression {
|
_serializeValueMap(val: ObjectValue): BabelNodeExpression {
|
||||||
|
@ -26,7 +26,8 @@ import {
|
|||||||
import { Get } from "../methods/index.js";
|
import { Get } from "../methods/index.js";
|
||||||
import { ModuleTracer } from "./modules.js";
|
import { ModuleTracer } from "./modules.js";
|
||||||
import buildTemplate from "babel-template";
|
import buildTemplate from "babel-template";
|
||||||
import { type ReactSerializerState } from "./types";
|
import { ReactStatistics, type ReactSerializerState } from "./types";
|
||||||
|
import { Reconciler } from "../react/reconcilation.js";
|
||||||
import * as t from "babel-types";
|
import * as t from "babel-types";
|
||||||
|
|
||||||
export class Functions {
|
export class Functions {
|
||||||
@ -109,16 +110,17 @@ export class Functions {
|
|||||||
return recordedAdditionalFunctions;
|
return recordedAdditionalFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkReactRootComponents(react: ReactSerializerState): void {
|
checkReactRootComponents(statistics: ReactStatistics, react: ReactSerializerState): void {
|
||||||
let recordedReactRootComponents = this.__generateAdditionalFunctions("__reactComponentRoots");
|
let recordedReactRootComponents = this.__generateAdditionalFunctions("__reactComponentRoots");
|
||||||
|
|
||||||
// Get write effects of the components
|
// Get write effects of the components
|
||||||
for (let [funcValue] of recordedReactRootComponents) {
|
for (let [funcValue] of recordedReactRootComponents) {
|
||||||
|
let reconciler = new Reconciler(this.realm, this.moduleTracer, statistics, react);
|
||||||
invariant(
|
invariant(
|
||||||
funcValue instanceof ECMAScriptSourceFunctionValue,
|
funcValue instanceof ECMAScriptSourceFunctionValue,
|
||||||
"only ECMAScriptSourceFunctionValue function values are supported as React root components"
|
"only ECMAScriptSourceFunctionValue function values are supported as React root components"
|
||||||
);
|
);
|
||||||
throw new FatalError("TODO: implement functional component folding");
|
this.writeEffects.set(funcValue, reconciler.render(funcValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ export class Serializer {
|
|||||||
let reactStatistics = null;
|
let reactStatistics = null;
|
||||||
if (this.realm.react.enabled) {
|
if (this.realm.react.enabled) {
|
||||||
reactStatistics = new ReactStatistics();
|
reactStatistics = new ReactStatistics();
|
||||||
this.functions.checkReactRootComponents(this.react);
|
this.functions.checkReactRootComponents(reactStatistics, this.react);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.initializeMoreModules) {
|
if (this.options.initializeMoreModules) {
|
||||||
|
@ -244,6 +244,9 @@ export default class ObjectValue extends ConcreteValue {
|
|||||||
intrinsicNameGenerated: void | true;
|
intrinsicNameGenerated: void | true;
|
||||||
hashValue: void | number;
|
hashValue: void | number;
|
||||||
|
|
||||||
|
// ReactElement
|
||||||
|
$BailOutReason: void | string;
|
||||||
|
|
||||||
equals(x: Value): boolean {
|
equals(x: Value): boolean {
|
||||||
return x instanceof ObjectValue && this.getHash() === x.getHash();
|
return x instanceof ObjectValue && this.getHash() === x.getHash();
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function MaybeShow(props) {
|
function MaybeShow(props) {
|
||||||
if (props.show) {
|
if (props.show) {
|
||||||
@ -19,7 +17,7 @@ function Override(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function App(props/*: {show: boolean}*/) {
|
function App(props: {show: boolean}) {
|
||||||
return (
|
return (
|
||||||
<Override overrideShow={props.show}>
|
<Override overrideShow={props.show}>
|
||||||
<MaybeShow show={true}>
|
<MaybeShow show={true}>
|
||||||
@ -40,8 +38,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function MaybeShow(props) {
|
function MaybeShow(props) {
|
||||||
if (props.show) {
|
if (props.show) {
|
||||||
@ -25,8 +23,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function SubChild(props, context) {
|
function SubChild(props, context) {
|
||||||
return <span>The context title is: {context.title}</span>;
|
return <span>The context title is: {context.title}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Child(props: any, context/*: {title: string}*/) {
|
function Child(props: any, context: {title: string}) {
|
||||||
return <span><SubChild /></span>;
|
return <span><SubChild /></span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +47,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(Child);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function Fn(props) {
|
function Fn(props) {
|
||||||
return <div>Hello {props[props.dynamicKey]}</div>;
|
return <div>Hello {props[props.dynamicKey]}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function App(props/*: {dynamicKey: string}*/) {
|
function App(props: {dynamicKey: string}) {
|
||||||
return <Fn foo="World" dynamicKey={props.dynamicKey} />;
|
return <Fn foo="World" dynamicKey={props.dynamicKey} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,8 +16,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
// we can't use ES2015 classes in Prepack yet (they don't serialize)
|
// we can't use ES2015 classes in Prepack yet (they don't serialize)
|
||||||
// so we have to use ES5 instead
|
// so we have to use ES5 instead
|
||||||
@ -32,7 +30,7 @@ var Stateful = (function (superclass) {
|
|||||||
return Stateful;
|
return Stateful;
|
||||||
}(React.Component));
|
}(React.Component));
|
||||||
|
|
||||||
function App(props/*: {switch: boolean}*/) {
|
function App(props: {switch: boolean}) {
|
||||||
if (props.switch) {
|
if (props.switch) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -67,8 +65,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
// we can't use ES2015 classes in Prepack yet (they don't serialize)
|
// we can't use ES2015 classes in Prepack yet (they don't serialize)
|
||||||
// so we have to use ES5 instead
|
// so we have to use ES5 instead
|
||||||
@ -39,7 +37,7 @@ function SettingsPane() {
|
|||||||
return <div key='ha'><Stateful /></div>;
|
return <div key='ha'><Stateful /></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function App(props/*: {switch: boolean}*/) {
|
function App(props: {switch: boolean}) {
|
||||||
if (props.switch) {
|
if (props.switch) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -74,8 +72,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function A(props) {
|
function A(props) {
|
||||||
return 'Hello, ';
|
return 'Hello, ';
|
||||||
@ -25,8 +23,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function A() {
|
function A() {
|
||||||
}
|
}
|
||||||
@ -26,8 +24,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function A(props) {
|
function A(props) {
|
||||||
return props.children;
|
return props.children;
|
||||||
}
|
}
|
||||||
@ -23,8 +22,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
let refB = false;
|
let refB = false;
|
||||||
|
|
||||||
@ -15,7 +13,7 @@ function A(foo) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function App({rootRef}/*: {rootRef: Function}*/) {
|
function App({rootRef}: {rootRef: Function}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<A rootRef={rootRef} />
|
<A rootRef={rootRef} />
|
||||||
@ -37,8 +35,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
function A(props) {
|
function A(props) {
|
||||||
return <div>Hello {props.x}</div>;
|
return <div>Hello {props.x}</div>;
|
||||||
@ -27,14 +25,12 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
App.getTrials = function(renderer, Root) {
|
App.getTrials = function(renderer, Root) {
|
||||||
React.createElement("div")
|
|
||||||
renderer.update(<Root />);
|
renderer.update(<Root />);
|
||||||
return [['simple render', renderer.toJSON()]];
|
return [['simple render', renderer.toJSON()]];
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
@ -1,8 +1,6 @@
|
|||||||
if (this.__createReactMock) {
|
|
||||||
var React = __createReactMock();
|
|
||||||
} else {
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
}
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
// we can't use ES2015 classes in Prepack yet (they don't serialize)
|
// we can't use ES2015 classes in Prepack yet (they don't serialize)
|
||||||
// so we have to use ES5 instead
|
// so we have to use ES5 instead
|
||||||
@ -40,7 +38,7 @@ function SettingsPane() {
|
|||||||
return <Stateful>Bye</Stateful>;
|
return <Stateful>Bye</Stateful>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function App(props/*: {switch: boolean}*/) {
|
function App(props: {switch: boolean}) {
|
||||||
if (props.switch) {
|
if (props.switch) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -75,8 +73,7 @@ App.getTrials = function(renderer, Root) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.__registerReactComponentRoot) {
|
if (this.__registerReactComponentRoot) {
|
||||||
// to be used when component folding is added in separate PR
|
__registerReactComponentRoot(App);
|
||||||
// __registerReactComponentRoot(App);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = App;
|
module.exports = App;
|
||||||
|
23
test/react/functional-components/type-same.js
Normal file
23
test/react/functional-components/type-same.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
var React = require('react');
|
||||||
|
// the JSX transform converts to React, so we need to add it back in
|
||||||
|
this['React'] = React;
|
||||||
|
|
||||||
|
function Foo() {
|
||||||
|
return <div>123</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
function App(props: {yar: boolean}) {
|
||||||
|
return <div>{props.yar ? <Foo arg={1} /> : <Foo arg={2} />}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
App.getTrials = function(renderer, Root) {
|
||||||
|
renderer.update(<Root />);
|
||||||
|
let childKey = renderer.toTree().rendered.props.children.key
|
||||||
|
return [['no added keys to child components', childKey]];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.__registerReactComponentRoot) {
|
||||||
|
__registerReactComponentRoot(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = App;
|
Loading…
Reference in New Issue
Block a user