UNPKG

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