From 111af14f617edfd6267db01d9d5cdeea6f8973cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 27 Apr 2017 00:30:00 -0700 Subject: [PATCH] Refactor the public API in a similar style to Babel API (#469) * Refactor the public API This refactors the public Node API to mimic the [Babel API](http://babeljs.io/docs/usage/api/). This is a bit more idiomatic than "run CLI" or instantiate the full serializer object. I'm passing a single options object instead of individual arguments. This is a bit more managable as the option list grows. The prepack-node file is what is exposed in Node which has a file system API. Both synchronous and asynchronous forms. prepack-standalone is a module for environments without access to the file system such as browsers. I configure this to be exposed by default for such environments. That way we can also use this in the webpack build so that our own repl on the website can just use this public API. * Throw an error when serialization fails This lets any compiler flow to terminate properly by default but the error can safely be ignored if we've already printed error messages for it such as in the CLI. Turn internalDebug error messages off by default but allow it to be configured. * Added missing realm options Not yet exposed in the cli but available through the API. --- package.json | 3 +- src/options.js | 71 ++++++++++++++++++++++++++++++++++ src/prepack-cli.js | 49 ++++++++++++++++++++--- src/prepack-node.js | 67 ++++++++++++++++++++++++++++++++ src/prepack-standalone.js | 59 ++++++++++++++++++++++++++++ src/prepack.js | 81 --------------------------------------- webpack.config.js | 4 +- 7 files changed, 244 insertions(+), 90 deletions(-) create mode 100644 src/options.js create mode 100644 src/prepack-node.js create mode 100644 src/prepack-standalone.js delete mode 100644 src/prepack.js 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: [