diff --git a/scripts/test-react.js b/scripts/test-react.js
index 22165f3ce..e24a04b2c 100644
--- a/scripts/test-react.js
+++ b/scripts/test-react.js
@@ -22,7 +22,7 @@ let { expect, describe, it } = global;
function runTestSuite(outputJsx) {
let reactTestRoot = path.join(__dirname, "../test/react/");
let prepackOptions = {
- compatibility: "react-mocks",
+ compatibility: "fb-www",
internalDebug: true,
serialize: true,
uniqueSuffix: "",
@@ -57,8 +57,20 @@ function runTestSuite(outputJsx) {
let moduleShim = { exports: null };
let requireShim = name => {
switch (name) {
+ case "React":
case "react":
return React;
+ case "RelayModern":
+ return {
+ QueryRenderer(props) {
+ return props.render({ props: {}, error: null });
+ },
+ graphql() {
+ return null;
+ },
+ };
+ case "FBEnvironment":
+ return {};
default:
throw new Error(`Unrecognized import: "${name}".`);
}
@@ -230,6 +242,14 @@ function runTestSuite(outputJsx) {
await runTest(directory, "classes-with-state.js");
});
});
+
+ describe("fb-www mocks", () => {
+ let directory = "mocks";
+
+ it("fb-www", async () => {
+ await runTest(directory, "fb1.js");
+ });
+ });
});
}
diff --git a/src/globals.js b/src/globals.js
index 609947bdd..d8627769f 100644
--- a/src/globals.js
+++ b/src/globals.js
@@ -13,14 +13,14 @@ import type { Realm } from "./realm.js";
import initializePrepackGlobals from "./intrinsics/prepack/global.js";
import initializeDOMGlobals from "./intrinsics/dom/global.js";
import initializeReactNativeGlobals from "./intrinsics/react-native/global.js";
-import initializeReactMocks from "./intrinsics/react-mocks/global.js";
+import initializeReactMocks from "./intrinsics/fb-www/global.js";
export default function(realm: Realm): Realm {
initializePrepackGlobals(realm);
if (realm.isCompatibleWith("browser")) {
initializeDOMGlobals(realm);
}
- if (realm.isCompatibleWith("react-mocks")) {
+ if (realm.isCompatibleWith("fb-www")) {
initializeDOMGlobals(realm);
initializeReactMocks(realm);
}
diff --git a/src/intrinsics/react-mocks/global.js b/src/intrinsics/fb-www/global.js
similarity index 66%
rename from src/intrinsics/react-mocks/global.js
rename to src/intrinsics/fb-www/global.js
index 0b1a93c70..24099e035 100644
--- a/src/intrinsics/react-mocks/global.js
+++ b/src/intrinsics/fb-www/global.js
@@ -12,7 +12,8 @@
import type { Realm } from "../../realm.js";
import { AbstractValue, NativeFunctionValue, Value, StringValue } from "../../values/index.js";
import buildExpressionTemplate from "../../utils/builder.js";
-import { createMockReact } from "./mocks.js";
+import { createMockReact } from "./react-mocks.js";
+import { createMockReactRelay } from "./relay-mocks.js";
import invariant from "../../invariant";
export default function(realm: Realm): void {
@@ -31,13 +32,22 @@ export default function(realm: Realm): void {
global.$DefineOwnProperty("require", {
value: new NativeFunctionValue(realm, "global.require", "require", 0, (context, [requireNameVal]) => {
invariant(requireNameVal instanceof StringValue);
- if (requireNameVal.value === "react" || requireNameVal.value === "React") {
- if (realm.react.reactLibraryObject === undefined) {
- let reactLibraryObject = createMockReact(realm);
- realm.react.reactLibraryObject = reactLibraryObject;
- return reactLibraryObject;
+ let requireNameValValue = requireNameVal.value;
+
+ if (requireNameValValue === "react" || requireNameValValue === "React") {
+ if (realm.fbLibraries.react === undefined) {
+ let react = createMockReact(realm, requireNameValValue);
+ realm.fbLibraries.react = react;
+ return react;
}
- return realm.react.reactLibraryObject;
+ return realm.fbLibraries.react;
+ } else if (requireNameValValue === "react-relay" || requireNameValValue === "RelayModern") {
+ if (realm.fbLibraries.reactRelay === undefined) {
+ let reactRelay = createMockReactRelay(realm, requireNameValValue);
+ realm.fbLibraries.reactRelay = reactRelay;
+ return reactRelay;
+ }
+ return realm.fbLibraries.reactRelay;
}
let requireName = `require("${requireNameVal.value}")`;
let type = Value.getTypeFromName("function");
diff --git a/src/intrinsics/react-mocks/mocks.js b/src/intrinsics/fb-www/react-mocks.js
similarity index 91%
rename from src/intrinsics/react-mocks/mocks.js
rename to src/intrinsics/fb-www/react-mocks.js
index a831644ac..f230bf506 100644
--- a/src/intrinsics/react-mocks/mocks.js
+++ b/src/intrinsics/fb-www/react-mocks.js
@@ -251,7 +251,7 @@ let reactCode = `
`;
let reactAst = parseExpression(reactCode, { plugins: ["flow"] });
-export function createMockReact(realm: Realm): ObjectValue {
+export function createMockReact(realm: Realm, reactRequireName: string): ObjectValue {
let reactFactory = Environment.GetValue(realm, realm.$GlobalEnv.evaluate(reactAst, false));
invariant(reactFactory instanceof ECMAScriptSourceFunctionValue);
@@ -268,35 +268,35 @@ export function createMockReact(realm: Realm): ObjectValue {
getReactSymbol("react.symbol", realm),
currentOwner,
]);
- reactValue.intrinsicName = `require("react")`;
+ reactValue.intrinsicName = `require("${reactRequireName}")`;
invariant(reactValue instanceof ObjectValue);
let reactComponentValue = Get(realm, reactValue, "Component");
- reactComponentValue.intrinsicName = `require("react").Component`;
+ reactComponentValue.intrinsicName = `require("${reactRequireName}").Component`;
invariant(reactComponentValue instanceof ECMAScriptFunctionValue);
let reactPureComponentValue = Get(realm, reactValue, "PureComponent");
- reactPureComponentValue.intrinsicName = `require("react").PureComponent`;
+ reactPureComponentValue.intrinsicName = `require("${reactRequireName}").PureComponent`;
invariant(reactPureComponentValue instanceof ECMAScriptFunctionValue);
reactComponentValue.$FunctionKind = "normal";
invariant(reactComponentValue instanceof ObjectValue);
let reactComponentPrototypeValue = Get(realm, reactComponentValue, "prototype");
- reactComponentPrototypeValue.intrinsicName = `require("react").Component.prototype`;
+ reactComponentPrototypeValue.intrinsicName = `require("${reactRequireName}").Component.prototype`;
let reactPureComponentPrototypeValue = Get(realm, reactPureComponentValue, "prototype");
- reactPureComponentPrototypeValue.intrinsicName = `require("react").PureComponent.prototype`;
+ reactPureComponentPrototypeValue.intrinsicName = `require("${reactRequireName}").PureComponent.prototype`;
let reactCloneElementValue = Get(realm, reactValue, "cloneElement");
- reactCloneElementValue.intrinsicName = `require("react").cloneElement`;
+ reactCloneElementValue.intrinsicName = `require("${reactRequireName}").cloneElement`;
let reactCreateElementValue = Get(realm, reactValue, "createElement");
- reactCreateElementValue.intrinsicName = `require("react").createElement`;
+ reactCreateElementValue.intrinsicName = `require("${reactRequireName}").createElement`;
let reactIsValidElementValue = Get(realm, reactValue, "isValidElement");
- reactIsValidElementValue.intrinsicName = `require("react").isValidElement`;
+ reactIsValidElementValue.intrinsicName = `require("${reactRequireName}").isValidElement`;
let reactChildrenValue = Get(realm, reactValue, "Children");
- reactChildrenValue.intrinsicName = `require("react").Children`;
+ reactChildrenValue.intrinsicName = `require("${reactRequireName}").Children`;
return reactValue;
}
diff --git a/src/intrinsics/fb-www/relay-mocks.js b/src/intrinsics/fb-www/relay-mocks.js
new file mode 100644
index 000000000..50985dd7f
--- /dev/null
+++ b/src/intrinsics/fb-www/relay-mocks.js
@@ -0,0 +1,33 @@
+/**
+ * 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 type { Realm } from "../../realm.js";
+import { ObjectValue } from "../../values/index.js";
+import { Create } from "../../singletons.js";
+import { createAbstract } from "../prepack/utils.js";
+
+export function createMockReactRelay(realm: Realm, relayRequireName: string): ObjectValue {
+ let reactRelay = new ObjectValue(realm, realm.intrinsics.ObjectPrototype, `require("${relayRequireName}")`, true);
+ // for QueryRenderer, we want to leave the component alone but process it's "render" prop
+ let queryRendererComponent = createAbstract(realm, "function", `require("${relayRequireName}").QueryRenderer`);
+ Create.CreateDataPropertyOrThrow(realm, reactRelay, "QueryRenderer", queryRendererComponent);
+
+ let graphql = createAbstract(realm, "function", `require("${relayRequireName}").graphql`);
+ Create.CreateDataPropertyOrThrow(realm, reactRelay, "graphql", graphql);
+
+ let createFragmentContainer = createAbstract(
+ realm,
+ "function",
+ `require("${relayRequireName}").createFragmentContainer`
+ );
+ Create.CreateDataPropertyOrThrow(realm, reactRelay, "createFragmentContainer", createFragmentContainer);
+ return reactRelay;
+}
diff --git a/src/options.js b/src/options.js
index ef0a8eea9..760bee9b4 100644
--- a/src/options.js
+++ b/src/options.js
@@ -11,15 +11,8 @@
import type { ErrorHandler } from "./errors.js";
-export type Compatibility = "browser" | "jsc-600-1-4-17" | "mobile" | "node-source-maps" | "node-cli" | "react-mocks";
-export const CompatibilityValues = [
- "browser",
- "jsc-600-1-4-17",
- "mobile",
- "node-source-maps",
- "node-cli",
- "react-mocks",
-];
+export type Compatibility = "browser" | "jsc-600-1-4-17" | "mobile" | "node-source-maps" | "node-cli" | "fb-www";
+export const CompatibilityValues = ["browser", "jsc-600-1-4-17", "mobile", "node-source-maps", "node-cli", "fb-www"];
export type ReactOutputTypes = "create-element" | "jsx";
export type RealmOptions = {
diff --git a/src/react/branching.js b/src/react/branching.js
index 8de8bae8e..9f4694bc3 100644
--- a/src/react/branching.js
+++ b/src/react/branching.js
@@ -25,6 +25,7 @@ import {
import { type ReactSerializerState } from "../serializer/types.js";
import { isReactElement, addKeyToReactElement, mapOverArrayValue } from "./utils";
import { ExpectedBailOut } from "./errors.js";
+import invariant from "../invariant";
// 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
@@ -90,7 +91,8 @@ export class BranchState {
}
}
}
- captureBranchedValue(type: StringValue | ECMAScriptSourceFunctionValue, value: Value): Value {
+ captureBranchedValue(type: Value, value: Value): Value {
+ invariant(type instanceof ECMAScriptSourceFunctionValue || type instanceof StringValue);
this._branchesToValidate.push({ type, value });
return value;
}
diff --git a/src/react/components.js b/src/react/components.js
index 31e293ba5..41f5e535e 100644
--- a/src/react/components.js
+++ b/src/react/components.js
@@ -29,16 +29,21 @@ const lifecycleMethods = new Set([
"componentWillReceiveProps",
]);
-export function getInitialProps(realm: Realm, componentType: ECMAScriptSourceFunctionValue): AbstractObjectValue {
+export function getInitialProps(
+ realm: Realm,
+ componentType: ECMAScriptSourceFunctionValue | null
+): AbstractObjectValue {
let propsName = null;
- if (valueIsClassComponent(realm, componentType)) {
- propsName = "this.props";
- } 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;
+ if (componentType !== null) {
+ if (valueIsClassComponent(realm, componentType)) {
+ propsName = "this.props";
+ } 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;
+ }
}
}
}
diff --git a/src/react/reconcilation.js b/src/react/reconcilation.js
index 02a4c7c94..a7b4d2747 100644
--- a/src/react/reconcilation.js
+++ b/src/react/reconcilation.js
@@ -33,6 +33,8 @@ import { BranchState, type BranchStatusEnum } from "./branching.js";
import { getInitialProps, getInitialContext, createClassInstance, createSimpleClassInstance } from "./components.js";
import { ExpectedBailOut, SimpleClassBailOut } from "./errors.js";
+type RenderStrategy = "NORMAL" | "RELAY_QUERY_RENDERER";
+
export class Reconciler {
constructor(
realm: Realm,
@@ -149,13 +151,26 @@ export class Reconciler {
return componentType.$Call(this.realm.intrinsics.undefined, [props, context]);
}
+ _renderRelayQueryRendererComponent(
+ reactElement: ObjectValue,
+ props: ObjectValue | AbstractObjectValue,
+ context: ObjectValue | AbstractObjectValue
+ ) {
+ // TODO: for now we do nothing, in the future we want to evaluate the render prop of this component
+ return {
+ result: reactElement,
+ childContext: context,
+ };
+ }
+
_renderComponent(
- componentType: ECMAScriptSourceFunctionValue,
+ componentType: Value,
props: ObjectValue | AbstractObjectValue,
context: ObjectValue | AbstractObjectValue,
branchStatus: BranchStatusEnum,
branchState: BranchState | null
) {
+ invariant(componentType instanceof ECMAScriptSourceFunctionValue);
let value;
let childContext = context;
@@ -207,6 +222,17 @@ export class Reconciler {
};
}
+ _getRenderStrategy(func: Value): RenderStrategy {
+ // check if it's a ReactRelay.QueryRenderer
+ if (this.realm.fbLibraries.reactRelay !== undefined) {
+ let QueryRenderer = Get(this.realm, this.realm.fbLibraries.reactRelay, "QueryRenderer");
+ if (func === QueryRenderer) {
+ return "RELAY_QUERY_RENDERER";
+ }
+ }
+ return "NORMAL";
+ }
+
_resolveDeeply(
value: Value,
context: ObjectValue | AbstractObjectValue,
@@ -275,7 +301,9 @@ export class Reconciler {
);
return reactElement;
}
- if (!(typeValue instanceof ECMAScriptSourceFunctionValue)) {
+ let renderStrategy = this._getRenderStrategy(typeValue);
+
+ if (renderStrategy === "NORMAL" && !(typeValue instanceof ECMAScriptSourceFunctionValue)) {
this._assignBailOutMessage(
reactElement,
`Bail-out: type on was not a ECMAScriptSourceFunctionValue`
@@ -283,13 +311,28 @@ export class Reconciler {
return reactElement;
}
try {
- let { result } = this._renderComponent(
- typeValue,
- propsValue,
- context,
- branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus,
- null
- );
+ let result;
+ switch (renderStrategy) {
+ case "NORMAL": {
+ let render = this._renderComponent(
+ typeValue,
+ propsValue,
+ context,
+ branchStatus === "NEW_BRANCH" ? "BRANCH" : branchStatus,
+ null
+ );
+ result = render.result;
+ break;
+ }
+ case "RELAY_QUERY_RENDERER": {
+ let render = this._renderRelayQueryRendererComponent(reactElement, propsValue, context);
+ result = render.result;
+ break;
+ }
+ default:
+ invariant(false, "unsupported render strategy");
+ }
+
if (result instanceof UndefinedValue) {
this._assignBailOutMessage(reactElement, `Bail-out: undefined was returned from render`);
if (branchStatus === "NEW_BRANCH" && branchState) {
diff --git a/src/react/utils.js b/src/react/utils.js
index 344066a6a..4a77712c6 100644
--- a/src/react/utils.js
+++ b/src/react/utils.js
@@ -97,7 +97,7 @@ export function valueIsClassComponent(realm: Realm, value: Value): boolean {
// logger isn't typed otherwise it will increase flow cycle length :()
export function valueIsReactLibraryObject(realm: Realm, value: ObjectValue, logger: any): boolean {
- if (realm.react.reactLibraryObject === value) {
+ if (realm.fbLibraries.react === value) {
return true;
}
// we check that the object is the React or React-like library by checking for
diff --git a/src/realm.js b/src/realm.js
index 20d60409e..7d6a4cfab 100644
--- a/src/realm.js
+++ b/src/realm.js
@@ -176,11 +176,18 @@ export class Realm {
output: opts.reactOutput || "create-element",
symbols: new Map(),
currentOwner: undefined,
- reactLibraryObject: undefined,
hoistableReactElements: new WeakMap(),
hoistableFunctions: new WeakMap(),
};
+ this.fbLibraries = {
+ react: undefined,
+ reactRelay: undefined,
+ cx: undefined,
+ fbt: undefined,
+ jsResource: undefined,
+ };
+
this.errorHandler = opts.errorHandler;
this.globalSymbolRegistry = [];
@@ -227,11 +234,18 @@ export class Realm {
output?: ReactOutputTypes,
symbols: Map,
currentOwner?: ObjectValue,
- reactLibraryObject?: ObjectValue,
hoistableReactElements: WeakMap,
hoistableFunctions: WeakMap,
};
+ fbLibraries: {
+ react: void | ObjectValue,
+ reactRelay: void | ObjectValue,
+ cx: void | ObjectValue,
+ fbt: void | ObjectValue,
+ jsResource: void | ObjectValue,
+ };
+
$GlobalObject: ObjectValue | AbstractObjectValue;
compatibility: Compatibility;
diff --git a/src/serializer/ResidualHeapVisitor.js b/src/serializer/ResidualHeapVisitor.js
index b4cf9e071..fbc0652fe 100644
--- a/src/serializer/ResidualHeapVisitor.js
+++ b/src/serializer/ResidualHeapVisitor.js
@@ -606,7 +606,7 @@ export class ResidualHeapVisitor {
this.logger.logError(val, `Arguments object is not supported in residual heap.`);
}
if (this.realm.react.enabled && valueIsReactLibraryObject(this.realm, val, this.logger)) {
- this.realm.react.reactLibraryObject = val;
+ this.realm.fbLibraries.react = val;
}
return;
}
@@ -920,7 +920,7 @@ export class ResidualHeapVisitor {
_visitReactLibrary() {
// find and visit the React library
- let reactLibraryObject = this.realm.react.reactLibraryObject;
+ let reactLibraryObject = this.realm.fbLibraries.react;
if (this.realm.react.output === "jsx") {
// React might not be defined in scope, i.e. another library is using JSX
// we don't throw an error as we should support JSX stand-alone
diff --git a/src/serializer/ResidualReactElements.js b/src/serializer/ResidualReactElements.js
index 55fd671f1..9e4490e23 100644
--- a/src/serializer/ResidualReactElements.js
+++ b/src/serializer/ResidualReactElements.js
@@ -125,7 +125,7 @@ export class ResidualReactElements {
}
}
}
- let reactLibraryObject = this.realm.react.reactLibraryObject;
+ let reactLibraryObject = this.realm.fbLibraries.react;
let shouldHoist =
this.residualHeapSerializer.currentFunctionBody !== this.residualHeapSerializer.mainBody &&
canHoistReactElement(this.realm, val);
diff --git a/test/react/mocks/fb1.js b/test/react/mocks/fb1.js
new file mode 100644
index 000000000..0b5a51f0e
--- /dev/null
+++ b/test/react/mocks/fb1.js
@@ -0,0 +1,32 @@
+var React = require('React');
+// the JSX transform converts to React, so we need to add it back in
+this['React'] = React;
+var {QueryRenderer, graphql} = require('RelayModern');
+
+var FBEnvironment = require('FBEnvironment');
+
+function App({ initialNumComments, someVariables, query, pageSize, onCommit }) {
+ return (
+ {
+ return Hello world
+ }}
+ />
+ );
+}
+
+App.getTrials = function(renderer, Root) {
+ renderer.update();
+ return [['fb1 mocks', renderer.toJSON()]];
+};
+
+if (this.__registerReactComponentRoot) {
+ __registerReactComponentRoot(App);
+}
+
+module.exports = App;
diff --git a/website/js/repl.js b/website/js/repl.js
index 751dd2c16..d24598744 100644
--- a/website/js/repl.js
+++ b/website/js/repl.js
@@ -40,7 +40,7 @@ var optionsConfig = [
{
type: "choice",
name: "compatibility",
- choices: ["browser", "jsc-600-1-4-17", "node-source-maps", "node-cli", "react-mocks"],
+ choices: ["browser", "jsc-600-1-4-17", "node-source-maps", "node-cli", "fb-wwww"],
defaultVal: "browser",
description: "The target environment for Prepack"
},