mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-11 14:46:37 +03:00
React/JSX code cleanup in preparation for React component folding
Summary: Release note: none This is one part of the component folding PRs (the original has been split up). This PR makes small changes to existing code and adds in: - a new Prepack global hook `__registerReactComponentRoot` that works in a similar way to additional functions - some tidy up and moving around of JSX/React code - added `checkReactRootComponents` stub function in the functions serializer code that will be used in upcoming PRs Closes https://github.com/facebook/prepack/pull/1117 Differential Revision: D6198875 Pulled By: trueadm fbshipit-source-id: ea183e40b8e6519a88dc574a08804e9f706c5390
This commit is contained in:
parent
4ca9ac0c4a
commit
dc27e144e9
@ -24,7 +24,7 @@ import type {
|
||||
BabelNodeJSXExpressionContainer,
|
||||
} from "babel-types";
|
||||
import { ArrayValue, StringValue, Value, NumberValue, ObjectValue, SymbolValue } from "../values/index.js";
|
||||
import { convertJSXExpressionToIdentifier } from "../utils/jsx";
|
||||
import { convertJSXExpressionToIdentifier } from "../react/jsx";
|
||||
import * as t from "babel-types";
|
||||
import {
|
||||
Get,
|
||||
|
@ -126,7 +126,7 @@ export default function(realm: Realm): void {
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
let uid = 0;
|
||||
let additonalFunctionUid = 0;
|
||||
// Allows dynamically registering additional functions.
|
||||
// WARNING: these functions will get exposed at global scope and called there.
|
||||
// NB: If we interpret one of these calls in an evaluateForEffects context
|
||||
@ -143,7 +143,7 @@ export default function(realm: Realm): void {
|
||||
realm.assignToGlobal(
|
||||
t.memberExpression(
|
||||
t.memberExpression(t.identifier("global"), t.identifier("__additionalFunctions")),
|
||||
t.identifier("" + uid++)
|
||||
t.identifier("" + additonalFunctionUid++)
|
||||
),
|
||||
functionValue
|
||||
);
|
||||
@ -155,6 +155,39 @@ export default function(realm: Realm): void {
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
if (realm.react.enabled) {
|
||||
global.$DefineOwnProperty("__reactComponentRoots", {
|
||||
value: new ObjectValue(realm, realm.intrinsics.ObjectPrototype, "__reactComponentRoots", true),
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
let reactComponentRootUid = 0;
|
||||
// this is almost a copy of the additionalFunctions code above
|
||||
global.$DefineOwnProperty("__registerReactComponentRoot", {
|
||||
value: new NativeFunctionValue(
|
||||
realm,
|
||||
"global.__registerReactComponentRoot",
|
||||
"__registerReactComponentRoot",
|
||||
0,
|
||||
(context, [functionValue]) => {
|
||||
invariant(functionValue instanceof ECMAScriptSourceFunctionValue);
|
||||
realm.assignToGlobal(
|
||||
t.memberExpression(
|
||||
t.memberExpression(t.identifier("global"), t.identifier("__reactComponentRoots")),
|
||||
t.identifier("" + reactComponentRootUid++)
|
||||
),
|
||||
functionValue
|
||||
);
|
||||
return realm.intrinsics.undefined;
|
||||
}
|
||||
),
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Maps from initialized moduleId to exports object
|
||||
// NB: Changes to this shouldn't ever be serialized
|
||||
global.$DefineOwnProperty("__initializedModules", {
|
||||
|
@ -20,20 +20,7 @@ import type {
|
||||
BabelNodeMemberExpression,
|
||||
} from "babel-types";
|
||||
import invariant from "../invariant.js";
|
||||
import { Value, ObjectValue, SymbolValue } from "../values/index.js";
|
||||
import { Get } from "../methods/index.js";
|
||||
|
||||
export function isReactElement(val: Value): boolean {
|
||||
if (val instanceof ObjectValue && val.properties.has("$$typeof")) {
|
||||
let realm = val.$Realm;
|
||||
let $$typeof = Get(realm, val, "$$typeof");
|
||||
if ($$typeof instanceof SymbolValue) {
|
||||
let symbolFromRegistry = realm.globalSymbolRegistry.find(e => e.$Symbol === $$typeof);
|
||||
return symbolFromRegistry !== undefined && symbolFromRegistry.$Key === "react.element";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
import { isReactComponent, getUniqueReactElementKey } from "./utils";
|
||||
|
||||
export function convertExpressionToJSXIdentifier(
|
||||
expr: BabelNodeExpression,
|
||||
@ -48,7 +35,7 @@ export function convertExpressionToJSXIdentifier(
|
||||
invariant(
|
||||
// ensure the 1st character of the string is uppercase
|
||||
// for a component unless it is not the root
|
||||
isRoot === false || (name.length > 0 && name[0] === name[0].toUpperCase()),
|
||||
isRoot === false || isReactComponent(name),
|
||||
"invalid JSXIdentifer from Identifier, Identifier name must be uppercase"
|
||||
);
|
||||
return t.jSXIdentifier(name);
|
||||
@ -109,21 +96,6 @@ function addKeyToElement(astElement: BabelNodeJSXElement, key) {
|
||||
}
|
||||
}
|
||||
|
||||
// we create a unique key for each JSXElement to prevent collisions
|
||||
// otherwise React will detect a missing/conflicting key at runtime and
|
||||
// this can break the reconcilation of JSXElements in arrays
|
||||
function getUniqueJSXElementKey(index?: string, usedReactElementKeys: Set<string>) {
|
||||
let key;
|
||||
do {
|
||||
key = Math.random().toString(36).replace(/[^a-z]+/g, "").substring(0, 2);
|
||||
} while (usedReactElementKeys.has(key));
|
||||
usedReactElementKeys.add(key);
|
||||
if (index !== undefined) {
|
||||
return `${key}${index}`;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
export function applyKeysToNestedArray(
|
||||
expr: BabelNodeArrayExpression,
|
||||
isBase: boolean,
|
||||
@ -137,20 +109,20 @@ export function applyKeysToNestedArray(
|
||||
|
||||
if (astElement != null) {
|
||||
if (t.isJSXElement(astElement) && isBase === false) {
|
||||
addKeyToElement((astElement: any), getUniqueJSXElementKey("" + i, usedReactElementKeys));
|
||||
addKeyToElement((astElement: any), getUniqueReactElementKey("" + i, usedReactElementKeys));
|
||||
} else if (t.isArrayExpression(astElement)) {
|
||||
applyKeysToNestedArray((astElement: any), false, usedReactElementKeys);
|
||||
} else if (astElement.type === "ConditionalExpression") {
|
||||
let alternate = (astElement.alternate: any);
|
||||
// it's common for conditions to be in an array, which means we need to check them for keys too
|
||||
if (t.isJSXElement(alternate.type) && isBase === false) {
|
||||
addKeyToElement(alternate, getUniqueJSXElementKey("0" + i, usedReactElementKeys));
|
||||
addKeyToElement(alternate, getUniqueReactElementKey("0" + i, usedReactElementKeys));
|
||||
} else if (t.isArrayExpression(alternate.type)) {
|
||||
applyKeysToNestedArray(alternate, false, usedReactElementKeys);
|
||||
}
|
||||
let consequent = (astElement.consequent: any);
|
||||
if (t.isJSXElement(consequent.type) && isBase === false) {
|
||||
addKeyToElement(consequent, getUniqueJSXElementKey("1" + i, usedReactElementKeys));
|
||||
addKeyToElement(consequent, getUniqueReactElementKey("1" + i, usedReactElementKeys));
|
||||
} else if (t.isArrayExpression(consequent.type)) {
|
||||
applyKeysToNestedArray(consequent, false, usedReactElementKeys);
|
||||
}
|
49
src/react/utils.js
Normal file
49
src/react/utils.js
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 { BabelNode, BabelNodeJSXIdentifier } from "babel-types";
|
||||
import { Value, ObjectValue, SymbolValue } from "../values/index.js";
|
||||
import { Get } from "../methods/index.js";
|
||||
|
||||
export function isReactElement(val: Value): boolean {
|
||||
if (val instanceof ObjectValue && val.properties.has("$$typeof")) {
|
||||
let realm = val.$Realm;
|
||||
let $$typeof = Get(realm, val, "$$typeof");
|
||||
if ($$typeof instanceof SymbolValue) {
|
||||
let symbolFromRegistry = realm.globalSymbolRegistry.find(e => e.$Symbol === $$typeof);
|
||||
return symbolFromRegistry !== undefined && symbolFromRegistry.$Key === "react.element";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isTagName(ast: BabelNode): boolean {
|
||||
return ast.type === "JSXIdentifier" && /^[a-z]|\-/.test(((ast: any): BabelNodeJSXIdentifier).name);
|
||||
}
|
||||
|
||||
export function isReactComponent(name: string) {
|
||||
return name.length > 0 && name[0] === name[0].toUpperCase();
|
||||
}
|
||||
|
||||
// we create a unique key for each JSXElement to prevent collisions
|
||||
// otherwise React will detect a missing/conflicting key at runtime and
|
||||
// this can break the reconcilation of JSXElements in arrays
|
||||
export function getUniqueReactElementKey(index?: string, usedReactElementKeys: Set<string>) {
|
||||
let key;
|
||||
do {
|
||||
key = Math.random().toString(36).replace(/[^a-z]+/g, "").substring(0, 2);
|
||||
} while (usedReactElementKeys.has(key));
|
||||
usedReactElementKeys.add(key);
|
||||
if (index !== undefined) {
|
||||
return `${key}${index}`;
|
||||
}
|
||||
return key;
|
||||
}
|
@ -33,8 +33,8 @@ import {
|
||||
convertExpressionToJSXIdentifier,
|
||||
convertKeyValueToJSXAttribute,
|
||||
applyKeysToNestedArray,
|
||||
isReactElement,
|
||||
} from "../utils/jsx";
|
||||
} from "../react/jsx.js";
|
||||
import { isReactElement } from "../react/utils.js";
|
||||
import * as t from "babel-types";
|
||||
import type {
|
||||
BabelNodeArrayExpression,
|
||||
@ -735,33 +735,17 @@ export class ResidualHeapSerializer {
|
||||
return t.arrayExpression(initProperties);
|
||||
}
|
||||
|
||||
_getPropertyValue(properties: Map<string, any>, key: string) {
|
||||
if (properties.has(key)) {
|
||||
let val = properties.get(key);
|
||||
|
||||
if (val !== undefined) {
|
||||
let descriptor = val.descriptor;
|
||||
invariant(!IsAccessorDescriptor(this.realm, descriptor), "expected descriptor to be a non-accessor property");
|
||||
|
||||
if (descriptor !== undefined) {
|
||||
let descriptorValue = descriptor.value;
|
||||
|
||||
if (descriptorValue !== undefined) {
|
||||
return descriptorValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_serializeValueReactElementChild(child: Value): BabelNode {
|
||||
if (isReactElement(child)) {
|
||||
// if we know it's a ReactElement, we add the value to the serializedValues
|
||||
// and short cut to get back the JSX expression so we don't emit additional data
|
||||
// we do this to ensure child JSXElements can get keys assigned if needed
|
||||
this.serializedValues.add(child);
|
||||
return this._serializeValueObject(((child: any): ObjectValue));
|
||||
let reactChild = this._serializeValueObject(((child: any): ObjectValue));
|
||||
if (reactChild.leadingComments !== null) {
|
||||
return t.jSXExpressionContainer(reactChild);
|
||||
}
|
||||
return reactChild;
|
||||
}
|
||||
const expr = this.serializeValue(child);
|
||||
|
||||
@ -776,11 +760,10 @@ export class ResidualHeapSerializer {
|
||||
}
|
||||
|
||||
_serializeValueReactElement(val: ObjectValue): BabelNodeExpression {
|
||||
let objectProperties: Map<string, any> = val.properties;
|
||||
let typeValue = this._getPropertyValue(objectProperties, "type");
|
||||
let keyValue = this._getPropertyValue(objectProperties, "key");
|
||||
let refValue = this._getPropertyValue(objectProperties, "ref");
|
||||
let propsValue = this._getPropertyValue(objectProperties, "props");
|
||||
let typeValue = Get(this.realm, val, "type");
|
||||
let keyValue = Get(this.realm, val, "key");
|
||||
let refValue = Get(this.realm, val, "ref");
|
||||
let propsValue = Get(this.realm, val, "props");
|
||||
|
||||
invariant(typeValue !== null, "JSXElement type of null");
|
||||
|
||||
@ -818,12 +801,12 @@ export class ResidualHeapSerializer {
|
||||
let childrenValue = desc.value;
|
||||
if (childrenValue instanceof ArrayValue) {
|
||||
this.serializedValues.add(childrenValue);
|
||||
let childrenLength = this._getPropertyValue(childrenValue.properties, "length");
|
||||
let childrenLength = Get(this.realm, childrenValue, "length");
|
||||
let childrenLengthValue = 0;
|
||||
if (childrenLength instanceof NumberValue) {
|
||||
childrenLengthValue = childrenLength.value;
|
||||
for (let i = 0; i < childrenLengthValue; i++) {
|
||||
let child = this._getPropertyValue(childrenValue.properties, "" + i);
|
||||
let child = Get(this.realm, childrenValue, "" + i);
|
||||
if (child instanceof Value) {
|
||||
children.push(this._serializeValueReactElementChild(child));
|
||||
} else {
|
||||
|
@ -16,10 +16,17 @@ import invariant from "../invariant.js";
|
||||
import { type Effects, type PropertyBindings, Realm } from "../realm.js";
|
||||
import type { PropertyBinding } from "../types.js";
|
||||
import { ignoreErrorsIn } from "../utils/errors.js";
|
||||
import { AbstractObjectValue, FunctionValue, ObjectValue, AbstractValue } from "../values/index.js";
|
||||
import {
|
||||
AbstractObjectValue,
|
||||
FunctionValue,
|
||||
ObjectValue,
|
||||
AbstractValue,
|
||||
ECMAScriptSourceFunctionValue,
|
||||
} from "../values/index.js";
|
||||
import { Get } from "../methods/index.js";
|
||||
import { ModuleTracer } from "./modules.js";
|
||||
import buildTemplate from "babel-template";
|
||||
import { type ReactSerializerState } from "./types";
|
||||
import * as t from "babel-types";
|
||||
|
||||
export class Functions {
|
||||
@ -69,11 +76,13 @@ export class Functions {
|
||||
return calls;
|
||||
}
|
||||
|
||||
_generateAdditionalFunctionCallsFromDirective(): Array<[FunctionValue, BabelNodeCallExpression]> {
|
||||
// __reactComponentRoots
|
||||
|
||||
__generateAdditionalFunctions(globalKey: string) {
|
||||
let recordedAdditionalFunctions: Map<FunctionValue, string> = new Map();
|
||||
let realm = this.realm;
|
||||
let globalRecordedAdditionalFunctionsMap = this.moduleTracer.modules.logger.tryQuery(
|
||||
() => Get(realm, realm.$GlobalObject, "__additionalFunctions"),
|
||||
() => Get(realm, realm.$GlobalObject, globalKey),
|
||||
realm.intrinsics.undefined,
|
||||
false
|
||||
);
|
||||
@ -97,6 +106,24 @@ export class Functions {
|
||||
recordedAdditionalFunctions.set(funcValue, funcId);
|
||||
}
|
||||
}
|
||||
return recordedAdditionalFunctions;
|
||||
}
|
||||
|
||||
checkReactRootComponents(react: ReactSerializerState): void {
|
||||
let recordedReactRootComponents = this.__generateAdditionalFunctions("__reactComponentRoots");
|
||||
|
||||
// Get write effects of the components
|
||||
for (let [funcValue] of recordedReactRootComponents) {
|
||||
invariant(
|
||||
funcValue instanceof ECMAScriptSourceFunctionValue,
|
||||
"only ECMAScriptSourceFunctionValue function values are supported as React root components"
|
||||
);
|
||||
throw new FatalError("TODO: implement functional component folding");
|
||||
}
|
||||
}
|
||||
|
||||
_generateAdditionalFunctionCallsFromDirective(): Array<[FunctionValue, BabelNodeCallExpression]> {
|
||||
let recordedAdditionalFunctions = this.__generateAdditionalFunctions("__additionalFunctions");
|
||||
|
||||
// The additional functions we registered at runtime are recorded at:
|
||||
// global.__additionalFunctions.id
|
||||
@ -127,6 +154,9 @@ export class Functions {
|
||||
// This may throw a FatalError if there is an unrecoverable error in the called function
|
||||
// When that happens we cannot prepack the bundle.
|
||||
// There may also be warnings reported for errors that happen inside imported modules that can be postponed.
|
||||
|
||||
// TODO check if the function is a React component
|
||||
// for now we do this on all functions
|
||||
let e = this.realm.evaluateNodeForEffectsInGlobalEnv(call, this.moduleTracer);
|
||||
this.writeEffects.set(funcValue, e);
|
||||
}
|
||||
|
@ -113,6 +113,9 @@ export class Serializer {
|
||||
if (this.logger.hasErrors()) return undefined;
|
||||
this.modules.resolveInitializedModules();
|
||||
this.functions.checkThatFunctionsAreIndependent();
|
||||
if (this.realm.react.enabled) {
|
||||
this.functions.checkReactRootComponents(this.react);
|
||||
}
|
||||
|
||||
if (this.options.initializeMoreModules) {
|
||||
if (timingStats !== undefined) timingStats.initializeMoreModulesTime = Date.now();
|
||||
|
@ -12,7 +12,7 @@
|
||||
import { Realm } from "../realm.js";
|
||||
import { FunctionValue } from "../values/index.js";
|
||||
import * as t from "babel-types";
|
||||
import { convertExpressionToJSXIdentifier } from "../utils/jsx";
|
||||
import { convertExpressionToJSXIdentifier } from "../react/jsx";
|
||||
import type { BabelNodeExpression, BabelNodeCallExpression, BabelNodeFunctionExpression } from "babel-types";
|
||||
import type { BabelTraversePath } from "babel-traverse";
|
||||
import type { FunctionBodyAstNode } from "../types.js";
|
||||
|
@ -34,7 +34,7 @@ import {
|
||||
UndefinedValue,
|
||||
Value,
|
||||
} from "./index.js";
|
||||
import { isReactElement } from "../utils/jsx";
|
||||
import { isReactElement } from "../react/utils.js";
|
||||
import type { ECMAScriptSourceFunctionValue, NativeFunctionCallback } from "./index.js";
|
||||
import {
|
||||
joinValuesAsConditional,
|
||||
|
Loading…
Reference in New Issue
Block a user