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