Make babel-node a standalone package (#6023)

* Make babel-node a standalone package

* New package `babel-node` previously `babel-cli/bin/babel-node`

* updates
This commit is contained in:
Brian Ng 2017-07-29 21:26:28 -05:00 committed by Henry Zhu
parent e32042f353
commit 6d965c0926
30 changed files with 627 additions and 0 deletions

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ package-lock.json
!/packages/babel-runtime/helpers/es6/toArray.js !/packages/babel-runtime/helpers/es6/toArray.js
/packages/babel-register/test/.babel /packages/babel-register/test/.babel
/packages/babel-cli/test/tmp /packages/babel-cli/test/tmp
/packages/babel-node/test/tmp
/packages/*/lib /packages/*/lib
.nyc_output .nyc_output
/babel.sublime-workspace /babel.sublime-workspace

View File

@ -0,0 +1,3 @@
src
test
node_modules

View File

@ -0,0 +1,17 @@
# babel-node
> A Babel-powered Node.js CLI
babel-node is a CLI that works exactly the same as the Node.js CLI, with the added benefit of compiling with Babel presets and plugins before running it.
## Install
```sh
npm install --save-dev babel-node
```
## Usage
```sh
babel-node [options] [ -e script | script.js ] [arguments]
```

View File

@ -0,0 +1,3 @@
#!/usr/bin/env node
require("../lib/babel-node");

View File

@ -0,0 +1,34 @@
{
"name": "babel-node",
"version": "7.0.0-alpha.17",
"description": "Babel command line",
"author": "Sebastian McKenzie <sebmck@gmail.com>",
"homepage": "https://babeljs.io/",
"license": "MIT",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-node",
"keywords": [
"6to5",
"babel",
"es6",
"transpile",
"transpiler",
"babel-cli",
"compiler"
],
"dependencies": {
"babel-core": "7.0.0-alpha.17",
"babel-register": "7.0.0-alpha.17",
"babel-polyfill": "7.0.0-alpha.17",
"commander": "^2.8.1",
"fs-readdir-recursive": "^1.0.0",
"lodash": "^4.2.0",
"output-file-sync": "^2.0.0",
"v8flags": "^3.0.0"
},
"devDependencies": {
"babel-helper-fixtures": "7.0.0-alpha.17"
},
"bin": {
"babel-node": "./bin/babel-node.js"
}
}

View File

@ -0,0 +1,186 @@
import commander from "commander";
import Module from "module";
import { inspect } from "util";
import path from "path";
import repl from "repl";
import * as babel from "babel-core";
import vm from "vm";
import "babel-polyfill";
import register from "babel-register";
import pkg from "../package.json";
const program = new commander.Command("babel-node");
function collect(value, previousValue): Array<string> {
// If the user passed the option with no value, like "babel-node file.js --presets", do nothing.
if (typeof value !== "string") return previousValue;
const values = value.split(",");
return previousValue ? previousValue.concat(values) : values;
}
/* eslint-disable max-len */
program.option("-e, --eval [script]", "Evaluate script");
program.option("-p, --print [code]", "Evaluate script and print result");
program.option(
"-o, --only [globs]",
"A comma-separated list of glob patterns to compile",
collect,
);
program.option(
"-i, --ignore [globs]",
"A comma-separated list of glob patterns to skip compiling",
collect,
);
program.option(
"-x, --extensions [extensions]",
"List of extensions to hook into [.es6,.js,.es,.jsx,.mjs]",
collect,
);
program.option("-w, --plugins [string]", "", collect);
program.option("-b, --presets [string]", "", collect);
/* eslint-enable max-len */
program.version(pkg.version);
program.usage("[options] [ -e script | script.js ] [arguments]");
program.parse(process.argv);
register({
extensions: program.extensions,
ignore: program.ignore,
only: program.only,
plugins: program.plugins,
presets: program.presets,
});
const replPlugin = ({ types: t }) => ({
visitor: {
ModuleDeclaration(path) {
throw path.buildCodeFrameError("Modules aren't supported in the REPL");
},
VariableDeclaration(path) {
if (path.node.kind !== "var") {
throw path.buildCodeFrameError(
"Only `var` variables are supported in the REPL",
);
}
},
Program(path) {
if (path.get("body").some(child => child.isExpressionStatement())) return;
// If the executed code doesn't evaluate to a value,
// prevent implicit strict mode from printing 'use strict'.
path.pushContainer(
"body",
t.expressionStatement(t.identifier("undefined")),
);
},
},
});
const _eval = function(code, filename) {
code = code.trim();
if (!code) return undefined;
code = babel.transform(code, {
filename: filename,
presets: program.presets,
plugins: (program.plugins || []).concat([replPlugin]),
}).code;
return vm.runInThisContext(code, {
filename: filename,
});
};
if (program.eval || program.print) {
let code = program.eval;
if (!code || code === true) code = program.print;
global.__filename = "[eval]";
global.__dirname = process.cwd();
const module = new Module(global.__filename);
module.filename = global.__filename;
module.paths = Module._nodeModulePaths(global.__dirname);
global.exports = module.exports;
global.module = module;
global.require = module.require.bind(module);
const result = _eval(code, global.__filename);
if (program.print) {
const output = typeof result === "string" ? result : inspect(result);
process.stdout.write(output + "\n");
}
} else {
if (program.args.length) {
// slice all arguments up to the first filename since they're babel args that we handle
let args = process.argv.slice(2);
let i = 0;
let ignoreNext = false;
args.some(function(arg, i2) {
if (ignoreNext) {
ignoreNext = false;
return;
}
if (arg[0] === "-") {
const parsedArg = program[arg.slice(2)];
if (parsedArg && parsedArg !== true) {
ignoreNext = true;
}
} else {
i = i2;
return true;
}
});
args = args.slice(i);
// make the filename absolute
const filename = args[0];
if (!path.isAbsolute(filename)) {
args[0] = path.join(process.cwd(), filename);
}
// add back on node and concat the sliced args
process.argv = ["node"].concat(args);
process.execArgv.unshift(__filename);
Module.runMain();
} else {
replStart();
}
}
function replStart() {
repl.start({
prompt: "> ",
input: process.stdin,
output: process.stdout,
eval: replEval,
useGlobal: true,
});
}
function replEval(code, context, filename, callback) {
let err;
let result;
try {
if (code[0] === "(" && code[code.length - 1] === ")") {
code = code.slice(1, -1); // remove "(" and ")"
}
result = _eval(code, filename);
} catch (e) {
err = e;
}
callback(err, result);
}

View File

@ -0,0 +1,98 @@
/**
* This tiny wrapper file checks for known node flags and appends them
* when found, before invoking the "real" _babel-node(1) executable.
*/
import getV8Flags from "v8flags";
import path from "path";
let args = [path.join(__dirname, "_babel-node")];
let babelArgs = process.argv.slice(2);
let userArgs;
// separate node arguments from script arguments
const argSeparator = babelArgs.indexOf("--");
if (argSeparator > -1) {
userArgs = babelArgs.slice(argSeparator); // including the --
babelArgs = babelArgs.slice(0, argSeparator);
}
/**
* Replace dashes with underscores in the v8Flag name
* Also ensure that if the arg contains a value (e.g. --arg=true)
* that only the flag is returned.
*/
function getNormalizedV8Flag(arg) {
const matches = arg.match(/--(.+)/);
if (matches) {
return `--${matches[1].replace(/-/g, "_")}`;
}
return arg;
}
getV8Flags(function(err, v8Flags) {
babelArgs.forEach(function(arg) {
const flag = arg.split("=")[0];
switch (flag) {
case "-d":
args.unshift("--debug");
break;
case "debug":
case "--debug":
case "--debug-brk":
case "--inspect":
args.unshift(arg);
break;
case "-gc":
args.unshift("--expose-gc");
break;
case "--nolazy":
args.unshift(flag);
break;
default:
if (
v8Flags.indexOf(getNormalizedV8Flag(flag)) >= 0 ||
arg.indexOf("--trace") === 0
) {
args.unshift(arg);
} else {
args.push(arg);
}
break;
}
});
// append arguments passed after --
if (argSeparator > -1) {
args = args.concat(userArgs);
}
try {
const kexec = require("kexec");
kexec(process.argv[0], args);
} catch (err) {
if (err.code !== "MODULE_NOT_FOUND") throw err;
const child_process = require("child_process");
const proc = child_process.spawn(process.argv[0], args, {
stdio: "inherit",
});
proc.on("exit", function(code, signal) {
process.on("exit", function() {
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code);
}
});
});
}
});

View File

@ -0,0 +1,4 @@
{
"args": ["--eval", "console.log([1, 2, 3].map(x => x * x));"],
"stdout": "[ 1, 4, 9 ]"
}

View File

@ -0,0 +1 @@
console.log([1, 2, 3].map(x => x * x));

View File

@ -0,0 +1,4 @@
{
"args": ["foo", "--extensions", ".bar"],
"stdout": "[ 1, 4, 9 ]"
}

View File

@ -0,0 +1,4 @@
{
"args": ["--print", "--eval", "([1, 2, 3].map(x => x * x))"],
"stdout": "[ 1, 4, 9 ]"
}

View File

@ -0,0 +1 @@
console.log(process.argv[2]);

View File

@ -0,0 +1,4 @@
{
"args": ["bar", "foo"],
"stdout": "foo"
}

View File

@ -0,0 +1,2 @@
var foo = () => console.log("foo");
foo();

View File

@ -0,0 +1,3 @@
{
"args": ["foo"]
}

View File

@ -0,0 +1 @@
foo

View File

@ -0,0 +1,2 @@
var foo = () => console.log("foo");
foo();

View File

@ -0,0 +1,3 @@
{
"args": ["bar"]
}

View File

@ -0,0 +1 @@
foo

View File

@ -0,0 +1,4 @@
{
"args": ["--eval","--print", "var a = 1;"],
"stdout": "undefined"
}

View File

@ -0,0 +1,2 @@
var bar = () => console.log("bar");
bar();

View File

@ -0,0 +1,5 @@
import "./bar2";
import "./not_node_modules";
var foo = () => console.log("foo");
foo();

View File

@ -0,0 +1,10 @@
/*
The purpose of this file is to test that the node_modules check in the require
hook doesn't mistakenly exclude something like "not_node_modules". To pass, this
file merely needs to be transpiled. The transpiled code won't, and doesn't need
to, execute without error. It won't execute because React will be undefined.
*/
try {
<Some jsx="element" />;
}
catch (e) {}

View File

@ -0,0 +1,3 @@
{
"args": ["foo2"]
}

View File

@ -0,0 +1,2 @@
bar
foo

View File

@ -0,0 +1,4 @@
{
"args": ["--expose-gc-as=garbageCollector", "--eval", "console.log(typeof global.garbageCollector)"],
"stdout": "function"
}

View File

@ -0,0 +1,4 @@
{
"args": ["--expose-gc", "--eval", "console.log(typeof global.gc)"],
"stdout": "function"
}

View File

@ -0,0 +1,4 @@
{
"args": ["--expose_gc_as=garbageCollector", "--eval", "console.log(typeof global.garbageCollector)"],
"stdout": "function"
}

View File

@ -0,0 +1,4 @@
{
"args": ["--expose_gc", "--eval", "console.log(typeof global.gc)"],
"stdout": "function"
}

View File

@ -0,0 +1,213 @@
const includes = require("lodash/includes");
const readdir = require("fs-readdir-recursive");
const helper = require("babel-helper-fixtures");
const assert = require("assert");
const rimraf = require("rimraf");
const outputFileSync = require("output-file-sync");
const child = require("child_process");
const merge = require("lodash/merge");
const path = require("path");
const chai = require("chai");
const fs = require("fs");
const fixtureLoc = path.join(__dirname, "fixtures");
const tmpLoc = path.join(__dirname, "tmp");
const fileFilter = function(x) {
return x !== ".DS_Store";
};
const presetLocs = [
path.join(__dirname, "../../babel-preset-es2015"),
path.join(__dirname, "../../babel-preset-react"),
].join(",");
const pluginLocs = [
path.join(__dirname, "/../../babel-plugin-transform-strict-mode"),
path.join(__dirname, "/../../babel-plugin-transform-es2015-modules-commonjs"),
].join(",");
const readDir = function(loc, filter) {
const files = {};
if (fs.existsSync(loc)) {
readdir(loc, filter).forEach(function(filename) {
files[filename] = helper.readFile(path.join(loc, filename));
});
}
return files;
};
const saveInFiles = function(files) {
// Place an empty .babelrc in each test so tests won't unexpectedly get to repo-level config.
outputFileSync(".babelrc", "{}");
Object.keys(files).forEach(function(filename) {
const content = files[filename];
outputFileSync(filename, content);
});
};
const assertTest = function(stdout, stderr, opts) {
const expectStderr = opts.stderr.trim();
stderr = stderr.trim();
if (opts.stderr) {
if (opts.stderrContains) {
assert.ok(
includes(stderr, expectStderr),
"stderr " +
JSON.stringify(stderr) +
" didn't contain " +
JSON.stringify(expectStderr),
);
} else {
chai.expect(stderr).to.equal(expectStderr, "stderr didn't match");
}
} else if (stderr) {
throw new Error("stderr:\n" + stderr);
}
const expectStdout = opts.stdout.trim();
stdout = stdout.trim();
stdout = stdout.replace(/\\/g, "/");
if (opts.stdout) {
if (opts.stdoutContains) {
assert.ok(
includes(stdout, expectStdout),
"stdout " +
JSON.stringify(stdout) +
" didn't contain " +
JSON.stringify(expectStdout),
);
} else {
chai.expect(stdout).to.equal(expectStdout, "stdout didn't match");
}
} else if (stdout) {
throw new Error("stdout:\n" + stdout);
}
if (opts.outFiles) {
const actualFiles = readDir(path.join(tmpLoc));
Object.keys(actualFiles).forEach(function(filename) {
if (!opts.inFiles.hasOwnProperty(filename)) {
const expect = opts.outFiles[filename];
const actual = actualFiles[filename];
chai.expect(expect, "Output is missing: " + filename).to.not.be
.undefined;
if (expect) {
chai
.expect(actual)
.to.equal(expect, "Compiled output does not match: " + filename);
}
}
});
Object.keys(opts.outFiles).forEach(function(filename) {
chai
.expect(actualFiles, "Extraneous file in output: " + filename)
.to.contain.key(filename);
});
}
};
const buildTest = function(binName, testName, opts) {
const binLoc = path.join(__dirname, "../lib", binName);
return function(callback) {
clear();
saveInFiles(opts.inFiles);
let args = [binLoc];
args.push("--presets", presetLocs, "--plugins", pluginLocs);
args.push("--only", "../../../../packages/*/test");
args = args.concat(opts.args);
const spawn = child.spawn(process.execPath, args);
let stderr = "";
let stdout = "";
spawn.stderr.on("data", function(chunk) {
stderr += chunk;
});
spawn.stdout.on("data", function(chunk) {
stdout += chunk;
});
spawn.on("close", function() {
let err;
try {
assertTest(stdout, stderr, opts);
} catch (e) {
err = e;
}
if (err) {
err.message =
args.map(arg => `"${arg}"`).join(" ") + ": " + err.message;
}
callback(err);
});
if (opts.stdin) {
spawn.stdin.write(opts.stdin);
spawn.stdin.end();
}
};
};
const clear = function() {
process.chdir(__dirname);
if (fs.existsSync(tmpLoc)) rimraf.sync(tmpLoc);
fs.mkdirSync(tmpLoc);
process.chdir(tmpLoc);
};
fs.readdirSync(fixtureLoc).forEach(function(binName) {
if (binName[0] === ".") return;
const suiteLoc = path.join(fixtureLoc, binName);
describe("bin/" + binName, function() {
fs.readdirSync(suiteLoc).forEach(function(testName) {
if (testName[0] === ".") return;
const testLoc = path.join(suiteLoc, testName);
const opts = {
args: [],
};
const optionsLoc = path.join(testLoc, "options.json");
if (fs.existsSync(optionsLoc)) merge(opts, require(optionsLoc));
["stdout", "stdin", "stderr"].forEach(function(key) {
const loc = path.join(testLoc, key + ".txt");
if (fs.existsSync(loc)) {
opts[key] = helper.readFile(loc);
} else {
opts[key] = opts[key] || "";
}
});
opts.outFiles = readDir(path.join(testLoc, "out-files"), fileFilter);
opts.inFiles = readDir(path.join(testLoc, "in-files"), fileFilter);
const babelrcLoc = path.join(testLoc, ".babelrc");
if (fs.existsSync(babelrcLoc)) {
// copy .babelrc file to tmp directory
opts.inFiles[".babelrc"] = helper.readFile(babelrcLoc);
}
it(testName, buildTest(binName, testName, opts));
});
});
});