diff --git a/packages/babel-cli/src/babel/dir.js b/packages/babel-cli/src/babel/dir.js index 6acd1d0512..1f6311b1fd 100644 --- a/packages/babel-cli/src/babel/dir.js +++ b/packages/babel-cli/src/babel/dir.js @@ -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) { diff --git a/packages/babel-cli/src/babel/file.js b/packages/babel-cli/src/babel/file.js index def6aa3eb7..2d043914d8 100644 --- a/packages/babel-cli/src/babel/file.js +++ b/packages/babel-cli/src/babel/file.js @@ -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) { diff --git a/packages/babel-cli/src/babel/index.js b/packages/babel-cli/src/babel/index.js index 696c42fe18..da482b029a 100755 --- a/packages/babel-cli/src/babel/index.js +++ b/packages/babel-cli/src/babel/index.js @@ -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 { - // 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] "); -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); diff --git a/packages/babel-cli/src/babel/options.js b/packages/babel-cli/src/babel/options.js new file mode 100644 index 0000000000..a058d48be1 --- /dev/null +++ b/packages/babel-cli/src/babel/options.js @@ -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] "); + +export default function parseArgv(args: Array) { + // + 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 { + // 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; +} diff --git a/packages/babel-cli/src/babel/util.js b/packages/babel-cli/src/babel/util.js index 9baa0c72ef..f5319b3668 100644 --- a/packages/babel-cli/src/babel/util.js +++ b/packages/babel-cli/src/babel/util.js @@ -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) {