diff --git a/packages/babel-core/src/config/loading/files/configuration.js b/packages/babel-core/src/config/loading/files/configuration.js index 688fe8e068..861ab73039 100644 --- a/packages/babel-core/src/config/loading/files/configuration.js +++ b/packages/babel-core/src/config/loading/files/configuration.js @@ -99,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 = @@ -116,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 ca9e7d6410..c1a76bed59 100644 --- a/packages/babel-core/src/config/loading/files/plugins.js +++ b/packages/babel-core/src/config/loading/files/plugins.js @@ -35,7 +35,7 @@ export function loadPlugin( throw new Error(`Plugin ${name} not found relative to ${dirname}`); } - const value = requireModule(filepath); + const value = requireModule("plugin", filepath); debug("Loaded plugin %o from %o.", name, dirname); return { filepath, value }; @@ -50,7 +50,7 @@ export function loadPreset( throw new Error(`Preset ${name} not found relative to ${dirname}`); } - const value = requireModule(filepath); + const value = requireModule("preset", filepath); debug("Loaded preset %o from %o.", name, dirname); @@ -63,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( @@ -91,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( @@ -195,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); + } }