Define a clear separation between Babel and CLI options.

This commit is contained in:
Logan Smyth 2018-05-01 21:45:40 -07:00
parent a192f8beb4
commit 41ca312545
5 changed files with 353 additions and 318 deletions

View File

@ -8,17 +8,19 @@ import * as util from "./util";
let compiledFiles = 0;
export default function(commander, filenames, opts) {
export default function({ cliOptions, babelOptions }) {
const filenames = cliOptions.filenames;
function write(src, base, callback) {
let relative = path.relative(base, src);
if (!util.isCompilableExtension(relative, commander.extensions)) {
if (!util.isCompilableExtension(relative, cliOptions.extensions)) {
return process.nextTick(callback);
}
// remove extension and then append back on .js
relative = util.adjustRelative(relative, commander.keepFileExtension);
relative = util.adjustRelative(relative, cliOptions.keepFileExtension);
const dest = getDest(commander, relative, base);
const dest = getDest(relative, base);
util.compile(
src,
@ -26,17 +28,21 @@ export default function(commander, filenames, opts) {
{
sourceFileName: slash(path.relative(dest + "/..", src)),
},
opts,
babelOptions,
),
function(err, res) {
if (err && cliOptions.watch) {
console.error(err);
err = null;
}
if (err) return callback(err);
if (!res) return callback();
// we've requested explicit sourcemaps to be written to disk
if (
res.map &&
commander.sourceMaps &&
commander.sourceMaps !== "inline"
babelOptions.sourceMaps &&
babelOptions.sourceMaps !== "inline"
) {
const mapLoc = dest + ".map";
res.code = util.addSourceMappingUrl(res.code, mapLoc);
@ -49,15 +55,19 @@ export default function(commander, filenames, opts) {
compiledFiles += 1;
util.log(src + " -> " + dest);
if (cliOptions.verbose) {
console.log(src + " -> " + dest);
}
return callback(null, true);
},
);
}
function getDest(commander, filename, base) {
if (commander.relative) return path.join(base, commander.outDir, filename);
return path.join(commander.outDir, filename);
function getDest(filename, base) {
if (cliOptions.relative) {
return path.join(base, cliOptions.outDir, filename);
}
return path.join(cliOptions.outDir, filename);
}
function outputDestFolder(outDir) {
@ -70,9 +80,9 @@ export default function(commander, filenames, opts) {
function handleFile(src, base, callback) {
write(src, base, function(err, res) {
if (err) return callback(err);
if (!res && commander.copyFiles) {
if (!res && cliOptions.copyFiles) {
const filename = path.relative(base, src);
const dest = getDest(commander, filename, base);
const dest = getDest(filename, base);
outputFileSync(dest, fs.readFileSync(src));
util.chmod(src, dest);
}
@ -83,7 +93,7 @@ export default function(commander, filenames, opts) {
function sequentialHandleFile(files, dirname, index, callback) {
if (files.length === 0) {
outputDestFolder(commander.outDir);
outputDestFolder(cliOptions.outDir);
return;
}
@ -114,11 +124,11 @@ export default function(commander, filenames, opts) {
if (stat.isDirectory(filename)) {
const dirname = filename;
if (commander.deleteDirOnStart) {
util.deleteDir(commander.outDir);
if (cliOptions.deleteDirOnStart) {
util.deleteDir(cliOptions.outDir);
}
const files = util.readdir(dirname, commander.includeDotfiles);
const files = util.readdir(dirname, cliOptions.includeDotfiles);
sequentialHandleFile(files, dirname, callback);
} else {
write(filename, path.dirname(filename), callback);
@ -134,21 +144,20 @@ export default function(commander, filenames, opts) {
if (index !== filenames.length) {
sequentialHandle(filenames, index);
} else {
util.log(
console.log(
`🎉 Successfully compiled ${compiledFiles} ${
compiledFiles > 1 ? "files" : "file"
} with Babel.`,
true,
);
}
});
}
if (!commander.skipInitialBuild) {
if (!cliOptions.skipInitialBuild) {
sequentialHandle(filenames);
}
if (commander.watch) {
if (cliOptions.watch) {
const chokidar = util.requireChokidar();
filenames.forEach(function(filenameOrDir) {

View File

@ -7,20 +7,18 @@ import fs from "fs";
import * as util from "./util";
export default function(commander, filenames, opts) {
if (commander.sourceMaps === "inline") {
opts.sourceMaps = true;
}
export default function({ cliOptions, babelOptions }) {
const filenames = cliOptions.filenames;
let results = [];
const buildResult = function() {
const map = new sourceMap.SourceMapGenerator({
file:
commander.sourceMapTarget ||
path.basename(commander.outFile || "") ||
cliOptions.sourceMapTarget ||
path.basename(cliOptions.outFile || "") ||
"stdout",
sourceRoot: opts.sourceRoot,
sourceRoot: babelOptions.sourceRoot,
});
let code = "";
@ -66,8 +64,8 @@ export default function(commander, filenames, opts) {
// add the inline sourcemap comment if we've either explicitly asked for inline source
// maps, or we've requested them without any output file
if (
commander.sourceMaps === "inline" ||
(!commander.outFile && commander.sourceMaps)
babelOptions.sourceMaps === "inline" ||
(!cliOptions.outFile && babelOptions.sourceMaps)
) {
code += "\n" + convertSourceMap.fromObject(map).toComment();
}
@ -81,15 +79,15 @@ export default function(commander, filenames, opts) {
const output = function() {
const result = buildResult();
if (commander.outFile) {
if (cliOptions.outFile) {
// we've requested for a sourcemap to be written to disk
if (commander.sourceMaps && commander.sourceMaps !== "inline") {
const mapLoc = commander.outFile + ".map";
if (babelOptions.sourceMaps && babelOptions.sourceMaps !== "inline") {
const mapLoc = cliOptions.outFile + ".map";
result.code = util.addSourceMappingUrl(result.code, mapLoc);
fs.writeFileSync(mapLoc, JSON.stringify(result.map));
}
fs.writeFileSync(commander.outFile, result.code);
fs.writeFileSync(cliOptions.outFile, result.code);
} else {
process.stdout.write(result.code + "\n");
}
@ -107,13 +105,13 @@ export default function(commander, filenames, opts) {
process.stdin.on("end", function() {
util.transform(
commander.filename,
cliOptions.filename,
code,
defaults(
{
sourceFileName: "stdin",
},
opts,
babelOptions,
),
function(err, res) {
if (err) throw err;
@ -136,7 +134,7 @@ export default function(commander, filenames, opts) {
const dirname = filename;
util
.readdirForCompilable(filename, commander.includeDotfiles)
.readdirForCompilable(filename, cliOptions.includeDotfiles)
.forEach(function(filename) {
_filenames.push(path.join(dirname, filename));
});
@ -149,9 +147,9 @@ export default function(commander, filenames, opts) {
_filenames.forEach(function(filename, index) {
let sourceFilename = filename;
if (commander.outFile) {
if (cliOptions.outFile) {
sourceFilename = path.relative(
path.dirname(commander.outFile),
path.dirname(cliOptions.outFile),
sourceFilename,
);
}
@ -162,10 +160,22 @@ export default function(commander, filenames, opts) {
defaults(
{
sourceFileName: sourceFilename,
// Since we're compiling everything to be merged together,
// "inline" applies to the final output file, but to the individual
// files being concatenated.
sourceMaps:
babelOptions.sourceMaps === "inline"
? true
: babelOptions.sourceMaps,
},
opts,
babelOptions,
),
function(err, res) {
if (err && cliOptions.watch) {
console.error(err);
err = null;
}
if (err) throw err;
filesProcessed++;
@ -180,11 +190,11 @@ export default function(commander, filenames, opts) {
};
const files = function() {
if (!commander.skipInitialBuild) {
if (!cliOptions.skipInitialBuild) {
walk();
}
if (commander.watch) {
if (cliOptions.watch) {
const chokidar = util.requireChokidar();
chokidar
.watch(filenames, {
@ -196,12 +206,14 @@ export default function(commander, filenames, opts) {
},
})
.on("all", function(type, filename) {
if (!util.isCompilableExtension(filename, commander.extensions)) {
if (!util.isCompilableExtension(filename, cliOptions.extensions)) {
return;
}
if (type === "add" || type === "change") {
util.log(type + " " + filename);
if (cliOptions.verbose) {
console.log(type + " " + filename);
}
try {
walk();
} catch (err) {

View File

@ -1,264 +1,10 @@
#!/usr/bin/env node
import fs from "fs";
import commander from "commander";
import { version } from "@babel/core";
import uniq from "lodash/uniq";
import glob from "glob";
import parseArgv from "./options";
import dirCommand from "./dir";
import fileCommand from "./file";
import pkg from "../../package.json";
const opts = parseArgv(process.argv);
function booleanify(val: any): boolean | any {
if (val === "true" || val == 1) {
return true;
}
if (val === "false" || val == 0 || !val) {
return false;
}
return val;
}
function collect(value, previousValue): Array<string> {
// If the user passed the option with no value, like "babel file.js --presets", do nothing.
if (typeof value !== "string") return previousValue;
const values = value.split(",");
return previousValue ? previousValue.concat(values) : values;
}
// Standard Babel input configs.
commander.option(
"-f, --filename [filename]",
"filename to use when reading from stdin - this will be used in source-maps, errors etc",
);
commander.option(
"--presets [list]",
"comma-separated list of preset names",
collect,
);
commander.option(
"--plugins [list]",
"comma-separated list of plugin names",
collect,
);
commander.option("--config-file [path]", "Path a to .babelrc file to use");
commander.option(
"--env-name [name]",
"The name of the 'env' to use when loading configs and plugins. " +
"Defaults to the value of BABEL_ENV, or else NODE_ENV, or else 'development'.",
);
// Basic file input configuration.
commander.option("--source-type [script|module]", "");
commander.option(
"--no-babelrc",
"Whether or not to look up .babelrc and .babelignore files",
);
commander.option(
"--ignore [list]",
"list of glob paths to **not** compile",
collect,
);
commander.option(
"--only [list]",
"list of glob paths to **only** compile",
collect,
);
// Misc babel config.
commander.option(
"--no-highlight-code",
"enable/disable ANSI syntax highlighting of code frames (on by default)",
);
// General output formatting.
commander.option(
"--no-comments",
"write comments to generated output (true by default)",
);
commander.option(
"--retain-lines",
"retain line numbers - will result in really ugly code",
);
commander.option(
"--compact [true|false|auto]",
"do not include superfluous whitespace characters and line terminators",
booleanify,
);
commander.option("--minified", "save as much bytes when printing [true|false]");
commander.option(
"--auxiliary-comment-before [string]",
"print a comment before any injected non-user code",
);
commander.option(
"--auxiliary-comment-after [string]",
"print a comment after any injected non-user code",
);
// General soucemap formatting.
commander.option("-s, --source-maps [true|false|inline|both]", "", booleanify);
commander.option(
"--source-map-target [string]",
"set `file` on returned source map",
);
commander.option(
"--source-file-name [string]",
"set `sources[0]` on returned source map",
);
commander.option(
"--source-root [filename]",
"the root from which all sources are relative",
);
// Config params for certain module output formats.
commander.option(
"--module-root [filename]",
"optional prefix for the AMD module formatter that will be prepend to the filename on module definitions",
);
commander.option("-M, --module-ids", "insert an explicit id for modules");
commander.option(
"--module-id [string]",
"specify a custom name for module ids",
);
// "babel" command specific arguments that are not passed to @babel/core.
commander.option(
"-x, --extensions [extensions]",
"List of extensions to compile when a directory has been input [.es6,.js,.es,.jsx,.mjs]",
collect,
);
commander.option(
"--keep-file-extension",
"Preserve the file extensions of the input files",
);
commander.option("-w, --watch", "Recompile files on changes");
commander.option(
"--skip-initial-build",
"Do not compile files before watching",
);
commander.option(
"-o, --out-file [out]",
"Compile all input files into a single file",
);
commander.option(
"-d, --out-dir [out]",
"Compile an input directory of modules into an output directory",
);
commander.option(
"--relative",
"Compile into an output directory relative to input directory or file. Requires --out-dir [out]",
);
commander.option(
"-D, --copy-files",
"When compiling a directory copy over non-compilable files",
);
commander.option(
"--include-dotfiles",
"Include dotfiles when compiling and copying non-compilable files",
);
commander.option("--verbose", "Log everything");
commander.option(
"--delete-dir-on-start",
"Delete the out directory before compilation",
);
commander.version(pkg.version + " (@babel/core " + version + ")");
commander.usage("[options] <files ...>");
commander.parse(process.argv);
//
const errors = [];
let filenames = commander.args.reduce(function(globbed, input) {
let files = glob.sync(input);
if (!files.length) files = [input];
return globbed.concat(files);
}, []);
filenames = uniq(filenames);
filenames.forEach(function(filename) {
if (!fs.existsSync(filename)) {
errors.push(filename + " doesn't exist");
}
});
if (commander.outDir && !filenames.length) {
errors.push("filenames required for --out-dir");
}
if (commander.outFile && commander.outDir) {
errors.push("cannot have --out-file and --out-dir");
}
if (commander.relative && !commander.outDir) {
errors.push("output directory required for --relative");
}
if (commander.watch) {
if (!commander.outFile && !commander.outDir) {
errors.push("--watch requires --out-file or --out-dir");
}
if (!filenames.length) {
errors.push("--watch requires filenames");
}
}
if (commander.skipInitialBuild && !commander.watch) {
errors.push("--skip-initial-build requires --watch");
}
if (commander.deleteDirOnStart && !commander.outDir) {
errors.push("--delete-dir-on-start requires --out-dir");
}
if (
!commander.outDir &&
filenames.length === 0 &&
typeof commander.filename !== "string" &&
commander.babelrc !== false
) {
errors.push(
"stdin compilation requires either -f/--filename [filename] or --no-babelrc",
);
}
if (errors.length) {
console.error(errors.join(". "));
process.exit(2);
}
//
const opts = commander.opts();
// Delete options that are specific to @babel/cli and shouldn't be passed to @babel/core.
delete opts.version;
delete opts.extensions;
delete opts.watch;
delete opts.skipInitialBuild;
delete opts.outFile;
delete opts.outDir;
delete opts.copyFiles;
delete opts.includeDotfiles;
delete opts.verbose;
delete opts.deleteDirOnStart;
delete opts.keepFileExtension;
delete opts.relative;
delete opts.sourceMapTarget;
// Commander will default the "--no-" arguments to true, but we want to leave them undefined so that
// @babel/core can handle the default-assignment logic on its own.
if (opts.babelrc === true) opts.babelrc = undefined;
if (opts.comments === true) opts.comments = undefined;
if (opts.highlightCode === true) opts.highlightCode = undefined;
const fn = commander.outDir ? dirCommand : fileCommand;
fn(commander, filenames, opts);
const fn = opts.cliOptions.outDir ? dirCommand : fileCommand;
fn(opts);

View File

@ -0,0 +1,283 @@
import fs from "fs";
import commander from "commander";
import { version } from "@babel/core";
import uniq from "lodash/uniq";
import glob from "glob";
import pkg from "../../package.json";
// Standard Babel input configs.
commander.option(
"-f, --filename [filename]",
"filename to use when reading from stdin - this will be used in source-maps, errors etc",
);
commander.option(
"--presets [list]",
"comma-separated list of preset names",
collect,
);
commander.option(
"--plugins [list]",
"comma-separated list of plugin names",
collect,
);
commander.option("--config-file [path]", "Path a to .babelrc file to use");
commander.option(
"--env-name [name]",
"The name of the 'env' to use when loading configs and plugins. " +
"Defaults to the value of BABEL_ENV, or else NODE_ENV, or else 'development'.",
);
// Basic file input configuration.
commander.option("--source-type [script|module]", "");
commander.option(
"--no-babelrc",
"Whether or not to look up .babelrc and .babelignore files",
);
commander.option(
"--ignore [list]",
"list of glob paths to **not** compile",
collect,
);
commander.option(
"--only [list]",
"list of glob paths to **only** compile",
collect,
);
// Misc babel config.
commander.option(
"--no-highlight-code",
"enable/disable ANSI syntax highlighting of code frames (on by default)",
);
// General output formatting.
commander.option(
"--no-comments",
"write comments to generated output (true by default)",
);
commander.option(
"--retain-lines",
"retain line numbers - will result in really ugly code",
);
commander.option(
"--compact [true|false|auto]",
"do not include superfluous whitespace characters and line terminators",
booleanify,
);
commander.option("--minified", "save as much bytes when printing [true|false]");
commander.option(
"--auxiliary-comment-before [string]",
"print a comment before any injected non-user code",
);
commander.option(
"--auxiliary-comment-after [string]",
"print a comment after any injected non-user code",
);
// General soucemap formatting.
commander.option("-s, --source-maps [true|false|inline|both]", "", booleanify);
commander.option(
"--source-map-target [string]",
"set `file` on returned source map",
);
commander.option(
"--source-file-name [string]",
"set `sources[0]` on returned source map",
);
commander.option(
"--source-root [filename]",
"the root from which all sources are relative",
);
// Config params for certain module output formats.
commander.option(
"--module-root [filename]",
"optional prefix for the AMD module formatter that will be prepend to the filename on module definitions",
);
commander.option("-M, --module-ids", "insert an explicit id for modules");
commander.option(
"--module-id [string]",
"specify a custom name for module ids",
);
// "babel" command specific arguments that are not passed to @babel/core.
commander.option(
"-x, --extensions [extensions]",
"List of extensions to compile when a directory has been input [.es6,.js,.es,.jsx,.mjs]",
collect,
);
commander.option(
"--keep-file-extension",
"Preserve the file extensions of the input files",
);
commander.option("-w, --watch", "Recompile files on changes");
commander.option(
"--skip-initial-build",
"Do not compile files before watching",
);
commander.option(
"-o, --out-file [out]",
"Compile all input files into a single file",
);
commander.option(
"-d, --out-dir [out]",
"Compile an input directory of modules into an output directory",
);
commander.option(
"--relative",
"Compile into an output directory relative to input directory or file. Requires --out-dir [out]",
);
commander.option(
"-D, --copy-files",
"When compiling a directory copy over non-compilable files",
);
commander.option(
"--include-dotfiles",
"Include dotfiles when compiling and copying non-compilable files",
);
commander.option("--verbose", "Log everything");
commander.option(
"--delete-dir-on-start",
"Delete the out directory before compilation",
);
commander.version(pkg.version + " (@babel/core " + version + ")");
commander.usage("[options] <files ...>");
export default function parseArgv(args: Array<string>) {
//
commander.parse(args);
const errors = [];
let filenames = commander.args.reduce(function(globbed, input) {
let files = glob.sync(input);
if (!files.length) files = [input];
return globbed.concat(files);
}, []);
filenames = uniq(filenames);
filenames.forEach(function(filename) {
if (!fs.existsSync(filename)) {
errors.push(filename + " doesn't exist");
}
});
if (commander.outDir && !filenames.length) {
errors.push("filenames required for --out-dir");
}
if (commander.outFile && commander.outDir) {
errors.push("cannot have --out-file and --out-dir");
}
if (commander.relative && !commander.outDir) {
errors.push("output directory required for --relative");
}
if (commander.watch) {
if (!commander.outFile && !commander.outDir) {
errors.push("--watch requires --out-file or --out-dir");
}
if (!filenames.length) {
errors.push("--watch requires filenames");
}
}
if (commander.skipInitialBuild && !commander.watch) {
errors.push("--skip-initial-build requires --watch");
}
if (commander.deleteDirOnStart && !commander.outDir) {
errors.push("--delete-dir-on-start requires --out-dir");
}
if (
!commander.outDir &&
filenames.length === 0 &&
typeof commander.filename !== "string" &&
commander.babelrc !== false
) {
errors.push(
"stdin compilation requires either -f/--filename [filename] or --no-babelrc",
);
}
if (errors.length) {
console.error(errors.join(". "));
process.exit(2);
}
const opts = commander.opts();
return {
babelOptions: {
presets: opts.presets,
plugins: opts.plugins,
configFile: opts.configFile,
envName: opts.envName,
sourceType: opts.sourceType,
ignore: opts.ignore,
only: opts.only,
retainLines: opts.retainLines,
compact: opts.compact,
minified: opts.minified,
auxiliaryCommentBefore: opts.auxiliaryCommentBefore,
auxiliaryCommentAfter: opts.auxiliaryCommentAfter,
sourceMaps: opts.sourceMaps,
sourceFileName: opts.sourceFileName,
sourceRoot: opts.sourceRoot,
moduleRoot: opts.moduleRoot,
moduleIds: opts.moduleIds,
moduleId: opts.moduleId,
// Commander will default the "--no-" arguments to true, but we want to
// leave them undefined so that @babel/core can handle the
// default-assignment logic on its own.
babelrc: opts.babelrc === true ? undefined : opts.babelrc,
highlightCode:
opts.highlightCode === true ? undefined : opts.highlightCode,
comments: opts.comments === true ? undefined : opts.comments,
},
cliOptions: {
filename: opts.filename,
filenames,
extensions: opts.extensions,
keepFileExtension: opts.keepFileExtension,
watch: opts.watch,
skipInitialBuild: opts.skipInitialBuild,
outFile: opts.outFile,
outDir: opts.outDir,
relative: opts.relative,
copyFiles: opts.copyFiles,
includeDotfiles: opts.includeDotfiles,
verbose: opts.verbose,
deleteDirOnStart: opts.deleteDirOnStart,
sourceMapTarget: opts.sourceMapTarget,
},
};
}
function booleanify(val: any): boolean | any {
if (val === "true" || val == 1) {
return true;
}
if (val === "false" || val == 0 || !val) {
return false;
}
return val;
}
function collect(value, previousValue): Array<string> {
// If the user passed the option with no value, like "babel file.js --presets", do nothing.
if (typeof value !== "string") return previousValue;
const values = value.split(",");
return previousValue ? previousValue.concat(values) : values;
}

View File

@ -1,4 +1,3 @@
import commander from "commander";
import readdirRecursive from "fs-readdir-recursive";
import * as babel from "@babel/core";
import includes from "lodash/includes";
@ -46,10 +45,6 @@ export function addSourceMappingUrl(code, loc) {
return code + "\n//# sourceMappingURL=" + path.basename(loc);
}
export function log(msg, force) {
if (force === true || commander.verbose) console.log(msg);
}
export function transform(filename, code, opts, callback) {
opts = {
...opts,
@ -60,17 +55,7 @@ export function transform(filename, code, opts, callback) {
}
export function compile(filename, opts, callback) {
babel.transformFile(filename, opts, function(err, res) {
if (err) {
if (commander.watch) {
console.error(err);
return callback(null, null);
} else {
return callback(err);
}
}
return callback(null, res);
});
babel.transformFile(filename, opts, callback);
}
export function deleteDir(path) {