prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
300 lines (245 loc) • 9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.factorifyObjects = factorifyObjects;
var t = _interopRequireWildcard(require("@babel/types"));
var _NameGenerator = require("../utils/NameGenerator");
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.
*/
/* strict-local */
function isLiteral(node) {
switch (node.type) {
case "NullLiteral":
case "BooleanLiteral":
case "StringLiteral":
case "NumericLiteral":
return true;
case "UnaryExpression":
return node.operator === "void" && isLiteral(node.argument);
default:
return false;
}
}
function isSameNode(left, right) {
let type = left.type;
if (type !== right.type) {
return false;
}
if (type === "Identifier") {
return left.name === right.name;
}
if (type === "NullLiteral") {
return true;
}
if (type === "BooleanLiteral" || type === "StringLiteral" || type === "NumericLiteral") {
return Object.is(left.value, right.value);
}
if (type === "UnaryExpression") {
return left.operator === "void" && right.operator === "void" && isLiteral(left.argument) && isLiteral(right.argument);
}
return false;
}
function getObjectKeys(obj) {
let keys = [];
for (let prop of obj.properties) {
if (prop.type !== "ObjectProperty") return false;
let key = prop.key;
if (key.type === "StringLiteral") {
keys.push(key.value);
} else if (key.type === "Identifier") {
if (prop.computed) return false;
keys.push(key.name);
} else {
return false;
}
}
for (let key of keys) {
if (key.indexOf("|") >= 0) return false;
}
return keys.join("|");
} // This function looks for recurring initialization patterns in the code of the form
// var x = { a: literal1, b: literal2 }
// var y = { a: literal1, b: literal3 }
// and transforms them into something like
// function factory(b) { return { a: literal1, b } }
// var x = factory(literal2);
// var y = factory(literal3);
// TODO #884: Right now, the visitor below only looks into top-level variable declaration
// with a flat object literal initializer.
// It should also look into conditional control flow, residual functions, and nested object literals.
function factorifyObjects(body, factoryNameGenerator) {
let signatures = Object.create(null);
for (let node of body) {
switch (node.type) {
case "VariableDeclaration":
for (let declar of node.declarations) {
let {
init
} = declar;
if (!init) continue;
if (init.type !== "ObjectExpression") continue;
let keys = getObjectKeys(init);
if (!keys) continue;
let initializerAstNodeName = "init";
let declars = signatures[keys] = signatures[keys] || [];
declars.push({
declar,
initializerAstNodeName
});
}
break;
case "ExpressionStatement":
const expr = node.expression;
if (expr.type !== "AssignmentExpression") {
break;
}
const {
right
} = expr;
if (right.type !== "ObjectExpression") {
break;
}
let keys = getObjectKeys(right);
if (!keys) continue;
let initializerAstNodeName = "right";
let declars = signatures[keys] = signatures[keys] || [];
declars.push({
declar: node.expression,
initializerAstNodeName
});
break;
default:
// Continue to next node.
break;
}
}
for (let signatureKey in signatures) {
let declars = signatures[signatureKey];
if (declars.length < 5) continue;
let keys = signatureKey.split("|");
let rootFactoryParams = [];
let rootFactoryProps = [];
for (let keyIndex = 0; keyIndex < keys.length; keyIndex++) {
let key = keys[keyIndex];
let id = t.identifier(`__${keyIndex}`);
rootFactoryParams.push(id);
let keyNode = t.isValidIdentifier(key) ? t.identifier(key) : t.stringLiteral(key);
rootFactoryProps.push(t.objectProperty(keyNode, id));
}
let rootFactoryId = t.identifier(factoryNameGenerator.generate("root"));
let rootFactoryBody = t.blockStatement([t.returnStatement(t.objectExpression(rootFactoryProps))]);
let rootFactory = t.functionDeclaration(rootFactoryId, rootFactoryParams, rootFactoryBody);
body.unshift(rootFactory);
for (let _ref of declars) {
let {
declar,
initializerAstNodeName
} = _ref;
let args = [];
for (let prop of declar[initializerAstNodeName].properties) {
args.push(prop.value);
}
declar[initializerAstNodeName] = t.callExpression(rootFactoryId, args);
}
let seen = new Set();
for (let _ref2 of declars) {
let {
declar,
initializerAstNodeName
} = _ref2;
if (seen.has(declar)) continue; // build up a map containing the arguments that are shared
let common = [];
let mostSharedArgsLength = 0;
for (let _ref3 of declars) {
let {
declar: declar2,
initializerAstNodeName: initializerAstNodeName2
} = _ref3;
if (seen.has(declar2)) continue;
if (declar === declar2) continue;
let sharedArgs = [];
for (let i = 0; i < keys.length; i++) {
if (isSameNode(declar[initializerAstNodeName].arguments[i], declar2[initializerAstNodeName2].arguments[i])) {
sharedArgs.push(i);
}
}
if (!sharedArgs.length) continue;
mostSharedArgsLength = Math.max(mostSharedArgsLength, sharedArgs.length);
common.push({
declar: declar2,
initializerAstNodeName: initializerAstNodeName2,
sharedArgs
});
} // build up a mapping of the argument positions that are shared so we can pick the top one
let sharedPairs = Object.create(null);
for (let _ref4 of common) {
let {
declar: declar2,
initializerAstNodeName: initializerAstNodeName2,
sharedArgs
} = _ref4;
if (sharedArgs.length === mostSharedArgsLength) {
sharedArgs = sharedArgs.join(",");
let pair = sharedPairs[sharedArgs] = sharedPairs[sharedArgs] || [{
declar,
initializerAstNodeName
}];
pair.push({
declar: declar2,
initializerAstNodeName: initializerAstNodeName2
});
}
} // get the highest pair
let highestPairArgs;
let highestPairCount;
for (let pairArgs in sharedPairs) {
let pair = sharedPairs[pairArgs];
if (highestPairArgs === undefined || pair.length > highestPairCount) {
highestPairCount = pair.length;
highestPairArgs = pairArgs;
}
}
if (highestPairArgs === undefined) continue;
let declarsSub = sharedPairs[highestPairArgs];
let removeArgs = highestPairArgs.split(",");
let subFactoryArgs = [];
let subFactoryParams = [];
let sharedArgs = declar[initializerAstNodeName].arguments;
for (let i = 0; i < sharedArgs.length; i++) {
let arg = sharedArgs[i];
if (removeArgs.indexOf(i + "") >= 0) {
subFactoryArgs.push(arg);
} else {
let id = t.identifier(`__${i}`);
subFactoryArgs.push(id);
subFactoryParams.push(id);
}
}
let subFactoryId = t.identifier(factoryNameGenerator.generate("sub"));
let subFactoryBody = t.blockStatement([t.returnStatement(t.callExpression(rootFactoryId, subFactoryArgs))]);
let subFactory = t.functionDeclaration(subFactoryId, subFactoryParams, subFactoryBody);
body.unshift(subFactory);
for (let _ref5 of declarsSub) {
let {
declar: declar2,
initializerAstNodeName: initializerAstNodeName2
} = _ref5;
seen.add(declar2);
let call = declar2[initializerAstNodeName2];
call.callee = subFactoryId;
call.arguments = call.arguments.filter(function (val, i) {
return removeArgs.indexOf(i + "") < 0;
});
}
}
}
}
//# sourceMappingURL=factorify.js.map