diff --git a/packages/babel-core/src/config/build-config-chain.js b/packages/babel-core/src/config/build-config-chain.js index dd10f3166c..d52a75666c 100644 --- a/packages/babel-core/src/config/build-config-chain.js +++ b/packages/babel-core/src/config/build-config-chain.js @@ -3,6 +3,9 @@ import { getEnv } from "./helpers/environment"; import path from "path"; import micromatch from "micromatch"; +import buildDebug from "debug"; + +const debug = buildDebug("babel:config:config-chain"); import { findConfigs, loadConfig } from "./loading/files"; @@ -67,7 +70,15 @@ class ConfigChainBuilder { ); } - if (this.matchesPatterns(ignore, dirname)) return true; + if (this.matchesPatterns(ignore, dirname)) { + debug( + "Ignored %o because it matched one of %O from %o", + this.filename, + ignore, + dirname, + ); + return true; + } } if (only) { @@ -77,7 +88,15 @@ class ConfigChainBuilder { ); } - if (!this.matchesPatterns(only, dirname)) return true; + if (!this.matchesPatterns(only, dirname)) { + debug( + "Ignored %o because it failed to match one of %O from %o", + this.filename, + only, + dirname, + ); + return true; + } } return false; diff --git a/packages/babel-core/src/config/loading/files/configuration.js b/packages/babel-core/src/config/loading/files/configuration.js index 73ce1565d4..861ab73039 100644 --- a/packages/babel-core/src/config/loading/files/configuration.js +++ b/packages/babel-core/src/config/loading/files/configuration.js @@ -1,5 +1,6 @@ // @flow +import buildDebug from "debug"; import path from "path"; import fs from "fs"; import json5 from "json5"; @@ -7,6 +8,8 @@ import resolve from "resolve"; import { getEnv } from "../../helpers/environment"; import { makeStrongCache } from "../../caching"; +const debug = buildDebug("babel:config:loading:files:configuration"); + type ConfigFile = { filepath: string, dirname: string, @@ -31,6 +34,7 @@ export function findConfigs(dirname: string): Array { const ignore = readIgnoreConfig(ignoreLoc); if (ignore) { + debug("Found ignore %o from %o.", ignore.filepath, dirname); confs.push(ignore); foundIgnore = true; } @@ -57,6 +61,7 @@ export function findConfigs(dirname: string): Array { }, null); if (conf) { + debug("Found configuration %o from %o.", conf.filepath, dirname); confs.push(conf); foundConfig = true; } @@ -80,6 +85,7 @@ export function loadConfig(name: string, dirname: string): ConfigFile { throw new Error(`Config file ${filepath} contains no configuration data`); } + debug("Loaded config %o from $o.", name, dirname); return conf; } @@ -93,14 +99,31 @@ function readConfig(filepath) { : readConfigFile(filepath); } +const LOADING_CONFIGS = new Set(); const readConfigJS = makeStrongCache((filepath, cache) => { if (!fs.existsSync(filepath)) { cache.forever(); return null; } + // The `require()` call below can make this code reentrant if a require hook like babel-register has been + // loaded into the system. That would cause Babel to attempt to compile the `.babelrc.js` file as it loads + // below. To cover this case, we auto-ignore re-entrant config processing. + if (LOADING_CONFIGS.has(filepath)) { + cache.never(); + + debug("Auto-ignoring usage of config %o.", filepath); + return { + filepath, + dirname: path.dirname(filepath), + options: {}, + }; + } + let options; try { + LOADING_CONFIGS.add(filepath); + // $FlowIssue const configModule = (require(filepath): mixed); options = @@ -110,6 +133,8 @@ const readConfigJS = makeStrongCache((filepath, cache) => { } catch (err) { err.message = `${filepath}: Error while loading config - ${err.message}`; throw err; + } finally { + LOADING_CONFIGS.delete(filepath); } if (typeof options === "function") { diff --git a/packages/babel-core/src/config/loading/files/plugins.js b/packages/babel-core/src/config/loading/files/plugins.js index ff65ae8f56..c1a76bed59 100644 --- a/packages/babel-core/src/config/loading/files/plugins.js +++ b/packages/babel-core/src/config/loading/files/plugins.js @@ -4,9 +4,12 @@ * This file handles all logic for converting string-based configuration references into loaded objects. */ +import buildDebug from "debug"; import resolve from "resolve"; import path from "path"; +const debug = buildDebug("babel:config:loading:files:plugins"); + const EXACT_RE = /^module:/; const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/\/]+[/\/]|babel-plugin-)/; const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/\/]+[/\/]|babel-preset-)/; @@ -32,10 +35,10 @@ export function loadPlugin( throw new Error(`Plugin ${name} not found relative to ${dirname}`); } - return { - filepath, - value: requireModule(filepath), - }; + const value = requireModule("plugin", filepath); + debug("Loaded plugin %o from %o.", name, dirname); + + return { filepath, value }; } export function loadPreset( @@ -47,10 +50,11 @@ export function loadPreset( throw new Error(`Preset ${name} not found relative to ${dirname}`); } - return { - filepath, - value: requireModule(filepath), - }; + const value = requireModule("preset", filepath); + + debug("Loaded preset %o from %o.", name, dirname); + + return { filepath, value }; } export function loadParser( @@ -59,7 +63,7 @@ export function loadParser( ): { filepath: string, value: Function } { const filepath = resolve.sync(name, { basedir: dirname }); - const mod = requireModule(filepath); + const mod = requireModule("parser", filepath); if (!mod) { throw new Error( @@ -71,10 +75,13 @@ export function loadParser( `Parser ${name} relative to ${dirname} does not export a .parse function`, ); } + const value = mod.parse; + + debug("Loaded parser %o from %o.", name, dirname); return { filepath, - value: mod.parse, + value, }; } @@ -84,7 +91,7 @@ export function loadGenerator( ): { filepath: string, value: Function } { const filepath = resolve.sync(name, { basedir: dirname }); - const mod = requireModule(filepath); + const mod = requireModule("generator", filepath); if (!mod) { throw new Error( @@ -96,10 +103,13 @@ export function loadGenerator( `Generator ${name} relative to ${dirname} does not export a .print function`, ); } + const value = mod.print; + + debug("Loaded generator %o from %o.", name, dirname); return { filepath, - value: mod.print, + value, }; } @@ -185,7 +195,20 @@ function resolveStandardizedName( } } -function requireModule(name: string): mixed { - // $FlowIssue - return require(name); +const LOADING_MODULES = new Set(); +function requireModule(type: string, name: string): mixed { + if (LOADING_MODULES.has(name)) { + throw new Error( + // eslint-disable-next-line max-len + `Reentrant ${type} detected trying to load "${name}". This module is not ignored and is trying to load itself while compiling itself, leading to a dependency cycle. We recommend adding it to your "ignore" list in your babelrc, or to a .babelignore.`, + ); + } + + try { + LOADING_MODULES.add(name); + // $FlowIssue + return require(name); + } finally { + LOADING_MODULES.delete(name); + } }