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.
This commit is contained in:
Sebastian Markbåge 2017-04-27 00:30:00 -07:00 committed by GitHub
parent cfbf667c21
commit 111af14f61
7 changed files with 244 additions and 90 deletions

View File

@ -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",

71
src/options.js Normal file
View File

@ -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,
};
}

View File

@ -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;
}
}

67
src/prepack-node.js Normal file
View File

@ -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;
}

59
src/prepack-standalone.js Normal file
View File

@ -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;
}

View File

@ -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);
}

View File

@ -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: [