prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
481 lines (374 loc) • 18.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResidualReactElementSerializer = void 0;
var _realm = require("../realm.js");
var _ResidualHeapSerializer = require("./ResidualHeapSerializer.js");
var _hoisting = require("../react/hoisting.js");
var t = _interopRequireWildcard(require("@babel/types"));
var _index = require("../values/index.js");
var _jsx = require("../react/jsx.js");
var _logger = require("../utils/logger.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
var _errors = require("../errors.js");
var _elements = require("../react/elements.js");
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)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { 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, residualOptimizedFunctions) {
this.realm = realm;
this.residualHeapSerializer = residualHeapSerializer;
this.logger = residualHeapSerializer.logger;
this.reactOutput = realm.react.output || "create-element";
this._lazilyHoistedNodes = new Map();
this._residualOptimizedFunctions = residualOptimizedFunctions;
}
_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(reactElement, id, reactElementAst, hoistedCreateElementIdentifier, originalCreateElementIdentifier) {
// if the currentHoistedReactElements is not defined, we create it an emit the function call
// this should only occur once per additional function
const optimizedFunction = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(reactElement);
(0, _invariant.default)(optimizedFunction);
let lazilyHoistedNodes = this._lazilyHoistedNodes.get(optimizedFunction);
if (lazilyHoistedNodes === undefined) {
let funcId = t.identifier(this.residualHeapSerializer.functionNameGenerator.generate());
lazilyHoistedNodes = {
id: funcId,
createElementIdentifier: hoistedCreateElementIdentifier,
nodes: []
};
this._lazilyHoistedNodes.set(optimizedFunction, lazilyHoistedNodes);
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.getPrelude(optimizedFunction).push(statement);
} // we then push the reactElement and its id into our list of elements to process after
// the current additional function has serialzied
lazilyHoistedNodes.nodes.push({
id,
astNode: reactElementAst
});
}
_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 typeValue = (0, _utils.getProperty)(this.realm, value, "type");
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");
let shouldHoist = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(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;
let dependencies = [typeValue, keyValue, refValue, propsValue, value];
let createElement;
if (this.reactOutput === "create-element") {
createElement = this._getReactCreateElementValue();
dependencies.push(createElement);
}
this.residualHeapSerializer.emitter.emitNowOrAfterWaitingForDependencies(dependencies, () => {
if (this.reactOutput === "jsx") {
reactElementAstNode = this._serializeReactElementToJSXElement(value, reactElement);
} else if (this.reactOutput === "create-element") {
originalCreateElementIdentifier = this.residualHeapSerializer.serializeValue(createElement);
if (shouldHoist) {
const optimizedFunction = this._residualOptimizedFunctions.tryGetOptimizedFunctionRoot(value);
(0, _invariant.default)(optimizedFunction);
const lazilyHoistedNodes = this._lazilyHoistedNodes.get(optimizedFunction); // if we haven't created a lazilyHoistedNodes before, then this is the first time
// so we only create the hoisted identifier once
if (lazilyHoistedNodes === undefined) {
// create a new unique instance
hoistedCreateElementIdentifier = t.identifier(this.residualHeapSerializer.intrinsicNameGenerator.generate());
} else {
hoistedCreateElementIdentifier = lazilyHoistedNodes.createElementIdentifier;
}
}
let createElementIdentifier = shouldHoist ? hoistedCreateElementIdentifier : originalCreateElementIdentifier;
reactElementAstNode = this._serializeReactElementToCreateElement(value, reactElement, createElementIdentifier);
} else {
(0, _invariant.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(value, id, reactElementAstNode, hoistedCreateElementIdentifier, originalCreateElementIdentifier);
} else {
// Note: it can be expected that we assign to the same variable multiple times
// this is due to fact ReactElements are immutable objects and the fact that
// when we inline/fold logic, the same ReactElements are referenced at different
// points with different attributes. Given we can't mutate an immutable object,
// we instead create new objects and assign to the same binding
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)]));
}
}
}, this.residualHeapSerializer.emitter.getBody());
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);
}, this.residualHeapSerializer.emitter.getBody());
} else {
serialize();
}
}
_serializeReactFragmentType(typeValue) {
let reactLibraryObject = this._getReactLibraryValue();
return t.memberExpression(this.residualHeapSerializer.serializeValue(reactLibraryObject), t.identifier("Fragment"));
}
serializeReactElement(val) {
let reactElementData = this.realm.react.reactElements.get(val);
(0, _invariant.default)(reactElementData !== undefined);
let {
firstRenderOnly
} = reactElementData;
let reactElement = this._createReactElement(val);
(0, _elements.traverseReactElement)(this.realm, reactElement.value, {
visitType: typeValue => {
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); // Increment ref count one more time to ensure that this object will be assigned a unique id.
// Abstract values that are emitted as first argument to JSX elements needs a proper id.
this.residualHeapSerializer.residualHeapValueIdentifiers.incrementReferenceCount(typeValue);
}
reactElement.type = expr;
});
},
visitKey: keyValue => {
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);
},
visitRef: refValue => {
if (!firstRenderOnly) {
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);
}
},
visitAbstractOrPartialProps: propsValue => {
let reactElementSpread = this._createReactElementAttribute();
this._serializeNowOrAfterWaitingForDependencies(propsValue, reactElement, () => {
let expr = this.residualHeapSerializer.serializeValue(propsValue);
reactElementSpread.expr = expr;
reactElementSpread.type = "SPREAD";
});
reactElement.attributes.push(reactElementSpread);
},
visitConcreteProps: propsValue => {
for (let [propName, binding] of propsValue.properties) {
if (binding.descriptor === undefined || propName === "children") {
continue;
}
let propValue = (0, _utils.getProperty)(this.realm, propsValue, propName);
if ((0, _utils.canExcludeReactElementObjectProperty)(this.realm, val, propName, propValue)) {
continue;
}
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);
}
},
visitChildNode: childValue => {
reactElement.children.push(this._serializeReactElementChild(childValue, 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.spreadElement(expr));
} else if (attribute.type === "PROPERTY") {
let attributeKey = attribute.key;
let key;
(0, _invariant.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, _invariant.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(optimizedFunction) {
const entries = [];
const lazilyHoistedNodes = this._lazilyHoistedNodes.get(optimizedFunction);
if (lazilyHoistedNodes !== undefined) {
let {
id,
nodes,
createElementIdentifier
} = 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.delete(optimizedFunction);
}
return entries;
}
}
exports.ResidualReactElementSerializer = ResidualReactElementSerializer;
//# sourceMappingURL=ResidualReactElementSerializer.js.map