diff --git a/package.json b/package.json index f5bd47622..03fa2a45d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "prepack": "bin/prepack.js", "prepack-repl": "bin/prepack-repl.js" }, - "main": "lib/prepack.js", + "main": "lib/prepack-node.js", + "browser": "lib/prepack-standalone.js", "scripts": { "build": "babel src --out-dir lib --source-maps && npm run build-bundle", "build-bundle": "webpack", diff --git a/src/options.js b/src/options.js new file mode 100644 index 000000000..016ceea3a --- /dev/null +++ b/src/options.js @@ -0,0 +1,71 @@ +/** + * 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. + */ + +/* @flow */ + +import type { RealmOptions } from "./types"; +import type { SerializerOptions } from "./serializer/types"; + +export type Options = { + filename?: string, + inputSourceMapFilename?: string, + sourceMaps?: boolean, + compatibility?: "browser" | "jsc-600-1-4-17", + mathRandomSeed?: string, + speculate?: boolean, + trace?: boolean, + debugNames?: boolean, + singlePass?: boolean, + logStatistics?: boolean, + logModules?: boolean, + delayUnsupportedRequires?: boolean, + internalDebug?: boolean, + uniqueSuffix?: string, + timeout?: number, + strictlyMonotonicDateNow?: boolean, +}; + +export function getRealmOptions({ + compatibility = "browser", + mathRandomSeed, + debugNames = false, + uniqueSuffix, + timeout, + strictlyMonotonicDateNow +}: Options): RealmOptions { + return { + partial: true, + compatibility, + mathRandomSeed, + debugNames, + uniqueSuffix, + timeout, + strictlyMonotonicDateNow, + }; +} + +export function getSerializerOptions({ + speculate = false, + trace = false, + singlePass = false, + logStatistics = false, + logModules = false, + delayUnsupportedRequires = false, + internalDebug = false +}: Options): SerializerOptions { + return { + initializeMoreModules: speculate, + trace, + singlePass, + logStatistics, + logModules, + delayUnsupportedRequires, + internalDebug, + }; +} diff --git a/src/prepack-cli.js b/src/prepack-cli.js index 9b4e8b840..27a05b656 100644 --- a/src/prepack-cli.js +++ b/src/prepack-cli.js @@ -9,7 +9,8 @@ /* @flow */ -import { run } from "./prepack.js"; +import { prepackFileSync, InitializationError } from "./prepack-node.js"; +import fs from "fs"; let args = Array.from(process.argv); args.splice(0, 2); @@ -17,8 +18,8 @@ let inputFilename; let outputFilename; let compatibility; let mathRandomSeed; -let inputMap; -let ouputMap; +let inputSourceMap; +let outputSourceMap; let flags = { speculate: false, trace: false, @@ -27,6 +28,7 @@ let flags = { logStatistics: false, logModules: false, delayUnsupportedRequires: false, + internalDebug: false, }; while (args.length) { let arg = args[0]; args.shift(); @@ -52,10 +54,10 @@ while (args.length) { mathRandomSeed = args[0]; args.shift(); break; case "srcmapIn": - inputMap = args[0]; args.shift(); + inputSourceMap = args[0]; args.shift(); break; case "srcmapOut": - ouputMap = args[0]; args.shift(); + outputSourceMap = args[0]; args.shift(); break; case "help": console.log("Usage: prepack.js [ --out output.js ] [ --compatibility jsc ] [ --mathRandomSeed seedvalue ] [ --srcmapIn inputMap ] [ --srcmapOut outputMap ] [ --speculate ] [ --trace ] [ -- | input.js ] [ --singlePass ] [ --debugNames ]"); @@ -70,9 +72,44 @@ while (args.length) { } } } + if (!inputFilename) { console.error("Missing input file."); process.exit(1); } else { - run(inputFilename, compatibility, mathRandomSeed, outputFilename, inputMap, ouputMap, flags.speculate, flags.trace, flags.debugNames, flags.singlePass, flags.logStatistics, flags.logModules, flags.delayUnsupportedRequires); + try { + let serialized = prepackFileSync(inputFilename, { + compatibility, + mathRandomSeed, + inputSourceMapFilename: inputSourceMap, + sourceMaps: !!outputSourceMap, + ...flags + }); + + let code = serialized.code; + + if (code.length >= 1000 || outputFilename) { + let filename = outputFilename || (inputFilename + "-processed.js"); + console.log(`Prepacked source code written to ${filename}.`); + fs.writeFileSync(filename, code); + } + + if (code.length <= 1000 && !outputFilename) { + console.log("+++++++++++++++++ Prepacked source code"); + console.log(code); + console.log("================="); + } + + if (outputSourceMap) { + fs.writeFileSync(outputSourceMap, serialized.map || ""); + } + } catch (x) { + if (x instanceof InitializationError) { + // Ignore InitializationError since they have already logged + // their errors to the console, but exit with an error code. + process.exit(1); + } + // For any other type of error, rethrow. + throw x; + } } diff --git a/src/prepack-node.js b/src/prepack-node.js new file mode 100644 index 000000000..791298641 --- /dev/null +++ b/src/prepack-node.js @@ -0,0 +1,67 @@ +/** + * 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. + */ + +/* @flow */ +import Serializer from "./serializer/index.js"; +import fs from "fs"; +import { getRealmOptions, getSerializerOptions } from "./options"; +import { InitializationError } from "./prepack-standalone"; + +import type { Options } from "./options"; + +export * from "./prepack-standalone"; + +export function prepackFile(filename: string, options: Options = {}, callback: Function) { + let sourceMapFilename = options.inputSourceMapFilename || (filename + ".map"); + fs.readFile(filename, "utf8", function(fileErr, code) { + if (fileErr) { + callback(fileErr); + return; + } + fs.readFile(sourceMapFilename, "utf8", function(mapErr, sourceMap) { + if (mapErr) { + console.log(`No sourcemap found at ${sourceMapFilename}.`); + sourceMap = ""; + } + let serialized; + try { + serialized = new Serializer( + getRealmOptions(options), + getSerializerOptions(options), + ).init(filename, code, sourceMap, options.sourceMaps); + if (!serialized) { + throw new InitializationError(); + } + } catch (err) { + callback(err); + return; + } + callback(null, serialized); + }); + }); +} + +export function prepackFileSync(filename: string, options: Options = {}) { + let code = fs.readFileSync(filename, "utf8"); + let sourceMap = ""; + let sourceMapFilename = options.inputSourceMapFilename || (filename + ".map"); + try { + sourceMap = fs.readFileSync(sourceMapFilename, "utf8"); + } catch (_e) { + console.log(`No sourcemap found at ${sourceMapFilename}.`); + } + let serialized = new Serializer( + getRealmOptions(options), + getSerializerOptions(options), + ).init(filename, code, sourceMap, options.sourceMaps); + if (!serialized) { + throw new InitializationError(); + } + return serialized; +} diff --git a/src/prepack-standalone.js b/src/prepack-standalone.js new file mode 100644 index 000000000..3cb815a31 --- /dev/null +++ b/src/prepack-standalone.js @@ -0,0 +1,59 @@ +/** + * 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. + */ + +/* @flow */ +import Serializer from "./serializer/index.js"; +import * as t from "babel-types"; +import { getRealmOptions, getSerializerOptions } from "./options"; + +import type { Options } from "./options"; +import type { BabelNodeFile, BabelNodeProgram } from "babel-types"; + +// This should just be a class but Babel classes doesn't work with +// built-in super classes. +export function InitializationError() { + let self = new Error("An error occurred while prepacking. See the error logs."); + Object.setPrototypeOf(self, InitializationError.prototype); + return self; +} +Object.setPrototypeOf(InitializationError, Error); +Object.setPrototypeOf(InitializationError.prototype, Error.prototype); + + +export function prepack(code: string, options: Options = {}) { + let filename = options.filename || 'unknown'; + let serialized = new Serializer( + getRealmOptions(options), + getSerializerOptions(options), + ).init(filename, code, "", false); + if (!serialized) { + throw new InitializationError(); + } + return serialized; +} + +export function prepackFromAst(ast: BabelNodeFile | BabelNodeProgram, code: string, options: Options = {}) { + if (ast && ast.type === "Program") { + ast = t.file(ast, [], []); + } else if (!ast || ast.type !== "File") { + throw new Error("Not a valid ast?"); + } + + // TODO: Expose an option to wire an already parsed ast all the way through + // to the execution environment. For now, we just reparse. + + let serialized = new Serializer( + getRealmOptions(options), + getSerializerOptions(options), + ).init("", code, "", options.sourceMaps); + if (!serialized) { + throw new InitializationError(); + } + return serialized; +} diff --git a/src/prepack.js b/src/prepack.js deleted file mode 100644 index f3c4269b5..000000000 --- a/src/prepack.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * 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. - */ - -/* @flow */ -import Serializer from "./serializer/index.js"; -import invariant from "./invariant.js"; -let fs = require("fs"); - -function run_internal( - name: string, - raw: string, - map: string = "", - compatibility?: "browser" | "jsc-600-1-4-17" = "browser", - mathRandomSeed: void | string, - outputFilename?: string, - outputMap?: string, - speculate: boolean = false, - trace: boolean = false, - debugNames: boolean = false, - singlePass: boolean = false, - logStatistics: boolean = false, - logModules: boolean = false, - delayUnsupportedRequires: boolean = false) { - let serialized = - new Serializer( - { partial: true, compatibility, mathRandomSeed, debugNames }, - { initializeMoreModules: speculate, internalDebug: true, trace, singlePass, logStatistics, logModules, delayUnsupportedRequires }) - .init(name, raw, map, outputMap !== undefined); - if (!serialized) { - process.exit(1); - invariant(false); - } - let code = serialized.code; - - if (code.length >= 1000 || outputFilename) { - let filename = outputFilename || (name + "-processed.js"); - console.log(`Prepacked source code written to ${filename}.`); - fs.writeFileSync(filename, code); - } - - if (code.length <= 1000 && !outputFilename) { - console.log("+++++++++++++++++ Prepacked source code"); - console.log(code); - console.log("================="); - } - - if (outputMap) { - fs.writeFileSync(outputMap, map); - } -} - -export function run( - inFn: string, - compat?: "browser" | "jsc-600-1-4-17" = "browser", - mathRandSeed: void | string, - outFn?: string, - inputMap?: string, - outMap?: string, - speculateOpt?: boolean, - trace?: boolean, - debugNames?: boolean, - singlePass?: boolean, - logStatistics?: boolean, - logModules?: boolean, - delayUnsupportedRequires?: boolean) { - let input = fs.readFileSync(inFn, "utf8"); - let map = ""; - let mapFile = inputMap ? inputMap : inFn + ".map"; - try { - map = fs.readFileSync(mapFile, "utf8"); - } catch (_e) { - console.log(`No sourcemap found at ${mapFile}.`); - } - run_internal(inFn, input, map, compat, mathRandSeed, outFn, outMap, speculateOpt, trace, debugNames, singlePass, logStatistics, logModules, delayUnsupportedRequires); -} diff --git a/webpack.config.js b/webpack.config.js index be7377182..d004f955a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,11 +11,11 @@ const path = require('path'); const webpack = require('webpack'); const WebpackConfig = { - entry: "./lib/serializer/index.js", + entry: "./", output: { path: path.join(__dirname), filename: "prepack.min.js", - library: 'prepack', + library: 'Prepack', }, plugins: [