prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
408 lines (377 loc) • 15.8 kB
JavaScript
;
var _errors = require("./errors.js");
var _options = require("./options.js");
require("./serializer/types.js");
var _prepackNode = require("./prepack-node.js");
var _fs = require("fs");
var _fs2 = _interopRequireDefault(_fs);
var _v = require("v8");
var _v2 = _interopRequireDefault(_v);
var _package = require("../package.json");
var _invariant = require("./invariant");
var _invariant2 = _interopRequireDefault(_invariant);
var _nodeZip = require("node-zip");
var _nodeZip2 = _interopRequireDefault(_nodeZip);
var _path = require("path");
var _path2 = _interopRequireDefault(_path);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Prepack helper
/**
* 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.
*/
/* eslint-disable no-shadow */
function run(Object, Array, console, JSON, process, prepackStdin, prepackFileSync, FatalError, CompatibilityValues, fs) {
let HELP_STR = `
input The name of the file to run Prepack over (for web please provide the single js bundle file)
--out The name of the output file
--compatibility The target environment for Prepack [${CompatibilityValues.map(v => `"${v}"`).join(", ")}]
--mathRandomSeed If you want Prepack to evaluate Math.random() calls, please provide a seed.
--srcmapIn The input sourcemap filename. If present, Prepack will output a sourcemap that maps from
the original file (pre-input sourcemap) to Prepack's output
--srcmapOut The output sourcemap filename.
--maxStackDepth Specify the maximum call stack depth.
--timeout The amount of time in seconds until Prepack should time out.
--abstractEffectsInAdditionalFunctions Experimental flag to allow abstract effectful function calls.
--lazyObjectsRuntime Enable lazy objects feature and specify the JS runtime that support this feature.
--debugNames Changes the output of Prepack so that for named functions and variables that get emitted into
Prepack's output, the original name is appended as a suffix to Prepack's generated identifier.
--speculate Enable speculative initialization of modules (for the module system Prepack has builtin
knowledge about). Prepack will try to execute all factory functions it is able to.
--trace Traces the order of module initialization.
--serialize Serializes the partially evaluated global environment as a program that recreates it.
(default = true)
--check [start[, count]] Check residual functions for diagnostic messages. Do not serialize or produce residual code.
--residual Produces the residual program that results after constant folding.
--profile Enables console logging of profile information of different phases of prepack.
--statsFile The name of the output file where statistics will be written to.
--heapGraphFilePath The name of the output file where heap graph will be written to.
--inlineExpressions When generating code, tells prepack to avoid naming expressions when they are only used once,
and instead inline them where they are used.
--simpleClosures When generating code, tells prepack to not defer initializing closures
--omitInvariants When generating code, tells prepack to omit writing invariants. (Invariants generated by default.)
--emitConcreteModel Synthesize concrete model values for abstract models(defined by __assumeDataProperty).
--version Output the version number.
--repro Create a zip file with all information needed to reproduce a Prepack run"
`;
let args = Array.from(process.argv);
args.splice(0, 2);
let inputFilenames = [];
let outputFilename;
let check;
let compatibility;
let mathRandomSeed;
let inputSourceMap;
let outputSourceMap;
let statsFileName;
let maxStackDepth;
let timeout;
let debugIdentifiers;
let lazyObjectsRuntime;
let heapGraphFilePath;
let debugInFilePath;
let debugOutFilePath;
let reactOutput = "create-element";
let reproFilePath;
let flags = {
initializeMoreModules: false,
trace: false,
debugNames: false,
omitInvariants: false,
emitConcreteModel: false,
inlineExpressions: false,
simpleClosures: false,
abstractEffectsInAdditionalFunctions: false,
logStatistics: false,
logModules: false,
delayInitializations: false,
delayUnsupportedRequires: false,
accelerateUnsupportedRequires: true,
internalDebug: false,
debugScopes: false,
serialize: false,
residual: false,
profile: false,
reactEnabled: false
};
let reproArguments = [];
let reproFileNames = [];
let inputFile = fileName => {
reproFileNames.push(fileName);
return _path2.default.basename(fileName);
};
while (args.length) {
let arg = args.shift();
if (!arg.startsWith("--")) {
inputFilenames.push(arg);
reproArguments.push(inputFile(arg));
} else {
arg = arg.slice(2);
switch (arg) {
case "out":
arg = args.shift();
outputFilename = arg;
// skip for repro purposes
break;
case "compatibility":
arg = args.shift();
if (!CompatibilityValues.includes(arg)) {
console.error(`Unsupported compatibility: ${arg}`);
process.exit(1);
}
compatibility = arg;
reproArguments.push("--compatibility", compatibility);
break;
case "mathRandomSeed":
mathRandomSeed = args.shift();
reproArguments.push("--mathRandomSeed", mathRandomSeed);
break;
case "srcmapIn":
inputSourceMap = args.shift();
reproArguments.push("--srcmapIn", inputFile(inputSourceMap));
break;
case "srcmapOut":
outputSourceMap = args.shift();
// skip for repro purposes
break;
case "statsFile":
statsFileName = args.shift();
// skip for repro purposes
break;
case "maxStackDepth":
let value = args.shift();
if (isNaN(value)) {
console.error("Stack depth value must be a number");
process.exit(1);
}
maxStackDepth = parseInt(value, 10);
reproArguments.push("--maxStackDepth", maxStackDepth.toString());
break;
case "timeout":
let seconds = args.shift();
if (isNaN(seconds)) {
console.error("Timeout must be a number");
process.exit(1);
}
timeout = parseInt(seconds, 10) * 1000;
reproArguments.push("--timeout", timeout.toString());
break;
case "debugIdentifiers":
let debugIdentifiersString = args.shift();
debugIdentifiers = debugIdentifiersString.split(",");
reproArguments.push("--debugIdentifiers", debugIdentifiersString);
break;
case "check":
let range = args.shift();
if (range.startsWith("--")) {
args.unshift(range);
range = "0";
}
let pair = range.split(",");
if (pair.length === 1) pair.push(Number.MAX_SAFE_INTEGER);
let start = +pair[0];
if (start < 0 || !Number.isInteger(start)) {
console.error("check start offset must be a number");
process.exit(1);
}
let count = +pair[1];
if (count < 0 || !Number.isInteger(count)) {
console.error("check count must be a number");
process.exit(1);
}
check = [start, count];
reproArguments.push("--check", range);
break;
case "debugInFilePath":
debugInFilePath = args.shift();
// skip for repro purposes
break;
case "debugOutFilePath":
debugOutFilePath = args.shift();
// skip for repro purposes
break;
case "lazyObjectsRuntime":
lazyObjectsRuntime = args.shift();
reproArguments.push("--lazyObjectsRuntime", lazyObjectsRuntime);
break;
case "heapGraphFilePath":
heapGraphFilePath = args.shift();
// skip for repro purposes
break;
case "reactOutput":
arg = args.shift();
if (!_options.ReactOutputValues.includes(arg)) {
console.error(`Unsupported reactOutput: ${arg}`);
process.exit(1);
}
reactOutput = arg;
reproArguments.push("--reactOutput", reactOutput);
break;
case "repro":
reproFilePath = args.shift();
// skip for repro purposes
break;
case "help":
const options = ["-- | input.js", "--out output.js", "--compatibility jsc", "--mathRandomSeed seedvalue", "--srcmapIn inputMap", "--srcmapOut outputMap", "--maxStackDepth depthValue", "--timeout seconds", "--debugIdentifiers id1,id2,...", "--check [start[, number]]", "--lazyObjectsRuntime lazyObjectsRuntimeName", "--heapGraphFilePath heapGraphFilePath", "--reactOutput " + _options.ReactOutputValues.join(" | "), "--repro reprofile.zip"];
for (let flag of Object.keys(flags)) options.push(`--${flag}`);
console.log("Usage: prepack.js " + options.map(option => `[ ${option} ]`).join(" ") + "\n" + HELP_STR);
return;
case "version":
console.log(_package.version);
return;
default:
if (arg in flags) {
flags[arg] = true;
reproArguments.push("--" + arg);
} else {
console.error(`Unknown option: ${arg}`);
process.exit(1);
}
}
}
}
if (reproFilePath !== undefined) {
const zip = (0, _nodeZip2.default)();
for (let fileName of reproFileNames) {
let content = fs.readFileSync(fileName, "utf8");
zip.file(_path2.default.basename(fileName), content);
}
zip.file("repro.sh", `#!/bin/bash
if [ -z "$PREPACK" ]; then
echo "Set environment variable PREPACK to bin/prepack.js in your Prepack directory."
else
node "$PREPACK" ${reproArguments.map(a => `"${a}"`).join(" ")}
fi
`);
const data = zip.generate({ base64: false, compression: "DEFLATE" });
fs.writeFileSync(reproFilePath, data, "binary");
}
if (!flags.serialize && !flags.residual) flags.serialize = true;
if (check) {
flags.serialize = false;
flags.residual = false;
}
let resolvedOptions = Object.assign({}, {
compatibility,
mathRandomSeed,
inputSourceMapFilename: inputSourceMap,
errorHandler,
sourceMaps: !!outputSourceMap,
maxStackDepth,
timeout,
debugIdentifiers,
check,
lazyObjectsRuntime,
debugInFilePath,
debugOutFilePath,
reactOutput
}, flags);
if (heapGraphFilePath) resolvedOptions.heapGraphFormat = "DotLanguage";
if (lazyObjectsRuntime && (resolvedOptions.delayInitializations || resolvedOptions.inlineExpressions)) {
console.error("lazy objects feature is incompatible with delayInitializations and inlineExpressions options");
process.exit(1);
}
let errors = new Map();
let errorList = [];
function errorHandler(diagnostic) {
if (diagnostic.location) errors.set(diagnostic.location, diagnostic);else errorList.push(diagnostic);
return "Recover";
}
function printDiagnostics() {
let foundFatal = false;
if (errors.size > 0 || errorList.length > 0) {
console.error("Errors found while prepacking");
let printError = (error, locString = "At an unknown location") => {
foundFatal = foundFatal || error.severity === "FatalError";
console.error(`${locString} ${error.severity} ${error.errorCode}: ${error.message}` + ` (https://github.com/facebook/prepack/wiki/${error.errorCode})`);
let callStack = error.callStack;
if (callStack !== undefined) {
let eolPos = callStack.indexOf("\n");
if (eolPos > 0) console.error(callStack.substring(eolPos + 1));
}
};
for (let [loc, error] of errors) {
let sourceMessage = "";
switch (loc.source) {
case null:
case "":
sourceMessage = "In an unknown source file";
break;
case "no-filename-specified":
sourceMessage = "In stdin";
break;
default:
(0, _invariant2.default)(loc !== null && loc.source !== null);
sourceMessage = `In input file ${loc.source}`;
break;
}
let locString = `${sourceMessage}(${loc.start.line}:${loc.start.column + 1})`;
printError(error, locString);
}
for (let error of errorList) printError(error);
}
return foundFatal;
}
try {
if (inputFilenames.length === 0) {
prepackStdin(resolvedOptions, processSerializedCode, printDiagnostics);
return;
}
let serialized = prepackFileSync(inputFilenames, resolvedOptions);
printDiagnostics();
if (resolvedOptions.serialize && serialized) processSerializedCode(serialized);
} catch (err) {
printDiagnostics();
//FatalErrors must have generated at least one CompilerDiagnostic.
if (err instanceof FatalError) {
(0, _invariant2.default)(errors.size > 0 || errorList.length > 0, "FatalError must generate at least one CompilerDiagnostic");
} else {
// if it is not a FatalError, it means prepack failed, and we should display the Prepack stack trace.
console.error(err.stack);
process.exit(1);
}
}
function processSerializedCode(serialized) {
if (serialized.code === "") {
console.error("Prepack returned empty code.");
return;
}
if (outputFilename) {
console.log(`Prepacked source code written to ${outputFilename}.`);
fs.writeFileSync(outputFilename, serialized.code);
} else {
console.log(serialized.code);
}
if (statsFileName) {
if (serialized.statistics === undefined || serialized.timingStats === undefined) {
return;
}
let stats = {
SerializerStatistics: serialized.statistics,
TimingStatistics: serialized.timingStats,
MemoryStatistics: _v2.default.getHeapStatistics()
};
fs.writeFileSync(statsFileName, JSON.stringify(stats));
}
if (outputSourceMap) {
fs.writeFileSync(outputSourceMap, serialized.map ? JSON.stringify(serialized.map) : "");
}
if (heapGraphFilePath) {
(0, _invariant2.default)(serialized.heapGraph);
fs.writeFileSync(heapGraphFilePath, serialized.heapGraph);
}
}
return true;
}
if (typeof __residual === "function") {
// If we're running inside of Prepack. This is the residual function we'll
// want to leave untouched in the final program.
__residual("boolean", run, Object, Array, console, JSON, process, _prepackNode.prepackStdin, _prepackNode.prepackFileSync, _errors.FatalError, _options.CompatibilityValues, _fs2.default);
} else {
run(Object, Array, console, JSON, process, _prepackNode.prepackStdin, _prepackNode.prepackFileSync, _errors.FatalError, _options.CompatibilityValues, _fs2.default);
}
//# sourceMappingURL=prepack-cli.js.map