prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
289 lines (223 loc) • 9.97 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ReactEquivalenceSet = exports.temporalAliasSymbol = void 0;
var _realm = require("../realm.js");
var _index = require("../values/index.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _utils = require("./utils.js");
var _ResidualReactElementVisitor = require("../serializer/ResidualReactElementVisitor.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
const temporalAliasSymbol = Symbol("temporalAlias"); // ReactEquivalenceSet keeps records around of the values
// of ReactElement/JSX nodes so we can return the same immutable values
// where possible, i.e. <div /> === <div />
//
// Rather than uses hashes, this class uses linked Maps to track equality of objects.
// It does this by recursively iterating through objects, by their properties/symbols and using
// each property key as a map, and then from that map, each value as a map. The value
// then links to the subsequent property/symbol in the object. This approach ensures insertion
// is maintained through all objects.
exports.temporalAliasSymbol = temporalAliasSymbol;
class ReactEquivalenceSet {
constructor(realm, residualReactElementVisitor) {
this.realm = realm;
this.residualReactElementVisitor = residualReactElementVisitor;
this.objectRoot = new Map();
this.arrayRoot = new Map();
this.reactElementRoot = new Map();
this.reactPropsRoot = new Map();
this.temporalAliasRoot = new Map();
}
_createNode() {
return {
map: new Map(),
value: null
};
}
getKey(key, map, visitedValues) {
if (!map.has(key)) {
map.set(key, new Map());
}
return map.get(key);
}
_getValue(val, map, visitedValues) {
if (val instanceof _index.StringValue || val instanceof _index.NumberValue) {
val = val.value;
} else if (val instanceof _index.AbstractValue) {
val = this.residualReactElementVisitor.residualHeapVisitor.equivalenceSet.add(val);
} else if (val instanceof _index.ArrayValue) {
val = this._getArrayValue(val, visitedValues);
} else if (val instanceof _index.ObjectValue && !(val instanceof _index.FunctionValue)) {
val = this._getObjectValue(val, visitedValues);
}
if (!map.has(val)) {
map.set(val, this._createNode());
}
return map.get(val);
} // for objects: [key/symbol] -> [key/symbol]... as nodes
_getObjectValue(object, visitedValues) {
if (visitedValues.has(object)) return object;
visitedValues.add(object);
if ((0, _utils.isReactElement)(object)) {
return this.residualReactElementVisitor.reactElementEquivalenceSet.add(object);
}
let currentMap = this.objectRoot;
let result;
for (let [propName] of object.properties) {
currentMap = this.getKey(propName, currentMap, visitedValues);
result = this.getEquivalentPropertyValue(object, propName, currentMap, visitedValues);
currentMap = result.map;
}
for (let [symbol] of object.symbols) {
currentMap = this.getKey(symbol, currentMap, visitedValues);
let prop = (0, _utils.getProperty)(this.realm, object, symbol);
result = this._getValue(prop, currentMap, visitedValues);
currentMap = result.map;
}
let temporalAlias = object.temporalAlias;
if (temporalAlias !== undefined) {
currentMap = this.getKey(temporalAliasSymbol, currentMap, visitedValues);
result = this.getTemporalAliasValue(temporalAlias, currentMap, visitedValues);
}
if (result === undefined) {
// If we have a temporalAlias, we can never return an empty object
if (temporalAlias === undefined && this.realm.react.emptyObject !== undefined) {
return this.realm.react.emptyObject;
}
return object;
}
if (result.value === null) {
result.value = object;
}
return result.value;
}
_getTemporalValue(temporalAlias, visitedValues) {
// Check to ensure the temporal alias is definitely declared in the current scope
if (!this.residualReactElementVisitor.wasTemporalAliasDeclaredInCurrentScope(temporalAlias)) {
return temporalAlias;
}
let temporalOperationEntry = this.realm.getTemporalOperationEntryFromDerivedValue(temporalAlias);
if (temporalOperationEntry === undefined) {
return temporalAlias;
}
let temporalArgs = temporalOperationEntry.args;
if (temporalArgs.length === 0) {
return temporalAlias;
}
let currentMap = this.temporalAliasRoot;
let result;
for (let i = 0; i < temporalArgs.length; i++) {
let arg = temporalArgs[i];
let equivalenceArg;
if (arg instanceof _index.ObjectValue && arg.temporalAlias === temporalAlias) {
continue;
}
if (arg instanceof _index.ObjectValue && (0, _utils.isReactElement)(arg)) {
equivalenceArg = this.residualReactElementVisitor.reactElementEquivalenceSet.add(arg);
if (arg !== equivalenceArg) {
temporalArgs[i] = equivalenceArg;
}
} else if (arg instanceof _index.AbstractObjectValue && !arg.values.isTop() && arg.kind !== "conditional") {
// Might be a temporal, so let's check
let childTemporalOperationEntry = this.realm.getTemporalOperationEntryFromDerivedValue(arg);
if (childTemporalOperationEntry !== undefined) {
equivalenceArg = this._getTemporalValue(arg, visitedValues);
(0, _invariant.default)(equivalenceArg instanceof _index.AbstractObjectValue);
if (equivalenceArg !== arg) {
temporalArgs[i] = equivalenceArg;
}
}
} else if (arg instanceof _index.AbstractValue) {
equivalenceArg = this.residualReactElementVisitor.residualHeapVisitor.equivalenceSet.add(arg);
if (arg !== equivalenceArg) {
temporalArgs[i] = equivalenceArg;
}
}
currentMap = this.getKey(i, currentMap, visitedValues);
(0, _invariant.default)(arg instanceof _index.Value && (equivalenceArg instanceof _index.Value || equivalenceArg === undefined));
result = this._getValue(equivalenceArg || arg, currentMap, visitedValues);
currentMap = result.map;
}
(0, _invariant.default)(result !== undefined);
if (result.value === null) {
result.value = temporalAlias;
} // Check to ensure the equivalent temporal alias is definitely declared in the current scope
if (!this.residualReactElementVisitor.wasTemporalAliasDeclaredInCurrentScope(result.value)) {
result.value = temporalAlias;
return temporalAlias;
}
return result.value;
}
getTemporalAliasValue(temporalAlias, map, visitedValues) {
let result = this._getTemporalValue(temporalAlias, visitedValues);
(0, _invariant.default)(result instanceof _index.AbstractObjectValue);
if (!map.has(result)) {
map.set(result, this._createNode());
}
return map.get(result);
} // for arrays: [length] -> ([length] is numeric) -> [0] -> [1] -> [2]... as nodes
_getArrayValue(array, visitedValues) {
if (visitedValues.has(array)) return array;
if (array.intrinsicName) return array;
visitedValues.add(array);
let currentMap = this.arrayRoot;
currentMap = this.getKey("length", currentMap, visitedValues);
let result = this.getEquivalentPropertyValue(array, "length", currentMap, visitedValues);
currentMap = result.map;
let lengthValue = (0, _utils.getProperty)(this.realm, array, "length"); // If we have a numeric lenth that is not abstract, then also check all the array elements
if (lengthValue instanceof _index.NumberValue) {
(0, _invariant.default)(lengthValue instanceof _index.NumberValue);
let length = lengthValue.value;
for (let i = 0; i < length; i++) {
currentMap = this.getKey(i, currentMap, visitedValues);
result = this.getEquivalentPropertyValue(array, "" + i, currentMap, visitedValues);
currentMap = result.map;
}
}
if (result === undefined) {
if (this.realm.react.emptyArray !== undefined) {
return this.realm.react.emptyArray;
}
return array;
}
if (result.value === null) {
result.value = array;
}
(0, _invariant.default)(result.value instanceof _index.ArrayValue);
return result.value;
}
getEquivalentPropertyValue(object, propName, map, visitedValues) {
let prop = (0, _utils.getProperty)(this.realm, object, propName);
let isFinal = object.mightBeFinalObject();
let equivalentProp;
if (prop instanceof _index.ObjectValue && (0, _utils.isReactElement)(prop)) {
equivalentProp = this.residualReactElementVisitor.reactElementEquivalenceSet.add(prop);
} else if (prop instanceof _index.ObjectValue && (0, _utils.isReactPropsObject)(prop)) {
equivalentProp = this.residualReactElementVisitor.reactPropsEquivalenceSet.add(prop);
} else if (prop instanceof _index.AbstractValue) {
equivalentProp = this.residualReactElementVisitor.residualHeapVisitor.equivalenceSet.add(prop);
}
if (equivalentProp !== undefined) {
if (prop !== equivalentProp && isFinal) {
(0, _utils.hardModifyReactObjectPropertyBinding)(this.realm, object, propName, equivalentProp);
}
if (!map.has(equivalentProp)) {
map.set(equivalentProp, this._createNode());
}
return map.get(equivalentProp);
} else {
return this._getValue(prop, map, visitedValues);
}
}
}
exports.ReactEquivalenceSet = ReactEquivalenceSet;
//# sourceMappingURL=ReactEquivalenceSet.js.map