prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
203 lines (158 loc) • 7.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.canHoistFunction = canHoistFunction;
exports.canHoistReactElement = canHoistReactElement;
exports.determineIfReactElementCanBeHoisted = determineIfReactElementCanBeHoisted;
var _realm = require("../realm.js");
var _index = require("../values/index.js");
var _index2 = require("../methods/index.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _utils = require("./utils.js");
var _ResidualHeapVisitor = require("../serializer/ResidualHeapVisitor.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.
*/
// a nested object of a React Element should be hoisted where all its properties are known
// at evaluation time to be safe to hoist (because of the heuristics of a React render)
function canHoistObject(realm, object, residualHeapVisitor, visitedValues) {
if ((0, _utils.isReactElement)(object)) {
return canHoistReactElement(realm, object, residualHeapVisitor, visitedValues);
}
for (let [propName] of object.properties) {
let prop = (0, _index2.Get)(realm, object, propName);
if (!canHoistValue(realm, prop, residualHeapVisitor, visitedValues)) {
return false;
}
}
for (let [symbol] of object.symbols) {
let prop = (0, _index2.Get)(realm, object, symbol);
if (!canHoistValue(realm, prop, residualHeapVisitor, visitedValues)) {
return false;
}
}
return true;
}
function canHoistArray(realm, array, residualHeapVisitor, visitedValues) {
if (array.intrinsicName) return false;
let lengthValue = (0, _index2.Get)(realm, array, "length");
if (!canHoistValue(realm, lengthValue, residualHeapVisitor, visitedValues)) {
return false;
}
if (lengthValue instanceof _index.NumberValue) {
let length = lengthValue.value;
for (let i = 0; i < length; i++) {
let element = (0, _index2.Get)(realm, array, "" + i);
if (!canHoistValue(realm, element, residualHeapVisitor, visitedValues)) {
return false;
}
}
}
return true;
}
function canHoistFunction(realm, func, residualHeapVisitor, visitedValues) {
if (realm.react.hoistableFunctions.has(func)) {
// cast because Flow thinks that we may have set a value to be something other than a boolean?
return realm.react.hoistableFunctions.get(func);
}
if (residualHeapVisitor === undefined) {
return false;
} // get the function instance
let functionInstance = residualHeapVisitor.functionInstances.get(func); // we can safely hoist the function if the residual bindings hoistable too
if (functionInstance !== undefined) {
(0, _invariant.default)(functionInstance.residualFunctionBindings instanceof Map);
let residualBindings = functionInstance.residualFunctionBindings;
for (let [, {
declarativeEnvironmentRecord,
value
}] of residualBindings) {
// if declarativeEnvironmentRecord is null, it's likely a global binding
// so we can assume that we can still hoist this function
if (declarativeEnvironmentRecord !== null) {
if (value === undefined) {
return false;
}
(0, _invariant.default)(value instanceof _index.Value);
if (!canHoistValue(realm, value, residualHeapVisitor, visitedValues)) {
return false;
}
}
}
if (func instanceof _index.ECMAScriptSourceFunctionValue) {
let code = func.$ECMAScriptCode;
let functionInfos = residualHeapVisitor.functionInfos.get(code);
if (functionInfos && functionInfos.unbound.size > 0) {
return false;
}
}
realm.react.hoistableFunctions.set(func, true);
return true;
}
realm.react.hoistableFunctions.set(func, false);
return false;
}
function canHoistAbstract(realm, abstract, residualHeapVisitor) {
// TODO #1687: add abstract value hoisting
return false;
}
function isPrimitive(realm, value) {
return value instanceof _index.StringValue || value instanceof _index.NumberValue || value instanceof _index.SymbolValue || value instanceof _index.BooleanValue || value === realm.intrinsics.null || value === realm.intrinsics.undefined;
}
function canHoistValue(realm, value, residualHeapVisitor, visitedValues) {
if (visitedValues.has(value)) {
// If there is a cycle, bail out.
// TODO: is there some way to *not* bail out in this case?
// Currently if we don't, the output is broken.
return false;
}
visitedValues.add(value);
let canHoist = false;
if (value instanceof _index.ArrayValue) {
canHoist = canHoistArray(realm, value, residualHeapVisitor, visitedValues);
} else if (value instanceof _index.FunctionValue) {
canHoist = canHoistFunction(realm, value, residualHeapVisitor, visitedValues);
} else if (value instanceof _index.ObjectValue) {
canHoist = canHoistObject(realm, value, residualHeapVisitor, visitedValues);
} else if (value instanceof _index.AbstractValue) {
canHoist = canHoistAbstract(realm, value, residualHeapVisitor);
} else if (isPrimitive) {
canHoist = true;
}
visitedValues.delete(value);
return canHoist;
}
function canHoistReactElement(realm, reactElement, residualHeapVisitor, visitedValues) {
if (realm.react.hoistableReactElements.has(reactElement)) {
// cast because Flow thinks that we may have set a value to be something other than a boolean?
return realm.react.hoistableReactElements.get(reactElement);
}
if (residualHeapVisitor === undefined) {
return false;
}
let type = (0, _utils.getProperty)(realm, reactElement, "type");
let ref = (0, _utils.getProperty)(realm, reactElement, "ref");
let key = (0, _utils.getProperty)(realm, reactElement, "key");
let props = (0, _utils.getProperty)(realm, reactElement, "props");
if (visitedValues === undefined) {
visitedValues = new Set();
visitedValues.add(reactElement);
}
if (canHoistValue(realm, type, residualHeapVisitor, visitedValues) && // we can't hoist string "refs" or if they're abstract, as they might be abstract strings
!(ref instanceof String || ref instanceof _index.AbstractValue) && canHoistValue(realm, ref, residualHeapVisitor, visitedValues) && canHoistValue(realm, key, residualHeapVisitor, visitedValues) && !props.isPartialObject() && canHoistValue(realm, props, residualHeapVisitor, visitedValues)) {
realm.react.hoistableReactElements.set(reactElement, true);
return true;
}
realm.react.hoistableReactElements.set(reactElement, false);
return false;
}
function determineIfReactElementCanBeHoisted(realm, reactElement, residualHeapVisitor) {
canHoistReactElement(realm, reactElement, residualHeapVisitor);
}
//# sourceMappingURL=hoisting.js.map