prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
435 lines (373 loc) • 18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResidualReactElementSerializer = undefined;
var _realm = require("../realm.js");
var _ResidualHeapSerializer = require("./ResidualHeapSerializer.js");
var _hoisting = require("../react/hoisting.js");
var _babelTypes = require("babel-types");
var t = _interopRequireWildcard(_babelTypes);
var _index = require("../values/index.js");
var _jsx = require("../react/jsx.js");
var _logger = require("../utils/logger.js");
var _invariant = require("../invariant.js");
var _invariant2 = _interopRequireDefault(_invariant);
var _errors = require("../errors");
var _utils = require("../react/utils.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
/**
* 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.
*/
class ResidualReactElementSerializer {
constructor(realm, residualHeapSerializer) {
this.realm = realm;
this.residualHeapSerializer = residualHeapSerializer;
this.logger = residualHeapSerializer.logger;
this.reactOutput = realm.react.output || "create-element";
this._lazilyHoistedNodes = undefined;
}
_createReactElement(value) {
return { attributes: [], children: [], declared: false, type: undefined, value };
}
_createReactElementAttribute() {
return { expr: undefined, key: undefined, type: "PENDING" };
}
_createReactElementChild() {
return { expr: undefined, type: "PENDING" };
}
_emitHoistedReactElement(id, reactElement, hoistedCreateElementIdentifier, originalCreateElementIdentifier) {
// if the currentHoistedReactElements is not defined, we create it an emit the function call
// this should only occur once per additional function
if (this._lazilyHoistedNodes === undefined) {
let funcId = t.identifier(this.residualHeapSerializer.functionNameGenerator.generate());
this._lazilyHoistedNodes = {
id: funcId,
createElementIdentifier: hoistedCreateElementIdentifier,
nodes: []
};
let statement = t.expressionStatement(t.logicalExpression("&&", t.binaryExpression("===", id, t.unaryExpression("void", t.numericLiteral(0), true)),
// pass the createElementIdentifier if it's not null
t.callExpression(funcId, originalCreateElementIdentifier ? [originalCreateElementIdentifier] : [])));
this.residualHeapSerializer.emitter.emit(statement);
}
// we then push the reactElement and its id into our list of elements to process after
// the current additional function has serialzied
(0, _invariant2.default)(this._lazilyHoistedNodes !== undefined);
(0, _invariant2.default)(Array.isArray(this._lazilyHoistedNodes.nodes));
this._lazilyHoistedNodes.nodes.push({ id, astNode: reactElement });
}
_getReactLibraryValue() {
let reactLibraryObject = this.realm.fbLibraries.react;
// if there is no React library, then we should throw and error
if (reactLibraryObject === undefined) {
throw new _errors.FatalError("unable to find React library reference in scope");
}
return reactLibraryObject;
}
_getReactCreateElementValue() {
let reactLibraryObject = this._getReactLibraryValue();
return (0, _utils.getProperty)(this.realm, reactLibraryObject, "createElement");
}
_emitReactElement(reactElement) {
let { value } = reactElement;
let shouldHoist = this.residualHeapSerializer.isReferencedOnlyByAdditionalFunction(value) !== undefined && (0, _hoisting.canHoistReactElement)(this.realm, value);
let id = this.residualHeapSerializer.getSerializeObjectIdentifier(value);
// this identifier is used as the deafult, but also passed to the hoisted factory function
let originalCreateElementIdentifier = null;
// this name is used when hoisting, and is passed into the factory function, rather than the original
let hoistedCreateElementIdentifier = null;
let reactElementAstNode;
this.residualHeapSerializer.emitter.emitNowOrAfterWaitingForDependencies([value], () => {
if (this.reactOutput === "jsx") {
reactElementAstNode = this._serializeReactElementToJSXElement(value, reactElement);
} else if (this.reactOutput === "create-element") {
let createElement = this._getReactCreateElementValue();
originalCreateElementIdentifier = this.residualHeapSerializer.serializeValue(createElement);
if (shouldHoist) {
// if we haven't created a _lazilyHoistedNodes before, then this is the first time
// so we only create the hoisted identifier once
if (this._lazilyHoistedNodes === undefined) {
// create a new unique instance
hoistedCreateElementIdentifier = t.identifier(this.residualHeapSerializer.intrinsicNameGenerator.generate());
} else {
hoistedCreateElementIdentifier = this._lazilyHoistedNodes.createElementIdentifier;
}
}
let createElementIdentifier = shouldHoist ? hoistedCreateElementIdentifier : originalCreateElementIdentifier;
reactElementAstNode = this._serializeReactElementToCreateElement(value, reactElement, createElementIdentifier);
} else {
(0, _invariant2.default)(false, "Unknown reactOutput specified");
}
// if we are hoisting this React element, put the assignment in the body
// also ensure we are in an additional function
if (shouldHoist) {
this._emitHoistedReactElement(id, reactElementAstNode, hoistedCreateElementIdentifier, originalCreateElementIdentifier);
} else {
if (reactElement.declared) {
this.residualHeapSerializer.emitter.emit(t.expressionStatement(t.assignmentExpression("=", id, reactElementAstNode)));
} else {
reactElement.declared = true;
this.residualHeapSerializer.emitter.emit(t.variableDeclaration("var", [t.variableDeclarator(id, reactElementAstNode)]));
}
}
});
return id;
}
_serializeNowOrAfterWaitingForDependencies(value, reactElement, func, shouldSerialize = true) {
let reason = this.residualHeapSerializer.emitter.getReasonToWaitForDependencies(value);
const serialize = () => {
func();
};
if (reason) {
this.residualHeapSerializer.emitter.emitAfterWaiting(reason, [value], () => {
serialize();
this._emitReactElement(reactElement);
});
} else {
serialize();
}
}
_serializeReactFragmentType(typeValue) {
let reactLibraryObject = this._getReactLibraryValue();
// we want to vist the Symbol type, but we don't want to serialize it
// as this is a React internal
this.residualHeapSerializer.serializedValues.add(typeValue);
(0, _invariant2.default)(typeValue.$Description instanceof _index.StringValue);
this.residualHeapSerializer.serializedValues.add(typeValue.$Description);
return t.memberExpression(this.residualHeapSerializer.serializeValue(reactLibraryObject), t.identifier("Fragment"));
}
_serializeReactElementType(reactElement) {
let { value } = reactElement;
let typeValue = (0, _utils.getProperty)(this.realm, value, "type");
this._serializeNowOrAfterWaitingForDependencies(typeValue, reactElement, () => {
let expr;
if (typeValue instanceof _index.SymbolValue && typeValue === (0, _utils.getReactSymbol)("react.fragment", this.realm)) {
expr = this._serializeReactFragmentType(typeValue);
} else {
expr = this.residualHeapSerializer.serializeValue(typeValue);
}
reactElement.type = expr;
});
}
_serializeReactElementAttributes(reactElement) {
let { value } = reactElement;
let keyValue = (0, _utils.getProperty)(this.realm, value, "key");
let refValue = (0, _utils.getProperty)(this.realm, value, "ref");
let propsValue = (0, _utils.getProperty)(this.realm, value, "props");
if (keyValue !== this.realm.intrinsics.null) {
let reactElementKey = this._createReactElementAttribute();
this._serializeNowOrAfterWaitingForDependencies(keyValue, reactElement, () => {
let expr = this.residualHeapSerializer.serializeValue(keyValue);
reactElementKey.expr = expr;
reactElementKey.key = "key";
reactElementKey.type = "PROPERTY";
});
reactElement.attributes.push(reactElementKey);
}
if (refValue !== this.realm.intrinsics.null) {
let reactElementRef = this._createReactElementAttribute();
this._serializeNowOrAfterWaitingForDependencies(refValue, reactElement, () => {
let expr = this.residualHeapSerializer.serializeValue(refValue);
reactElementRef.expr = expr;
reactElementRef.key = "ref";
reactElementRef.type = "PROPERTY";
});
reactElement.attributes.push(reactElementRef);
}
const assignPropsAsASpreadProp = () => {
let reactElementSpread = this._createReactElementAttribute();
this._serializeNowOrAfterWaitingForDependencies(propsValue, reactElement, () => {
let expr = this.residualHeapSerializer.serializeValue(propsValue);
reactElementSpread.expr = expr;
reactElementSpread.type = "SPREAD";
});
reactElement.attributes.push(reactElementSpread);
};
// handle props
if (propsValue instanceof _index.AbstractValue) {
assignPropsAsASpreadProp();
} else if (propsValue instanceof _index.ObjectValue) {
if (propsValue.isPartialObject()) {
assignPropsAsASpreadProp();
} else {
this.residualHeapSerializer.serializedValues.add(propsValue);
for (let [propName, binding] of propsValue.properties) {
if (binding.descriptor !== undefined && propName !== "children") {
(0, _invariant2.default)(propName !== "key" && propName !== "ref", `"${propName}" is a reserved prop name`);
let propValue = (0, _utils.getProperty)(this.realm, propsValue, propName);
let reactElementAttribute = this._createReactElementAttribute();
this._serializeNowOrAfterWaitingForDependencies(propValue, reactElement, () => {
let expr = this.residualHeapSerializer.serializeValue(propValue);
reactElementAttribute.expr = expr;
reactElementAttribute.key = propName;
reactElementAttribute.type = "PROPERTY";
});
reactElement.attributes.push(reactElementAttribute);
}
}
}
}
}
_serializeReactElementChildren(reactElement) {
let { value } = reactElement;
let propsValue = (0, _utils.getProperty)(this.realm, value, "props");
if (!(propsValue instanceof _index.ObjectValue)) {
return;
}
// handle children
if (propsValue.properties.has("children")) {
let childrenValue = (0, _utils.getProperty)(this.realm, propsValue, "children");
this.residualHeapSerializer.serializedValues.add(childrenValue);
if (childrenValue !== this.realm.intrinsics.undefined && childrenValue !== this.realm.intrinsics.null) {
if (childrenValue instanceof _index.ArrayValue) {
let childrenLength = (0, _utils.getProperty)(this.realm, childrenValue, "length");
let childrenLengthValue = 0;
if (childrenLength instanceof _index.NumberValue) {
childrenLengthValue = childrenLength.value;
for (let i = 0; i < childrenLengthValue; i++) {
let child = (0, _utils.getProperty)(this.realm, childrenValue, "" + i);
if (child instanceof _index.Value) {
reactElement.children.push(this._serializeReactElementChild(child, reactElement));
} else {
this.logger.logError(value, `ReactElement "props.children[${i}]" failed to serialize due to a non-value`);
}
}
}
} else {
reactElement.children.push(this._serializeReactElementChild(childrenValue, reactElement));
}
}
}
}
serializeReactElement(val) {
let reactElement = this._createReactElement(val);
this._serializeReactElementType(reactElement);
this._serializeReactElementAttributes(reactElement);
this._serializeReactElementChildren(reactElement);
return this._emitReactElement(reactElement);
}
_addSerializedValueToJSXAttriutes(prop, expr, attributes) {
if (prop === null) {
attributes.push(t.jSXSpreadAttribute(expr));
} else {
attributes.push((0, _jsx.convertKeyValueToJSXAttribute)(prop, expr));
}
}
_serializeReactElementToCreateElement(val, reactElement, createElementIdentifier) {
let { type, attributes, children } = reactElement;
let createElementArguments = [type];
// check if we need to add attributes
if (attributes.length !== 0) {
let astAttributes = [];
for (let attribute of attributes) {
let expr = attribute.expr;
if (attribute.type === "SPREAD") {
astAttributes.push(t.spreadProperty(expr));
} else if (attribute.type === "PROPERTY") {
let attributeKey = attribute.key;
let key;
(0, _invariant2.default)(typeof attributeKey === "string");
if (attributeKey.includes("-")) {
key = t.stringLiteral(attributeKey);
} else {
key = t.identifier(attributeKey);
}
astAttributes.push(t.objectProperty(key, expr));
}
}
createElementArguments.push(t.objectExpression(astAttributes));
}
if (children.length !== 0) {
if (attributes.length === 0) {
createElementArguments.push(t.nullLiteral());
}
let astChildren = [];
for (let child of children) {
let expr = child.expr;
if (child.type === "NORMAL") {
astChildren.push(expr);
}
}
createElementArguments.push(...astChildren);
}
// cast to any for createElementArguments as casting it to BabelNodeExpresion[] isn't working
let createElementCall = t.callExpression(createElementIdentifier, createElementArguments);
this._addBailOutMessageToBabelNode(val, createElementCall);
return createElementCall;
}
_serializeReactElementToJSXElement(val, reactElement) {
let { type, attributes, children } = reactElement;
let jsxTypeIdentifer = (0, _jsx.convertExpressionToJSXIdentifier)(type, true);
let astAttributes = [];
for (let attribute of attributes) {
let expr = attribute.expr;
if (attribute.type === "SPREAD") {
astAttributes.push(t.jSXSpreadAttribute(expr));
} else if (attribute.type === "PROPERTY") {
let attributeKey = attribute.key;
(0, _invariant2.default)(typeof attributeKey === "string");
astAttributes.push((0, _jsx.convertKeyValueToJSXAttribute)(attributeKey, expr));
}
}
let astChildren = [];
for (let child of children) {
let expr = child.expr;
if (child.type === "NORMAL") {
if (t.isStringLiteral(expr) || t.isNumericLiteral(expr)) {
astChildren.push(t.jSXText(expr.value + ""));
} else if (t.isJSXElement(expr)) {
astChildren.push(expr);
} else {
astChildren.push(t.jSXExpressionContainer(expr));
}
}
}
let openingElement = t.jSXOpeningElement(jsxTypeIdentifer, astAttributes, astChildren.length === 0);
let closingElement = t.jSXClosingElement(jsxTypeIdentifer);
let jsxElement = t.jSXElement(openingElement, closingElement, astChildren, astChildren.length === 0);
this._addBailOutMessageToBabelNode(val, jsxElement);
return jsxElement;
}
_addBailOutMessageToBabelNode(val, node) {
// if there has been a bail-out, we create an inline BlockComment node before the JSX element
if (val.$BailOutReason !== undefined) {
// $BailOutReason contains an optional string of what to print out in the comment
node.leadingComments = [{ type: "BlockComment", value: `${val.$BailOutReason}` }];
}
}
_serializeReactElementChild(child, reactElement) {
let reactElementChild = this._createReactElementChild();
this._serializeNowOrAfterWaitingForDependencies(child, reactElement, () => {
let expr = this.residualHeapSerializer.serializeValue(child);
reactElementChild.expr = expr;
reactElementChild.type = "NORMAL";
});
return reactElementChild;
}
serializeLazyHoistedNodes() {
const entries = [];
if (this._lazilyHoistedNodes !== undefined) {
let { id, nodes, createElementIdentifier } = this._lazilyHoistedNodes;
// create a function that initializes all the hoisted nodes
let func = t.functionExpression(null,
// use createElementIdentifier if it's not null
createElementIdentifier ? [createElementIdentifier] : [], t.blockStatement(nodes.map(node => t.expressionStatement(t.assignmentExpression("=", node.id, node.astNode)))));
// push it to the mainBody of the module
entries.push(t.variableDeclaration("var", [t.variableDeclarator(id, func)]));
// output all the empty variable declarations that will hold the nodes lazily
entries.push(...nodes.map(node => t.variableDeclaration("var", [t.variableDeclarator(node.id)])));
// reset the _lazilyHoistedNodes so other additional functions work
this._lazilyHoistedNodes = undefined;
}
return entries;
}
}
exports.ResidualReactElementSerializer = ResidualReactElementSerializer;
//# sourceMappingURL=ResidualReactElementSerializer.js.map