UNPKG

prepack

Version:

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

671 lines (543 loc) 27 kB
"use strict"; var _errors = require("./errors.js"); var _options = require("./options.js"); var _TextPrinter = require("./utils/TextPrinter.js"); var _prepackNode = require("./prepack-node.js"); var _fs = _interopRequireDefault(require("fs")); var _v = _interopRequireDefault(require("v8")); var _package = require("../package.json"); var _invariant = _interopRequireDefault(require("./invariant")); var _JSONTokenizer = _interopRequireDefault(require("./utils/JSONTokenizer.js")); var _DebugReproPackager = require("./utils/DebugReproPackager.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * 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. --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. --modulesToInitialize [ALL | comma separated list] Enable speculative initialization of modules (for the module system Prepack has builtin knowledge about). Prepack will try to execute the factory functions of the modules you specify. --trace Traces the order of module initialization. --check [start[, count]] Check residual functions for diagnostic messages. Do not generate code. --profile Collect statistics about time and memory usage of the different internal passes --logStatistics Log statistics to console --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. --dumpIRFilePath The name of the output file where the intermediate representation 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. --invariantLevel 0: no invariants (default); 1: checks for abstract values; 2: checks for accessed built-ins; 3: internal consistency --invariantMode Whether to throw an exception or call a console function to log an invariant violation; default = throw. --emitConcreteModel Synthesize concrete model values for abstract models(defined by __assumeDataProperty). --version Output the version number. --reproOnFatalError Create a zip file with all information needed to reproduce a Prepack run if Prepacking fails with a FatalError. --reproUnconditionally Create a zip file with all information needed to reproduce a Prepack run, regardless of success of Prepack. --cpuprofile Create a CPU profile file for the run that can be loaded into the Chrome JavaScript CPU Profile viewer. --debugDiagnosticSeverity FatalError | RecoverableError | Warning | Information (default = FatalError). Diagnostic level at which debugger will stop. --debugBuckRoot Root directory that buck assumes when creating sourcemap paths. --warnAsError Turns all warnings into errors. --diagnosticAsError A comma-separated list of non-fatal-error PPxxxx diagnostic codes that should get turned into (recoverable) errors. --noDiagnostic A comma-separated list of non-fatal-error PPxxxx diagnostic codes that should get suppressed. `; let args = Array.from(process.argv); args.splice(0, 2); let inputFilenames = []; let outputFilename; let check; let compatibility; let mathRandomSeed; let inputSourceMapFilenames = []; let outputSourceMap; let statsFileName; let maxStackDepth; let timeout; let debugIdentifiers; let lazyObjectsRuntime; let heapGraphFilePath; let dumpIRFilePath; let debugInFilePath; let debugOutFilePath; let reactOutput = "create-element"; let reproFilePath; let cpuprofilePath; let invariantMode; let invariantLevel; let reproMode; let debugReproPackager; // Indicates where to find a zip with prepack runtime. Used in environments where // the `yarn pack` strategy doesn't work. let externalPrepackPath; let diagnosticAsError; let noDiagnostic; let warnAsError; let modulesToInitialize; let flags = { trace: false, debugNames: false, emitConcreteModel: false, inlineExpressions: false, logStatistics: false, logModules: false, delayInitializations: false, internalDebug: false, debugScopes: false, profile: false, instantRender: false, reactEnabled: false }; let reproArguments = []; let reproFileNames = []; let debuggerConfigArgs = {}; let debugReproArgs; while (args.length) { let arg = args.shift(); if (!arg.startsWith("--")) { let inputs = arg.trim().split(/\s+/g); // Split on all whitespace for (let input of inputs) { inputFilenames.push(input); if (!input.includes(".map")) reproFileNames.push(input); // Don't include sourcemaps in reproFiles because they will be captured later on in prepack-node } } else { arg = arg.slice(2); switch (arg) { case "modulesToInitialize": let modulesString = args.shift().trim(); modulesToInitialize = modulesString === "ALL" ? modulesString : new Set(modulesString.split(",")); break; case "out": arg = args.shift(); outputFilename = arg; // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments 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": let inputSourceMap = args.shift(); inputSourceMapFilenames.push(inputSourceMap); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments // Furthermore, this is covered when sourcemaps are discovered in prepack-node break; case "srcmapOut": outputSourceMap = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments break; case "statsFile": statsFileName = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments 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 "diagnosticAsError": let diagnosticAsErrorString = args.shift(); diagnosticAsError = new Set(diagnosticAsErrorString.split(",")); reproArguments.push("--diagnosticAsError", diagnosticAsErrorString); break; case "noDiagnostic": let noDiagnosticString = args.shift(); noDiagnostic = new Set(noDiagnosticString.split(",")); reproArguments.push("--noDiagnostic", noDiagnosticString); break; case "warnAsError": warnAsError = true; reproArguments.push("--warnAsError"); 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(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally] as the field is not visable to the user break; case "debugOutFilePath": debugOutFilePath = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally] as the field is not visable to the user break; case "lazyObjectsRuntime": lazyObjectsRuntime = args.shift(); reproArguments.push("--lazyObjectsRuntime", lazyObjectsRuntime); break; case "heapGraphFilePath": heapGraphFilePath = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments break; case "dumpIRFilePath": dumpIRFilePath = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments 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 "reproOnFatalError": case "reproUnconditionally": debugReproPackager = new _DebugReproPackager.DebugReproPackager(); reproMode = arg; reproFilePath = args.shift(); debugReproArgs = {}; debugReproArgs.sourcemaps = []; if (debuggerConfigArgs.buckRoot) debugReproArgs.buckRoot = debuggerConfigArgs.buckRoot; // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as we don't need to create a repro from the repro... break; case "cpuprofile": cpuprofilePath = args.shift(); // do not include this in reproArguments needed by --repro[OnFatalError/Unconditionally], as path is likely not portable between environments break; case "invariantMode": arg = args.shift(); if (!_options.InvariantModeValues.includes(arg)) { console.error(`Unsupported invariantMode: ${arg}`); process.exit(1); } invariantMode = arg; reproArguments.push("--invariantMode", invariantMode); break; case "invariantLevel": let invariantLevelString = args.shift(); if (isNaN(invariantLevelString)) { console.error("invariantLevel must be a number"); process.exit(1); } invariantLevel = parseInt(invariantLevelString, 10); reproArguments.push("--invariantLevel", invariantLevel.toString()); break; case "debugDiagnosticSeverity": arg = args.shift(); if (!_options.DiagnosticSeverityValues.includes(arg)) { console.error(`Unsupported debugDiagnosticSeverity: ${arg}`); process.exit(1); } (0, _invariant.default)(arg === "FatalError" || arg === "RecoverableError" || arg === "Warning" || arg === "Information", `Invalid debugger diagnostic severity: ${arg}`); debuggerConfigArgs.diagnosticSeverity = arg; reproArguments.push("--debugDiagnosticSeverity", arg); break; case "debugBuckRoot": let buckRoot = args.shift(); debuggerConfigArgs.buckRoot = buckRoot; if (debugReproArgs) debugReproArgs.buckRoot = buckRoot; // Use $(pwd) instead of argument so repro script can be run from // any computer, not just the one it was generated on. // All sourcefiles are placed directly in the repro, so the repro folder is the buckRoot. reproArguments.push("--debugBuckRoot", "$(pwd)"); break; case "externalPrepackPath": externalPrepackPath = args.shift(); 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", "--dumpIRFilePath dumpIRFilePath", "--reactOutput " + _options.ReactOutputValues.join(" | "), "--repro reprofile.zip", "--cpuprofile name.cpuprofile", "--invariantMode " + _options.InvariantModeValues.join(" | "), "--warnAsError", "--diagnosticAsError PPxxxx,PPyyyy,...", "--noDiagnostic PPxxxx,PPyyyy,..."]; 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); } } } } let resolvedOptions = Object.assign({}, { compatibility, mathRandomSeed, inputSourceMapFilenames, errorHandler, sourceMaps: !!outputSourceMap, maxStackDepth, timeout, debugIdentifiers, check, serialize: !check, lazyObjectsRuntime, debugInFilePath, debugOutFilePath, reactOutput, invariantMode, invariantLevel, debuggerConfigArgs, debugReproArgs, modulesToInitialize }, flags); if (heapGraphFilePath !== undefined) resolvedOptions.heapGraphFormat = "DotLanguage"; if (dumpIRFilePath !== undefined) { resolvedOptions.onExecute = (realm, optimizedFunctions) => { let text = ""; new _TextPrinter.TextPrinter(line => { text += line + "\n"; }).print(realm, optimizedFunctions); (0, _invariant.default)(dumpIRFilePath !== undefined); fs.writeFileSync(dumpIRFilePath, text); }; } if (lazyObjectsRuntime !== undefined && (resolvedOptions.delayInitializations || resolvedOptions.inlineExpressions)) { console.error("lazy objects feature is incompatible with delayInitializations and inlineExpressions options"); process.exit(1); } let compilerDiagnostics = new Map(); let compilerDiagnosticsList = []; function errorHandler(compilerDiagnostic) { if (noDiagnostic !== undefined && noDiagnostic.has(compilerDiagnostic.errorCode)) return "Recover"; if (warnAsError && compilerDiagnostic.severity === "Warning" || diagnosticAsError !== undefined && diagnosticAsError.has(compilerDiagnostic.errorCode) && compilerDiagnostic.severity !== "FatalError") { compilerDiagnostic = new _errors.CompilerDiagnostic(compilerDiagnostic.message, compilerDiagnostic.location, compilerDiagnostic.errorCode, "RecoverableError", compilerDiagnostic.sourceFilePaths); } if (compilerDiagnostic.location) compilerDiagnostics.set(compilerDiagnostic.location, compilerDiagnostic);else compilerDiagnosticsList.push(compilerDiagnostic); return compilerDiagnostic.severity === "FatalError" ? "Fail" : "Recover"; } function printDiagnostics(caughtFatalError, caughtUnexpectedError = false) { if (compilerDiagnostics.size === 0 && compilerDiagnosticsList.length === 0) { // FatalErrors must have generated at least one CompilerDiagnostic. (0, _invariant.default)(!caughtFatalError, "FatalError must generate at least one CompilerDiagnostic"); return !caughtUnexpectedError; } let informations = 0; let warnings = 0; let recoverableErrors = 0; let fatalErrors = 0; let printCompilerDiagnostic = (compilerDiagnostic, locString = "At an unknown location") => { switch (compilerDiagnostic.severity) { case "Information": informations++; break; case "Warning": warnings++; break; case "RecoverableError": recoverableErrors++; break; default: (0, _invariant.default)(compilerDiagnostic.severity === "FatalError"); fatalErrors++; break; } console.error(`${locString} ${compilerDiagnostic.severity} ${compilerDiagnostic.errorCode}: ${compilerDiagnostic.message}` + ` (https://github.com/facebook/prepack/wiki/${compilerDiagnostic.errorCode})`); let callStack = compilerDiagnostic.callStack; if (callStack !== undefined) { let eolPos = callStack.indexOf("\n"); if (eolPos > 0) console.error(callStack.substring(eolPos + 1)); } }; for (let [loc, compilerDiagnostic] of compilerDiagnostics) { 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, _invariant.default)(loc !== null && loc.source !== null); sourceMessage = `In input file ${loc.source}`; break; } let locString = `${sourceMessage}(${loc.start.line}:${loc.start.column + 1})`; printCompilerDiagnostic(compilerDiagnostic, locString); } for (let compilerDiagnostic of compilerDiagnosticsList) printCompilerDiagnostic(compilerDiagnostic); (0, _invariant.default)(informations + warnings + recoverableErrors + fatalErrors > 0); let plural = (count, word) => count === 1 ? word : `${word}s`; const success = fatalErrors === 0 && recoverableErrors === 0 && !caughtUnexpectedError; console.error(`Prepack ${success ? "succeeded" : "failed"}, reporting ${[fatalErrors > 0 ? `${fatalErrors} ${plural(fatalErrors, "fatal error")}` : undefined, recoverableErrors > 0 ? `${recoverableErrors} ${plural(recoverableErrors, "recoverable error")}` : undefined, warnings > 0 ? `${warnings} ${plural(warnings, "warning")}` : undefined, informations > 0 ? `${informations} ${plural(informations, "informational message")}` : undefined].filter(s => s !== undefined).join(", ")}.`); return success; } let profiler; let success; let debugReproSourceFiles = []; let debugReproSourceMaps = []; try { if (cpuprofilePath !== undefined) { try { profiler = require("v8-profiler-node8"); } catch (e) { // Profiler optional dependency failed console.error("v8-profiler-node8 doesn't work correctly on Windows, see issue #1695"); throw e; } profiler.setSamplingInterval(100); // default is 1000us profiler.startProfiling(""); } try { if (inputFilenames.length === 0) { prepackStdin(resolvedOptions, processSerializedCode, printDiagnostics); return; } let serialized = prepackFileSync(inputFilenames, resolvedOptions); if (reproMode === "reproUnconditionally") { if (serialized.sourceFilePaths) { debugReproSourceFiles = serialized.sourceFilePaths.sourceFiles; debugReproSourceMaps = serialized.sourceFilePaths.sourceMaps; } else { // An input can have no sourcemap/sourcefiles, but we can still package // the input files, prepack runtime, and generate the script. debugReproSourceFiles = []; debugReproSourceMaps = []; } } success = printDiagnostics(false); if (resolvedOptions.serialize && serialized) processSerializedCode(serialized); } catch (err) { success = printDiagnostics(err instanceof FatalError, !(err instanceof FatalError)); (0, _invariant.default)(!success); if (!(err instanceof FatalError)) { // if it is not a FatalError, it means prepack failed, and we should display the Prepack stack trace. console.error(`unexpected ${err}:\n${err.stack}`); } if (reproMode) { // Get largest list of original sources from all diagnostics. // Must iterate through both because maps are ordered so we can't tell which diagnostic is most recent. let largestLength = 0; let allDiagnostics = Array.from(compilerDiagnostics.values()).concat(compilerDiagnosticsList); allDiagnostics.forEach(diagnostic => { if (diagnostic.sourceFilePaths && diagnostic.sourceFilePaths.sourceFiles && diagnostic.sourceFilePaths.sourceMaps) { if (diagnostic.sourceFilePaths.sourceFiles.length > largestLength) { debugReproSourceFiles = diagnostic.sourceFilePaths.sourceFiles; largestLength = diagnostic.sourceFilePaths.sourceFiles.length; debugReproSourceMaps = diagnostic.sourceFilePaths.sourceMaps; } } }); } } } finally { if (profiler !== undefined) { let data = profiler.stopProfiling(""); let start = Date.now(); (0, _invariant.default)(cpuprofilePath !== undefined); let stream = fs.createWriteStream(cpuprofilePath); let getNextToken = (0, _JSONTokenizer.default)(data); let write = () => { for (let token = getNextToken(); token !== undefined; token = getNextToken()) { if (!stream.write(token)) { stream.once("drain", write); return; } } stream.end(); (0, _invariant.default)(cpuprofilePath !== undefined); console.log(`Wrote ${cpuprofilePath} in ${Date.now() - start}ms`); }; write(); } } 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) { let statistics = serialized.statistics; if (statistics === undefined) { return; } let stats = { RealmStatistics: statistics.getRealmStatistics(), SerializerStatistics: statistics.getSerializerStatistics(), TimingStatistics: statistics.projectPerformanceTrackers("Time", pt => pt.time), HeapStatistics: statistics.projectPerformanceTrackers("Memory", pt => pt.memory), MemoryStatistics: _v.default.getHeapStatistics() }; fs.writeFileSync(statsFileName, JSON.stringify(stats)); } if (outputSourceMap) { fs.writeFileSync(outputSourceMap, serialized.map ? JSON.stringify(serialized.map) : ""); } if (heapGraphFilePath !== undefined) { (0, _invariant.default)(serialized.heapGraph); fs.writeFileSync(heapGraphFilePath, serialized.heapGraph); } } // If there will be a repro going on, don't exit. // The repro involves an async directory zip, so exiting here will cause the repro // to not complete. Instead, all calls to repro include a flag to indicate // whether or not it should process.exit() upon completion. if (!success && reproMode === undefined) { process.exit(1); } else if (!success && reproMode === "reproOnFatalError" || reproMode === "reproUnconditionally") { if (debugReproPackager) { debugReproPackager.generateDebugRepro(!success, debugReproSourceFiles, debugReproSourceMaps, reproFilePath, reproFileNames, reproArguments, externalPrepackPath); } else { console.error("Debug Repro Packager was not initialized."); process.exit(1); } } } 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, _fs.default); } else { run(Object, Array, console, JSON, process, _prepackNode.prepackStdin, _prepackNode.prepackFileSync, _errors.FatalError, _options.CompatibilityValues, _fs.default); } //# sourceMappingURL=prepack-cli.js.map