Basic JSX/ReactElement support

Summary:
Release notes: adds JSX and ReactElement evaluation/serialization support

This adds JSX/ReactElement support to the Prepack serializer so `ReactElement` "like" objects can correctly be converted back to JSX syntax. Example:

```jsx
'use strict';

const externalThing = __abstract('function', 'externalThing');

function Test() {
  return <div>{ externalThing() }</div>
}

global.test = Test;
```

Becomes:
```jsx
'use strict';

var _$0 = this;

var _0 = function () {
  return <div>{_1()}</div>;
};

var _1 = externalThing;
_$0.test = _0;
```

I've also added a JSXElement -> ReactElement evaluator.
Closes https://github.com/facebook/prepack/pull/1051

Differential Revision: D6053275

Pulled By: trueadm

fbshipit-source-id: 7c31545751ceedba55cdd4581bc6de51a98b412a
This commit is contained in:
Dominic Gannaway 2017-10-13 12:01:26 -07:00 committed by Facebook Github Bot
parent b0e977136c
commit cfc6993e9c
25 changed files with 1178 additions and 34 deletions

View File

@ -52,8 +52,9 @@
"prettier-all": "node ./scripts/prettier.js write"
},
"dependencies": {
"babel-core": "^6.8.0",
"babel-core": "^6.26.0",
"babel-generator": "^6.8.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-template": "^6.9.0",
"babel-traverse": "^6.9.0",
"babel-types": "^6.9.0",

View File

@ -41,7 +41,7 @@ exec("flow check --profile", function(error, stdout, stderr) {
process.exit(1);
}
console.log("Biggest cycle: " + cycle_len);
let MAX_CYCLE_LEN = 58;
let MAX_CYCLE_LEN = 59;
if (cycle_len > MAX_CYCLE_LEN) {
console.log("Error: You increased cycle length from the previous high of " + MAX_CYCLE_LEN);
process.exit(1);

View File

@ -22,8 +22,15 @@ let fs = require("fs");
let vm = require("vm");
let os = require("os");
let minimist = require("minimist");
let babel = require("babel-core");
const EOL = os.EOL;
function transformWithBabel(code, plugins) {
return babel.transform(code, {
plugins,
}).code;
}
function search(dir, relative) {
let tests = [];
@ -87,6 +94,7 @@ function runTest(name, code, options, args) {
console.log(chalk.inverse(name) + " " + util.inspect(options));
let compatibility = code.includes("// jsc") ? "jsc-600-1-4-17" : undefined;
let initializeMoreModules = code.includes("// initialize more modules");
let compileJSXWithBabel = code.includes("// babel:jsx");
let delayUnsupportedRequires = code.includes("// delay unsupported requires");
let functionCloneCountMatch = code.match(/\/\/ serialized function clone count: (\d+)/);
options = Object.assign({}, options, {
@ -104,6 +112,7 @@ function runTest(name, code, options, args) {
if (code.includes("// omit invariants")) options.omitInvariants = true;
if (code.includes("// additional functions")) options.additionalFunctions = ["additional1", "additional2"];
if (code.includes("// exceeds stack limit")) options.maxStackDepth = 10;
if (code.includes("// react")) options.reactEnabled = true;
if (code.includes("// throws introspection error")) {
try {
let realmOptions = {
@ -208,9 +217,13 @@ function runTest(name, code, options, args) {
}
let unique = 27277;
let oldUniqueSuffix = "";
let expectedCode = code;
if (compileJSXWithBabel) {
expectedCode = transformWithBabel(expectedCode, ["transform-react-jsx"]);
}
try {
try {
expected = exec(`${addedCode}\n(function () {${code} // keep newline here as code may end with comment
expected = exec(`${addedCode}\n(function () {${expectedCode} // keep newline here as code may end with comment
}).call(this);`);
} catch (e) {
expected = e;
@ -230,6 +243,9 @@ function runTest(name, code, options, args) {
break;
}
let newCode = serialized.code;
if (compileJSXWithBabel) {
newCode = transformWithBabel(newCode, ["transform-react-jsx"]);
}
codeIterations.push(newCode);
if (args.verbose) console.log(newCode);
let markersIssue = false;

View File

@ -0,0 +1,377 @@
/**
* 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 type { LexicalEnvironment } from "../environment.js";
import { FatalError } from "../errors.js";
import type {
BabelNode,
BabelNodeStringLiteral,
BabelNodeJSXText,
BabelNodeJSXElement,
BabelNodeJSXIdentifier,
BabelNodeJSXMemberExpression,
BabelNodeJSXAttribute,
BabelNodeJSXSpreadAttribute,
BabelNodeJSXExpressionContainer,
} from "babel-types";
import { ArrayValue, StringValue, Value, NumberValue, ObjectValue, SymbolValue } from "../values/index.js";
import { convertJSXExpressionToIdentifier } from "../utils/jsx";
import * as t from "babel-types";
import {
Get,
GetValue,
ResolveBinding,
ArrayCreate,
CreateDataPropertyOrThrow,
ObjectCreate,
Set,
} from "../methods/index.js";
import invariant from "../invariant.js";
import { computeBinary } from "./BinaryExpression.js";
let RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
let reactElementSymbolKey = "react.element";
// taken from Babel
function cleanJSXElementLiteralChild(child: string): null | string {
let lines = child.split(/\r\n|\n|\r/);
let lastNonEmptyLine = 0;
for (let i = 0; i < lines.length; i++) {
if (lines[i].match(/[^ \t]/)) {
lastNonEmptyLine = i;
}
}
let str = "";
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
let isFirstLine = i === 0;
let isLastLine = i === lines.length - 1;
let isLastNonEmptyLine = i === lastNonEmptyLine;
// replace rendered whitespace tabs with spaces
let trimmedLine = line.replace(/\t/g, " ");
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, "");
}
// trim whitespace touching an endline
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, "");
}
if (trimmedLine) {
if (!isLastNonEmptyLine) {
trimmedLine += " ";
}
str += trimmedLine;
}
}
if (str) {
return str;
}
return null;
}
function evaluateJSXMemberExpression(
ast: BabelNode,
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): Value {
switch (ast.type) {
case "JSXIdentifier":
return GetValue(realm, ResolveBinding(realm, ((ast: any): BabelNodeJSXIdentifier).name, strictCode, env));
case "JSXMemberExpression":
return GetValue(
realm,
env.evaluate(convertJSXExpressionToIdentifier(((ast: any): BabelNodeJSXMemberExpression)), strictCode)
);
default:
invariant(false, "Unknown JSX Identifier");
}
}
function evaluateJSXIdentifier(ast, strictCode, env, realm): Value {
if (isTagName(ast)) {
// special cased lower-case and custom elements
return new StringValue(realm, ((ast: any): BabelNodeJSXIdentifier).name);
}
return evaluateJSXMemberExpression(ast, strictCode, env, realm);
}
function evaluateJSXValue(value: BabelNode, strictCode: boolean, env: LexicalEnvironment, realm: Realm): Value {
if (value != null) {
switch (value.type) {
case "JSXText":
return new StringValue(realm, ((value: any): BabelNodeJSXText).value);
case "StringLiteral":
return new StringValue(realm, ((value: any): BabelNodeStringLiteral).value);
case "JSXExpressionContainer":
return GetValue(realm, env.evaluate(((value: any): BabelNodeJSXExpressionContainer).expression, strictCode));
case "JSXElement":
return GetValue(realm, env.evaluate(value, strictCode));
default:
invariant(false, `Unknown JSX value type: ${value.type}`);
}
}
invariant(false, `Null or undefined value passed when trying to evaluate JSX node value`);
}
function isTagName(ast: BabelNode): boolean {
return ast.type === "JSXIdentifier" && /^[a-z]|\-/.test(((ast: any): BabelNodeJSXIdentifier).name);
}
function getDefaultProps(
elementType: BabelNodeJSXIdentifier | BabelNodeJSXMemberExpression,
env,
realm: Realm
): null | ObjectValue {
let name;
if (elementType.type === "JSXIdentifier") {
name = elementType.name;
}
if (!isTagName(elementType) && typeof name === "string") {
// find the value of "ComponentXXX.defaultProps"
let defaultProps = GetValue(
realm,
env.evaluate(t.memberExpression(t.identifier(name), t.identifier("defaultProps")), false)
);
if (defaultProps instanceof ObjectValue) {
return defaultProps;
}
}
return null;
}
function evaluateJSXChildren(
children: Array<BabelNode>,
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): ArrayValue | Value | null {
if (children.length === 0) {
return null;
}
if (children.length === 1) {
let singleChild = evaluateJSXValue(children[0], strictCode, env, realm);
if (singleChild instanceof StringValue) {
let text = cleanJSXElementLiteralChild(singleChild.value);
if (text !== null) {
singleChild.value = text;
}
}
return singleChild;
}
let array = ArrayCreate(realm, 0);
let dynamicChildrenLength = children.length;
let dynamicIterator = 0;
let lastChildValue = null;
for (let i = 0; i < children.length; i++) {
let value = evaluateJSXValue(children[i], strictCode, env, realm);
if (value instanceof StringValue) {
let text = cleanJSXElementLiteralChild(value.value);
if (text === null) {
dynamicChildrenLength--;
// this is a space full of whitespace, so let's proceed
continue;
} else {
value.value = text;
}
}
lastChildValue = value;
CreateDataPropertyOrThrow(realm, array, "" + dynamicIterator, value);
dynamicIterator++;
}
if (dynamicChildrenLength === 1) {
return lastChildValue;
}
Set(realm, array, "length", new NumberValue(realm, dynamicChildrenLength), false);
return array;
}
function evaluateJSXAttributes(
elementType: BabelNodeJSXIdentifier | BabelNodeJSXMemberExpression,
astAttributes: Array<BabelNodeJSXAttribute | BabelNodeJSXSpreadAttribute>,
astChildren: Array<BabelNode>,
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): { attributes: Map<string, Value>, children: ArrayValue | Value | null } {
let attributes = new Map();
let children = evaluateJSXChildren(astChildren, strictCode, env, realm);
let defaultProps = getDefaultProps(elementType, env, realm);
// defaultProps are a bit like default function arguments
// if an actual value exists, it should overwrite the default value
if (defaultProps !== null) {
for (let [key] of defaultProps.properties) {
let defaultPropValue = Get(realm, defaultProps, key);
if (defaultPropValue instanceof Value) {
if (key === "children") {
if (children === null) {
children = defaultPropValue;
}
} else {
attributes.set(key, defaultPropValue);
}
}
}
}
for (let astAttribute of astAttributes) {
switch (astAttribute.type) {
case "JSXAttribute":
let { name, value } = astAttribute;
invariant(name.type === "JSXIdentifier", `JSX attribute name type not supported: ${astAttribute.type}`);
attributes.set(name.name, evaluateJSXValue(((value: any): BabelNodeJSXIdentifier), strictCode, env, realm));
break;
case "JSXSpreadAttribute":
let spreadValue = GetValue(realm, env.evaluate(astAttribute.argument, strictCode));
if (spreadValue instanceof ObjectValue) {
for (let [key, spreadProp] of spreadValue.properties) {
if (spreadProp !== undefined && spreadProp.descriptor !== undefined) {
let spreadPropValue = spreadProp.descriptor.value;
if (spreadPropValue instanceof Value) {
if (key === "children") {
children = spreadPropValue;
} else {
attributes.set(key, spreadPropValue);
}
}
}
}
} else {
throw new FatalError("ObjectValues are the only supported value for JSX Spread Attributes");
}
break;
default:
invariant(false, `Unknown JSX attribute type:: ${astAttribute.type}`);
}
}
return {
attributes,
children,
};
}
function getReactElementSymbol(realm: Realm): SymbolValue {
let reactElementSymbol = realm.react.reactElementSymbol;
if (reactElementSymbol !== undefined) {
return reactElementSymbol;
}
let SymbolFor = realm.intrinsics.Symbol.properties.get("for");
if (SymbolFor !== undefined) {
let SymbolForDescriptor = SymbolFor.descriptor;
if (SymbolForDescriptor !== undefined) {
let SymbolForValue = SymbolForDescriptor.value;
if (SymbolForValue !== undefined && typeof SymbolForValue.$Call === "function") {
realm.react.reactElementSymbol = reactElementSymbol = SymbolForValue.$Call(realm.intrinsics.Symbol, [
new StringValue(realm, reactElementSymbolKey),
]);
}
}
}
invariant(reactElementSymbol instanceof SymbolValue, `ReactElement "$$typeof" property was not a symbol`);
return reactElementSymbol;
}
function createReactProps(
realm: Realm,
type: Value,
attributes: Map<string, Value>,
children,
env: LexicalEnvironment
): ObjectValue {
let obj = ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
for (let [key, value] of attributes) {
if (typeof key === "string") {
if (RESERVED_PROPS.hasOwnProperty(key)) {
continue;
}
CreateDataPropertyOrThrow(realm, obj, key, value);
}
}
if (children !== null) {
CreateDataPropertyOrThrow(realm, obj, "children", children);
}
return obj;
}
function createReactElement(realm: Realm, type: Value, key: Value, ref: Value, props: ObjectValue): ObjectValue {
let obj = ObjectCreate(realm, realm.intrinsics.ObjectPrototype);
CreateDataPropertyOrThrow(realm, obj, "$$typeof", getReactElementSymbol(realm));
CreateDataPropertyOrThrow(realm, obj, "type", type);
CreateDataPropertyOrThrow(realm, obj, "key", key);
CreateDataPropertyOrThrow(realm, obj, "ref", ref);
CreateDataPropertyOrThrow(realm, obj, "props", props);
CreateDataPropertyOrThrow(realm, obj, "_owner", realm.intrinsics.null);
return obj;
}
export default function(
ast: BabelNodeJSXElement,
strictCode: boolean,
env: LexicalEnvironment,
realm: Realm
): ObjectValue {
invariant(realm.react.enabled, "JSXElements can only be evaluated with the reactEnabled option");
let openingElement = ast.openingElement;
let type = evaluateJSXIdentifier(openingElement.name, strictCode, env, realm);
let { attributes, children } = evaluateJSXAttributes(
openingElement.name,
openingElement.attributes,
ast.children,
strictCode,
env,
realm
);
let key = attributes.get("key") || realm.intrinsics.null;
let ref = attributes.get("ref") || realm.intrinsics.null;
if (key === realm.intrinsics.undefined) {
key = realm.intrinsics.null;
}
if (ref === realm.intrinsics.undefined) {
ref = realm.intrinsics.null;
}
// React uses keys to identify nodes as they get updated through the reconcilation
// phase. Keys are used in a map and thus need to be converted to strings
if (key !== realm.intrinsics.null) {
key = computeBinary(realm, "+", realm.intrinsics.emptyString, key);
}
let props = createReactProps(realm, type, attributes, children, env);
return createReactElement(realm, type, key, ref, props);
}

View File

@ -62,3 +62,4 @@ export { default as VariableDeclaration } from "./VariableDeclaration.js";
export { default as WhileStatement } from "./WhileStatement.js";
export { default as WithStatement } from "./WithStatement.js";
export { default as YieldExpression } from "./YieldExpression.js";
export { default as JSXElement } from "./JSXElement.js";

View File

@ -26,6 +26,7 @@ export type RealmOptions = {
strictlyMonotonicDateNow?: boolean,
timeout?: number,
maxStackDepth?: number,
reactEnabled?: boolean,
};
export type SerializerOptions = {

View File

@ -84,6 +84,7 @@ function run(
serialize: false,
residual: false,
profile: false,
reactEnabled: false,
};
while (args.length) {

View File

@ -27,6 +27,7 @@ export type PrepackOptions = {|
omitInvariants?: boolean,
outputFilename?: string,
profile?: boolean,
reactEnabled?: boolean,
residual?: boolean,
serialize?: boolean,
inlineExpressions?: boolean,
@ -47,6 +48,7 @@ export function getRealmOptions({
mathRandomSeed,
omitInvariants = false,
uniqueSuffix,
reactEnabled,
residual,
serialize = !residual,
strictlyMonotonicDateNow,
@ -60,6 +62,7 @@ export function getRealmOptions({
mathRandomSeed,
omitInvariants,
uniqueSuffix,
reactEnabled,
residual,
serialize,
strictlyMonotonicDateNow,

View File

@ -174,6 +174,11 @@ export class Realm {
this.partialEvaluators = (Object.create(null): any);
this.$GlobalEnv = ((undefined: any): LexicalEnvironment);
this.react = {
enabled: opts.reactEnabled || false,
reactElementSymbol: undefined,
};
this.errorHandler = opts.errorHandler;
this.globalSymbolRegistry = [];
@ -205,6 +210,11 @@ export class Realm {
$GlobalEnv: LexicalEnvironment;
intrinsics: Intrinsics;
react: {
enabled: boolean,
reactElementSymbol?: SymbolValue,
};
$GlobalObject: ObjectValue | AbstractObjectValue;
compatibility: Compatibility;

View File

@ -45,7 +45,9 @@ function serialize(realm: Realm, res: Value | AbruptCompletion): any {
return res;
}
let realm = construct_realm();
let realm = construct_realm({
reactEnabled: true,
});
initializeGlobals(realm);
repl.start({

View File

@ -11,8 +11,9 @@
import { Realm } from "../realm.js";
import type { Descriptor, PropertyBinding } from "../types.js";
import { ToLength, IsArray, Get } from "../methods/index.js";
import { ToLength, IsArray, Get, IsAccessorDescriptor } from "../methods/index.js";
import {
ArrayValue,
BoundFunctionValue,
ProxyValue,
SymbolValue,
@ -28,8 +29,15 @@ import {
NativeFunctionValue,
UndefinedValue,
} from "../values/index.js";
import {
convertExpressionToJSXIdentifier,
convertKeyValueToJSXAttribute,
applyKeysToNestedArray,
isReactElement,
} from "../utils/jsx";
import * as t from "babel-types";
import type {
BabelNodeArrayExpression,
BabelNodeExpression,
BabelNodeStatement,
BabelNodeIdentifier,
@ -42,7 +50,13 @@ import type {
import { Generator, PreludeGenerator, NameGenerator } from "../utils/generator.js";
import type { SerializationContext } from "../utils/generator.js";
import invariant from "../invariant.js";
import type { ResidualFunctionBinding, FunctionInfo, FunctionInstance, AdditionalFunctionInfo } from "./types.js";
import type {
ResidualFunctionBinding,
FunctionInfo,
FunctionInstance,
AdditionalFunctionInfo,
ReactSerializerState,
} from "./types.js";
import { TimingStatistics, SerializerStatistics } from "./types.js";
import { Logger } from "./logger.js";
import { Modules } from "./modules.js";
@ -70,13 +84,15 @@ export class ResidualHeapSerializer {
referencedDeclaredValues: Set<AbstractValue>,
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void,
additionalFunctionValueInfos: Map<FunctionValue, AdditionalFunctionInfo>,
statistics: SerializerStatistics
statistics: SerializerStatistics,
react: ReactSerializerState
) {
this.realm = realm;
this.logger = logger;
this.modules = modules;
this.residualHeapValueIdentifiers = residualHeapValueIdentifiers;
this.statistics = statistics;
this.react = react;
let realmGenerator = this.realm.generator;
invariant(realmGenerator);
@ -166,6 +182,7 @@ export class ResidualHeapSerializer {
referencedDeclaredValues: Set<AbstractValue>;
activeGeneratorBodies: Map<Generator, Array<BabelNodeStatement>>;
additionalFunctionValuesAndEffects: Map<FunctionValue, Effects> | void;
react: ReactSerializerState;
// function values nested in additional functions can't delay initializations
// TODO: revisit this and fix additional functions to be capable of delaying initializations
additionalFunctionValueNestedFunctions: Set<FunctionValue>;
@ -705,6 +722,119 @@ 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));
}
const expr = this.serializeValue(child);
if (t.isArrayExpression(expr)) {
applyKeysToNestedArray(((expr: any): BabelNodeArrayExpression), true, this.react.usedReactElementKeys);
} else if (t.isStringLiteral(expr) || t.isNumericLiteral(expr)) {
return t.jSXText(((expr: any).value: string) + "");
} else if (t.isJSXElement(expr)) {
return expr;
}
return t.jSXExpressionContainer(expr);
}
_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");
invariant(typeValue !== null, "JSXElement type of null");
let identifier = convertExpressionToJSXIdentifier(this.serializeValue(typeValue), true);
let attributes = [];
let children = [];
if (keyValue !== null) {
let keyExpr = this.serializeValue(keyValue);
if (keyExpr.type !== "NullLiteral") {
attributes.push(convertKeyValueToJSXAttribute("key", keyExpr));
}
}
if (refValue !== null) {
let refExpr = this.serializeValue(refValue);
if (refExpr.type !== "NullLiteral") {
attributes.push(convertKeyValueToJSXAttribute("ref", refExpr));
}
}
if (propsValue instanceof ObjectValue) {
// the propsValue is visited to get the properties, but we don't emit it as the object
// is contained within a JSXOpeningElement
this.serializedValues.add(propsValue);
// have to case propsValue to ObjectValue or Flow complains that propsValues can be null/undefined
for (let [key, propertyBinding] of (propsValue: ObjectValue).properties) {
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; // deleted
invariant(!IsAccessorDescriptor(this.realm, desc), "expected descriptor to be a non-accessor property");
invariant(key !== "key" && key !== "ref", `"${key}" is a reserved prop name`);
if (key === "children" && desc.value !== undefined) {
let childrenValue = desc.value;
if (childrenValue instanceof ArrayValue) {
this.serializedValues.add(childrenValue);
let childrenLength = this._getPropertyValue(childrenValue.properties, "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);
if (child instanceof Value) {
children.push(this._serializeValueReactElementChild(child));
} else {
this.logger.logError(val, `JSXElement "props.children[${i}]" failed to serialize due to a non-value`);
}
}
continue;
}
}
// otherwise it must be a value, as desc.value !== undefined.
children.push(this._serializeValueReactElementChild(((childrenValue: any): Value)));
continue;
}
if (desc.value instanceof Value) {
attributes.push(convertKeyValueToJSXAttribute(key, this.serializeValue(desc.value)));
}
}
}
let openingElement = t.jSXOpeningElement(identifier, (attributes: any), children.length === 0);
let closingElement = t.jSXClosingElement(identifier);
return t.jSXElement(openingElement, closingElement, children, children.length === 0);
}
_serializeValueMap(val: ObjectValue): BabelNodeExpression {
let kind = val.getKind();
let elems = [];
@ -988,6 +1118,8 @@ export class ResidualHeapSerializer {
return this._serializeValueTypedArrayOrDataView(val);
case "ArrayBuffer":
return this._serializeValueArrayBuffer(val);
case "ReactElement":
return this._serializeValueReactElement(val);
case "Map":
case "WeakMap":
return this._serializeValueMap(val);

View File

@ -13,7 +13,7 @@ import { GlobalEnvironmentRecord, DeclarativeEnvironmentRecord } from "../enviro
import { FatalError } from "../errors.js";
import { Realm } from "../realm.js";
import type { Effects } from "../realm.js";
import type { Descriptor, PropertyBinding } from "../types.js";
import type { Descriptor, PropertyBinding, ObjectKind } from "../types.js";
import { IsUnresolvableReference, ToLength, ResolveBinding, HashSet, IsArray, Get } from "../methods/index.js";
import {
BoundFunctionValue,
@ -115,20 +115,28 @@ export class ResidualHeapVisitor {
}
}
visitObjectProperties(obj: ObjectValue): void {
visitObjectProperties(obj: ObjectValue, kind?: ObjectKind): void {
// visit properties
for (let [symbol, propertyBinding] of obj.symbols) {
invariant(propertyBinding);
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; //deleted
this.visitDescriptor(desc);
this.visitValue(symbol);
if (kind !== "ReactElement") {
for (let [symbol, propertyBinding] of obj.symbols) {
invariant(propertyBinding);
let desc = propertyBinding.descriptor;
if (desc === undefined) continue; //deleted
this.visitDescriptor(desc);
this.visitValue(symbol);
}
}
// visit properties
for (let propertyBinding of obj.properties.values()) {
invariant(propertyBinding);
this.visitObjectProperty(propertyBinding);
for (let [propertyBindingKey, propertyBindingValue] of obj.properties) {
// we don't want to the $$typeof or _owner properties
// as this is contained within the JSXElement, otherwise
// they we be need to be emitted during serialization
if (kind === "ReactElement" && (propertyBindingKey === "$$typeof" || propertyBindingKey === "_owner")) {
continue;
}
invariant(propertyBindingValue);
this.visitObjectProperty(propertyBindingValue);
}
// inject properties with computed names
@ -142,7 +150,12 @@ export class ResidualHeapVisitor {
}
// prototype
this.visitObjectPrototype(obj);
if (kind !== "ReactElement") {
// we don't want to the ReactElement prototype visited
// as this is contained within the JSXElement, otherwise
// they we be need to be emitted during serialization
this.visitObjectPrototype(obj);
}
if (obj instanceof FunctionValue) this.visitConstructorPrototype(obj);
}
@ -372,7 +385,8 @@ export class ResidualHeapVisitor {
}
visitValueObject(val: ObjectValue): void {
this.visitObjectProperties(val);
let kind = val.getKind();
this.visitObjectProperties(val, kind);
// If this object is a prototype object that was implicitly created by the runtime
// for a constructor, then we can obtain a reference to this object
@ -383,12 +397,12 @@ export class ResidualHeapVisitor {
return;
}
let kind = val.getKind();
switch (kind) {
case "RegExp":
case "Number":
case "String":
case "Boolean":
case "ReactElement":
case "ArrayBuffer":
return;
case "Date":

View File

@ -20,6 +20,7 @@ import traverseFast from "../utils/traverse-fast.js";
import invariant from "../invariant.js";
import type { SerializerOptions } from "../options.js";
import { TimingStatistics, SerializerStatistics } from "./types.js";
import type { ReactSerializerState } from "./types.js";
import { Functions } from "./functions.js";
import { Logger } from "./logger.js";
import { Modules } from "./modules.js";
@ -49,6 +50,9 @@ export class Serializer {
if (serializerOptions.trace) this.realm.tracers.push(new LoggingTracer(this.realm));
this.options = serializerOptions;
this.react = {
usedReactElementKeys: new Set(),
};
}
realm: Realm;
@ -57,6 +61,7 @@ export class Serializer {
modules: Modules;
options: SerializerOptions;
statistics: SerializerStatistics;
react: ReactSerializerState;
_execute(sources: Array<SourceFile>, sourceMaps?: boolean = false) {
let realm = this.realm;
@ -149,7 +154,8 @@ export class Serializer {
residualHeapVisitor.referencedDeclaredValues,
additionalFunctionValuesAndEffects,
residualHeapVisitor.additionalFunctionValueInfos,
this.statistics
this.statistics,
this.react
).serialize();
if (this.logger.hasErrors()) return undefined;
if (timingStats !== undefined) timingStats.referenceCountsTime = Date.now() - timingStats.referenceCountsTime;
@ -171,7 +177,8 @@ export class Serializer {
residualHeapVisitor.referencedDeclaredValues,
additionalFunctionValuesAndEffects,
residualHeapVisitor.additionalFunctionValueInfos,
this.statistics
this.statistics,
this.react
);
let ast = residualHeapSerializer.serialize();

View File

@ -152,3 +152,7 @@ export type LocationService = {
getLocation: Value => void | BabelNodeIdentifier,
createLocation: () => BabelNodeIdentifier,
};
export type ReactSerializerState = {
usedReactElementKeys: Set<string>,
};

View File

@ -14,6 +14,7 @@ import { FunctionValue } from "../values/index.js";
import * as t from "babel-types";
import type { BabelNodeExpression, BabelNodeCallExpression } from "babel-types";
import { BabelTraversePath } from "babel-traverse";
import { convertExpressionToJSXIdentifier } from "../utils/jsx";
import type { TryQuery, FunctionInfo, ResidualFunctionBinding } from "./types.js";
export type ClosureRefVisitorState = {
@ -52,7 +53,13 @@ function replaceName(path, residualFunctionBinding, name, data) {
if (residualFunctionBinding && shouldVisit(path.node, data)) {
markVisited(residualFunctionBinding.serializedValue, data);
path.replaceWith(residualFunctionBinding.serializedValue);
let serializedValue = residualFunctionBinding.serializedValue;
if (path.node.type === "JSXIdentifier" || path.node.type === "JSXMemberIdentifier") {
path.replaceWith(convertExpressionToJSXIdentifier((serializedValue: any), true));
} else {
path.replaceWith(serializedValue);
}
}
}

View File

@ -275,5 +275,6 @@ export type ObjectKind =
| "ArrayBuffer"
| "WeakMap"
| "WeakSet"
| TypedArrayKind;
| TypedArrayKind
| "ReactElement";
// TODO #26 #712: Promises. All kinds of iterators. Generators.

161
src/utils/jsx.js Normal file
View File

@ -0,0 +1,161 @@
/**
* 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 * as t from "babel-types";
import type {
BabelNodeExpression,
BabelNodeArrayExpression,
BabelNodeJSXElement,
BabelNodeJSXMemberExpression,
BabelNodeJSXIdentifier,
BabelNodeIdentifier,
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;
}
export function convertExpressionToJSXIdentifier(
expr: BabelNodeExpression,
isRoot: boolean
): BabelNodeJSXMemberExpression | BabelNodeJSXIdentifier {
switch (expr.type) {
case "ThisExpression":
invariant(isRoot === false, `invalid conversion of root expression to JSXIdentifier for ThisExpression`);
return t.jSXIdentifier("this");
case "Identifier":
let name = expr.name;
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()),
"invalid JSXIdentifer from Identifier, Identifier name must be uppercase"
);
return t.jSXIdentifier(name);
case "StringLiteral":
let value = expr.value;
invariant(
// ensure the 1st character of the string is lowercase
// otherwise it will appear as a component
value.length > 0 && value[0] === value[0].toLowerCase(),
"invalid JSXIdentifer from string, strings must be lowercase"
);
return t.jSXIdentifier(value);
case "MemberExpression":
invariant(expr.computed === false, "Cannot inline computed expressions in JSX type.");
return t.jSXMemberExpression(
convertExpressionToJSXIdentifier(expr.object, false),
((convertExpressionToJSXIdentifier(expr.property, false): any): BabelNodeJSXIdentifier)
);
default:
invariant(false, "Invalid JSX type");
}
}
export function convertJSXExpressionToIdentifier(
expr: BabelNodeExpression
): BabelNodeMemberExpression | BabelNodeIdentifier {
switch (expr.type) {
case "JSXIdentifier":
return t.identifier(expr.name);
case "JSXMemberExpression":
return t.memberExpression(
convertJSXExpressionToIdentifier(expr.object),
(convertJSXExpressionToIdentifier(expr.property): any)
);
default:
invariant(false, "Invalid JSX type");
}
}
export function convertKeyValueToJSXAttribute(key: string, expr: BabelNodeExpression) {
return t.jSXAttribute(t.jSXIdentifier(key), expr.type === "StringLiteral" ? expr : t.jSXExpressionContainer(expr));
}
function addKeyToElement(astElement: BabelNodeJSXElement, key) {
let astAttributes = astElement.openingElement.attributes;
let existingKey = null;
for (let i = 0; i < astAttributes.length; i++) {
let astAttribute = astAttributes[i];
if (t.isJSXAttribute(astAttribute) && t.isJSXIdentifier(astAttribute.name) && astAttribute.name.name === "key") {
existingKey = astAttribute.value;
break;
}
}
if (existingKey === null) {
astAttributes.push(t.jSXAttribute(t.jSXIdentifier("key"), t.stringLiteral(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,
usedReactElementKeys: Set<string>
): void {
let astElements = expr.elements;
if (Array.isArray(astElements)) {
for (let i = 0; i < astElements.length; i++) {
let astElement = astElements[i];
if (astElement != null) {
if (t.isJSXElement(astElement) && isBase === false) {
addKeyToElement((astElement: any), getUniqueJSXElementKey("" + 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));
} 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));
} else if (t.isArrayExpression(consequent.type)) {
applyKeysToNestedArray(consequent, false, usedReactElementKeys);
}
}
}
}
}
}

View File

@ -27,7 +27,11 @@ export default function(
startLine: number = 1
): BabelNodeFile {
try {
let ast = parse(code, { filename, sourceType, startLine });
let plugins = [];
if (realm.react.enabled) {
plugins.push("jsx", "flow");
}
let ast = parse(code, { filename, sourceType, startLine, plugins });
traverseFast(ast, node => {
invariant(node.loc);
node.loc.source = filename;

View File

@ -34,6 +34,7 @@ import {
UndefinedValue,
Value,
} from "./index.js";
import { isReactElement } from "../utils/jsx";
import type { ECMAScriptSourceFunctionValue, NativeFunctionCallback } from "./index.js";
import {
joinValuesAsConditional,
@ -319,6 +320,7 @@ export default class ObjectValue extends ConcreteValue {
if (this.$ArrayBufferData !== undefined) return "ArrayBuffer";
if (this.$WeakMapData !== undefined) return "WeakMap";
if (this.$WeakSetData !== undefined) return "WeakSet";
if (isReactElement(this) && this.$Realm.react.enabled) return "ReactElement";
if (this.$TypedArrayName !== undefined) return this.$TypedArrayName;
// TODO #26 #712: Promises. All kinds of iterators. Generators.
return "Object";

View File

@ -0,0 +1,61 @@
// react
// babel:jsx
function MyComponent(props) {
return <span>{props.title}</span>;
}
MyComponent.defaultProps = {
title: 'Hello world',
children: 'No children!',
};
function ChildComponent(props) {
return <span>{props.title}</span>;
}
ChildComponent.defaultProps = {
title: 'I am a child',
};
function createElement(type, options, ...children) {
let key = null;
let ref = null;
if (options != null) {
if (options.key !== undefined) {
key = options.key;
delete options.key;
}
if (options.ref !== undefined) {
ref = options.ref;
delete options.ref;
}
}
let props = Object.assign({}, options);
if (children !== undefined) {
if (children.length === 1) {
props.children = children[0];
} else {
props.children = children;
}
}
return {
$$typeof: Symbol.for('react.element'),
props,
key,
ref,
type,
_owner: undefined,
}
}
global.React = {
createElement,
};
global.reactElement = (
<MyComponent>
<ChildComponent title={"I am a child (overwritten)"} />
</MyComponent>
);
inspect = function() { return reactElement; }

View File

@ -0,0 +1,48 @@
// react
// babel:jsx
function MyComponent() {
// ...
}
function createElement(type, options, ...children) {
let key = null;
let ref = null;
if (options != null) {
if (options.key !== undefined) {
key = options.key;
delete options.key;
}
if (options.ref !== undefined) {
ref = options.ref;
delete options.ref;
}
}
let props = Object.assign({}, options);
if (children !== undefined) {
if (children.length === 1) {
props.children = children[0];
} else {
props.children = children;
}
}
return {
$$typeof: Symbol.for('react.element'),
props,
key,
ref,
type,
_owner: undefined,
}
}
global.React = {
createElement,
};
global.reactElement = createElement('div', null, createElement(MyComponent, {
foo: 'bar'
}, 'Hello world'));
inspect = function() { return reactElement; }

View File

@ -0,0 +1,51 @@
// react
// babel:jsx
function MyComponent() {
// ...
}
const Container = {
MyComponent,
};
function createElement(type, options, ...children) {
let key = null;
let ref = null;
if (options != null) {
if (options.key !== undefined) {
key = options.key;
delete options.key;
}
if (options.ref !== undefined) {
ref = options.ref;
delete options.ref;
}
}
let props = Object.assign({}, options);
if (children !== undefined) {
if (children.length === 1) {
props.children = children[0];
} else {
props.children = children;
}
}
return {
$$typeof: Symbol.for('react.element'),
props,
key,
ref,
type,
_owner: undefined,
}
}
global.React = {
createElement,
};
global.reactElement = <div><MyComponent foo="bar">Hello world</MyComponent></div>;
global.reactElementB = <div><Container.MyComponent foo="bar">Hello world</Container.MyComponent></div>;
inspect = function() { return [reactElement, reactElementB]; }

View File

@ -0,0 +1,59 @@
// react
// babel:jsx
const Container = {
MyComponent,
};
function createElement(type, options, ...children) {
let key = null;
let ref = null;
if (options != null) {
if (options.key !== undefined) {
key = options.key;
delete options.key;
}
if (options.ref !== undefined) {
ref = options.ref;
delete options.ref;
}
}
let props = Object.assign({}, options);
if (children !== undefined) {
if (children.length === 1) {
props.children = children[0];
} else {
props.children = children;
}
}
return {
$$typeof: Symbol.for('react.element'),
props,
key,
ref,
type,
_owner: undefined,
}
}
global.React = {
createElement,
};
function MyComponent(props) {
return <span>Title: {props.title}, Number: {props.number}</span>;
}
let props = {
title: "Hello world",
number: 50,
};
global.reactElement = (
<div>
<MyComponent {...props}>Hello world</MyComponent>
</div>
);
inspect = function() { return reactElement; }

View File

@ -0,0 +1,64 @@
// react
// babel:jsx
function createElement(type, options, ...children) {
let key = null;
let ref = null;
if (options != null) {
if (options.key !== undefined) {
key = options.key;
delete options.key;
}
if (options.ref !== undefined) {
ref = options.ref;
delete options.ref;
}
}
let props = Object.assign({}, options);
if (children !== undefined) {
if (children.length === 1) {
props.children = children[0];
} else {
props.children = children;
}
}
return {
$$typeof: Symbol.for('react.element'),
props,
key,
ref,
type,
_owner: undefined,
}
}
global.React = {
createElement,
};
global.toggle = true;
global.reactElement = (
<div>
<span>Span 1</span>
Text node
{
[
<span>Span 2</span>,
"Text node",
[
<em key="a">Em 1</em>,
<em key="b">Em 2</em>,
"Text node",
<em key="c">Em 3</em>,
toggle ? <em key="d">Em 4</em> : <em key="d">Em 5</em>,
],
<span>Span 3</span>,
]
}
<span>Span 4</span>
</div>
);
inspect = function() { return reactElement; }

133
yarn.lock
View File

@ -272,6 +272,14 @@ babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
esutils "^2.0.2"
js-tokens "^3.0.0"
babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
dependencies:
chalk "^1.1.3"
esutils "^2.0.2"
js-tokens "^3.0.2"
babel-core@^5.8.33:
version "5.8.38"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-5.8.38.tgz#1fcaee79d7e61b750b00b8e54f6dfc9d0af86558"
@ -323,7 +331,7 @@ babel-core@^5.8.33:
trim-right "^1.0.0"
try-resolve "^1.0.0"
babel-core@^6.24.1, babel-core@^6.8.0:
babel-core@^6.24.1:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729"
dependencies:
@ -347,6 +355,30 @@ babel-core@^6.24.1, babel-core@^6.8.0:
slash "^1.0.0"
source-map "^0.5.0"
babel-core@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
dependencies:
babel-code-frame "^6.26.0"
babel-generator "^6.26.0"
babel-helpers "^6.24.1"
babel-messages "^6.23.0"
babel-register "^6.26.0"
babel-runtime "^6.26.0"
babel-template "^6.26.0"
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
convert-source-map "^1.5.0"
debug "^2.6.8"
json5 "^0.5.1"
lodash "^4.17.4"
minimatch "^3.0.4"
path-is-absolute "^1.0.1"
private "^0.1.7"
slash "^1.0.0"
source-map "^0.5.6"
babel-eslint@^4.1.8:
version "4.1.8"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-4.1.8.tgz#4f79e7a4f5879ecf03f48cb16f552a355fcc31b2"
@ -378,6 +410,19 @@ babel-generator@^6.25.0, babel-generator@^6.8.0:
source-map "^0.5.0"
trim-right "^1.0.1"
babel-generator@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
dependencies:
babel-messages "^6.23.0"
babel-runtime "^6.26.0"
babel-types "^6.26.0"
detect-indent "^4.0.0"
jsesc "^1.3.0"
lodash "^4.17.4"
source-map "^0.5.6"
trim-right "^1.0.1"
babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
@ -911,6 +956,18 @@ babel-register@^6.24.1:
mkdirp "^0.5.1"
source-map-support "^0.4.2"
babel-register@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
dependencies:
babel-core "^6.26.0"
babel-runtime "^6.26.0"
core-js "^2.5.0"
home-or-tmp "^2.0.0"
lodash "^4.17.4"
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@^6.18.0, babel-runtime@^6.22.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
@ -918,6 +975,13 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0:
core-js "^2.4.0"
regenerator-runtime "^0.10.0"
babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
babel-template@^6.24.1, babel-template@^6.25.0, babel-template@^6.9.0:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071"
@ -928,6 +992,16 @@ babel-template@^6.24.1, babel-template@^6.25.0, babel-template@^6.9.0:
babylon "^6.17.2"
lodash "^4.2.0"
babel-template@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
dependencies:
babel-runtime "^6.26.0"
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
lodash "^4.17.4"
babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0, babel-traverse@^6.9.0:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1"
@ -942,6 +1016,20 @@ babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0, babel-tr
invariant "^2.2.0"
lodash "^4.2.0"
babel-traverse@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
dependencies:
babel-code-frame "^6.26.0"
babel-messages "^6.23.0"
babel-runtime "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
debug "^2.6.8"
globals "^9.18.0"
invariant "^2.2.2"
lodash "^4.17.4"
babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25.0, babel-types@^6.9.0:
version "6.25.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e"
@ -951,6 +1039,15 @@ babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25
lodash "^4.2.0"
to-fast-properties "^1.0.1"
babel-types@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
dependencies:
babel-runtime "^6.26.0"
esutils "^2.0.2"
lodash "^4.17.4"
to-fast-properties "^1.0.3"
babylon@^5.8.38:
version "5.8.38"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-5.8.38.tgz#ec9b120b11bf6ccd4173a18bf217e60b79859ffd"
@ -1386,7 +1483,7 @@ content-type-parser@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94"
convert-source-map@^1.1.0, convert-source-map@^1.1.1:
convert-source-map@^1.1.0, convert-source-map@^1.1.1, convert-source-map@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
@ -1398,6 +1495,10 @@ core-js@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
core-js@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -1505,6 +1606,12 @@ debug@^2.1.1, debug@^2.2.0:
dependencies:
ms "2.0.0"
debug@^2.6.8:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
ms "2.0.0"
debug@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
@ -2487,7 +2594,7 @@ globals@^8.18.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-8.18.0.tgz#93d4a62bdcac38cfafafc47d6b034768cb0ffcb4"
globals@^9.0.0, globals@^9.14.0:
globals@^9.0.0, globals@^9.14.0, globals@^9.18.0:
version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@ -3065,7 +3172,7 @@ js-tokens@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.1.tgz#cc435a5c8b94ad15acb7983140fc80182c89aeae"
js-tokens@^3.0.0:
js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@ -3394,7 +3501,7 @@ lodash@^3.10.0, lodash@^3.9.3:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.2.0, lodash@^4.3.0:
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@ -3903,7 +4010,7 @@ path-exists@^2.0.0:
dependencies:
pinkie-promise "^2.0.0"
path-is-absolute@^1.0.0:
path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@ -4007,7 +4114,7 @@ pretty-ms@2.1.0:
parse-ms "^1.0.0"
plur "^1.0.0"
private@^0.1.6, private@~0.1.5:
private@^0.1.6, private@^0.1.7, private@~0.1.5:
version "0.1.7"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
@ -4211,6 +4318,10 @@ regenerator-runtime@^0.10.0:
version "0.10.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
regenerator-runtime@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
regenerator-transform@0.9.11:
version "0.9.11"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283"
@ -4540,6 +4651,12 @@ source-map-support@^0.2.10:
dependencies:
source-map "0.1.32"
source-map-support@^0.4.15:
version "0.4.18"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
dependencies:
source-map "^0.5.6"
source-map-support@^0.4.2, source-map-support@^0.4.6:
version "0.4.15"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
@ -4898,7 +5015,7 @@ to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
to-fast-properties@^1.0.0, to-fast-properties@^1.0.1:
to-fast-properties@^1.0.0, to-fast-properties@^1.0.1, to-fast-properties@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"