UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

329 lines (259 loc) 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getReplacement = getReplacement; exports.isPure = isPure; exports.ResidualFunctionInstantiator = void 0; var t = _interopRequireWildcard(require("@babel/types")); var _jsx = require("../react/jsx"); var _babelhelpers = require("../utils/babelhelpers"); 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. */ function canShareFunctionBody(duplicateFunctionInfo) { if (duplicateFunctionInfo.anyContainingAdditionalFunction) { // If the function is referenced by an optimized function, // it may get emitted within that optimized function, // and then the function name is not generally available in arbitrary other code // where we'd like to replace the body with a reference to the extracted function body. // TODO: Revisit interplay of factory function concept, scope concept, and optimized functions. return false; } // Only share function when: // 1. it does not access any free variables. // 2. it does not use "this". const { unbound, modified, usesThis } = duplicateFunctionInfo.functionInfo; return unbound.size === 0 && modified.size === 0 && !usesThis; } function getReplacement(node, value) { let truthiness; if (value !== undefined) if (!value.mightNotBeFalse()) truthiness = false;else if (!value.mightNotBeTrue()) truthiness = true; return { node, truthiness }; } function isPure(node) { switch (node.type) { case "NullLiteral": case "BooleanLiteral": case "StringLiteral": case "NumericLiteral": return true; case "UnaryExpression": case "SpreadElement": let unaryExpression = node; return isPure(unaryExpression.argument); case "BinaryExpression": case "LogicalExpression": let binaryExpression = node; return isPure(binaryExpression.left) && isPure(binaryExpression.right); default: return false; } } // This class instantiates residual functions by replacing certain nodes, // i.e. bindings to captured scopes that need to get renamed to variable ids. // The original nodes are never mutated; instead, nodes are cloned as needed. // Along the way, some trivial code optimizations are performed as well. class ResidualFunctionInstantiator { constructor(factoryFunctionInfos, identifierReplacements, callReplacements, root) { this.factoryFunctionInfos = factoryFunctionInfos; this.identifierReplacements = identifierReplacements; this.callReplacements = callReplacements; this.root = root; } instantiate() { return this._replace(this.root); } _getLiteralTruthiness(node) { switch (node.type) { case "BooleanLiteral": case "NumericLiteral": case "StringLiteral": return !!node.value; case "Identifier": { let replacement = this.identifierReplacements.get(node); if (replacement !== undefined) return replacement.truthiness; return undefined; } case "CallExpression": { let replacement = this.callReplacements.get(node); if (replacement !== undefined) return replacement.truthiness; return undefined; } case "FunctionExpression": case "ArrowFunctionExpression": case "RegExpLiteral": return true; case "ClassExpression": let classExpression = node; return classExpression.superClass === null && classExpression.body.body.length === 0 ? true : undefined; case "ObjectExpression": let objectExpression = node; return objectExpression.properties.every(property => isPure(property.key) && isPure(property.value)) ? true : undefined; case "ArrayExpression": let arrayExpression = node; return arrayExpression.elements.every(element => element === undefined || isPure(element)) ? true : undefined; case "NullLiteral": return false; case "UnaryExpression": let unaryExpression = node; return unaryExpression.operator === "void" && isPure(unaryExpression.argument) ? false : undefined; default: return undefined; } } _replaceIdentifier(node) { let replacement = this.identifierReplacements.get(node); if (replacement !== undefined) return replacement.node; return node; // nothing else to replace in an identifier } _replaceJSXIdentifier(node) { let replacement = this.identifierReplacements.get(node); if (replacement !== undefined) return (0, _jsx.convertExpressionToJSXIdentifier)(replacement.node, true); return node; // nothing else to replace in an identifier } _replaceLabeledStatement(node) { // intentionally ignore embedded identifier let newBody = this._replace(node.body); if (newBody !== node.body) { let res = Object.assign({}, node); res.body = newBody; return res; } return node; // nothing else to replace in a labeled statement } _replaceCallExpression(node) { let replacement = this.callReplacements.get(node); if (replacement !== undefined) return replacement.node; return this._replaceFallback(node); } _replaceFunctionExpression(node) { // Our goal is replacing duplicate nested function so skip root residual function itself. if (this.root !== node) { const functionExpression = node; const functionTag = functionExpression.body.uniqueOrderedTag; // Un-interpreted nested function? if (functionTag !== undefined) { // Un-interpreted nested function. const duplicateFunctionInfo = this.factoryFunctionInfos.get(functionTag); if (duplicateFunctionInfo && canShareFunctionBody(duplicateFunctionInfo)) { const { factoryId } = duplicateFunctionInfo; return t.callExpression(t.memberExpression(factoryId, t.identifier("bind")), [_babelhelpers.nullExpression]); } } } return this._replaceFallback(node); } _replaceIfStatement(node) { let testTruthiness = this._getLiteralTruthiness(node.test); if (testTruthiness === true) { // Strictly speaking this is not safe: Annex B.3.4 allows FunctionDeclarations as the body of IfStatements in sloppy mode, // which have weird hoisting behavior: `console.log(typeof f); if (true) function f(){} console.log(typeof f)` will print 'undefined', 'function', but // `console.log(typeof f); function f(){} console.log(typeof f)` will print 'function', 'function'. // However, Babylon can't parse these, so it doesn't come up. return this._replace(node.consequent); } else if (testTruthiness === false) { if (node.alternate !== null) { return this._replace(node.alternate); } else { return t.emptyStatement(); } } return this._replaceFallback(node); } _replaceConditionalExpression(node) { let testTruthiness = this._getLiteralTruthiness(node.test); if (testTruthiness !== undefined) { return testTruthiness ? this._replace(node.consequent) : this._replace(node.alternate); } return this._replaceFallback(node); } _replaceLogicalExpression(node) { let leftTruthiness = this._getLiteralTruthiness(node.left); if (node.operator === "&&" && leftTruthiness !== undefined) { return leftTruthiness ? this._replace(node.right) : this._replace(node.left); } else if (node.operator === "||" && leftTruthiness !== undefined) { return leftTruthiness ? this._replace(node.left) : this._replace(node.right); } return this._replaceFallback(node); } _replaceWhileStatement(node) { let testTruthiness = this._getLiteralTruthiness(node.test); if (testTruthiness === false) { return t.emptyStatement(); } return this._replaceFallback(node); } _replaceFallback(node) { let newNode; for (let key in node) { let subNode = node[key]; if (!subNode) continue; let newSubNode; if (Array.isArray(subNode)) { let newArray; for (let i = 0; i < subNode.length; i++) { let elementNode = subNode[i]; if (!elementNode) continue; let newElementNode = this._replace(elementNode); if (newElementNode !== elementNode) { if (newArray === undefined) newArray = subNode.slice(0); newArray[i] = newElementNode; } } if (newArray === undefined) continue; newSubNode = newArray; } else if (subNode.type) { newSubNode = this._replace(subNode); if (newSubNode === subNode) continue; } else continue; if (newNode === undefined) newNode = Object.assign({}, node); newNode[key] = newSubNode; } return newNode || node; } _replace(node) { switch (node.type) { case "Identifier": return this._replaceIdentifier(node); case "LabeledStatement": return this._replaceLabeledStatement(node); case "BreakStatement": case "ContinueStatement": return node; case "JSXIdentifier": case "JSXMemberExpressions": return this._replaceJSXIdentifier(node); case "CallExpression": return this._replaceCallExpression(node); case "FunctionExpression": return this._replaceFunctionExpression(node); case "IfStatement": return this._replaceIfStatement(node); case "ConditionalExpression": return this._replaceConditionalExpression(node); case "LogicalExpression": return this._replaceLogicalExpression(node); case "WhileStatement": return this._replaceWhileStatement(node); default: return this._replaceFallback(node); } } } exports.ResidualFunctionInstantiator = ResidualFunctionInstantiator; //# sourceMappingURL=ResidualFunctionInstantiator.js.map