UNPKG

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
"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