Merge pull request #5586 from loganfsmyth/config-dependency-cycles

Handle cycles of plugins compiling themselves and .babelrc.js files loading themselves
This commit is contained in:
Logan Smyth 2017-08-29 15:11:51 -07:00 committed by GitHub
commit d79a7920a1
3 changed files with 84 additions and 17 deletions

View File

@ -3,6 +3,9 @@
import { getEnv } from "./helpers/environment"; import { getEnv } from "./helpers/environment";
import path from "path"; import path from "path";
import micromatch from "micromatch"; import micromatch from "micromatch";
import buildDebug from "debug";
const debug = buildDebug("babel:config:config-chain");
import { findConfigs, loadConfig } from "./loading/files"; 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) { 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; return false;

View File

@ -1,5 +1,6 @@
// @flow // @flow
import buildDebug from "debug";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import json5 from "json5"; import json5 from "json5";
@ -7,6 +8,8 @@ import resolve from "resolve";
import { getEnv } from "../../helpers/environment"; import { getEnv } from "../../helpers/environment";
import { makeStrongCache } from "../../caching"; import { makeStrongCache } from "../../caching";
const debug = buildDebug("babel:config:loading:files:configuration");
type ConfigFile = { type ConfigFile = {
filepath: string, filepath: string,
dirname: string, dirname: string,
@ -31,6 +34,7 @@ export function findConfigs(dirname: string): Array<ConfigFile> {
const ignore = readIgnoreConfig(ignoreLoc); const ignore = readIgnoreConfig(ignoreLoc);
if (ignore) { if (ignore) {
debug("Found ignore %o from %o.", ignore.filepath, dirname);
confs.push(ignore); confs.push(ignore);
foundIgnore = true; foundIgnore = true;
} }
@ -57,6 +61,7 @@ export function findConfigs(dirname: string): Array<ConfigFile> {
}, null); }, null);
if (conf) { if (conf) {
debug("Found configuration %o from %o.", conf.filepath, dirname);
confs.push(conf); confs.push(conf);
foundConfig = true; foundConfig = true;
} }
@ -80,6 +85,7 @@ export function loadConfig(name: string, dirname: string): ConfigFile {
throw new Error(`Config file ${filepath} contains no configuration data`); throw new Error(`Config file ${filepath} contains no configuration data`);
} }
debug("Loaded config %o from $o.", name, dirname);
return conf; return conf;
} }
@ -93,14 +99,31 @@ function readConfig(filepath) {
: readConfigFile(filepath); : readConfigFile(filepath);
} }
const LOADING_CONFIGS = new Set();
const readConfigJS = makeStrongCache((filepath, cache) => { const readConfigJS = makeStrongCache((filepath, cache) => {
if (!fs.existsSync(filepath)) { if (!fs.existsSync(filepath)) {
cache.forever(); cache.forever();
return null; 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; let options;
try { try {
LOADING_CONFIGS.add(filepath);
// $FlowIssue // $FlowIssue
const configModule = (require(filepath): mixed); const configModule = (require(filepath): mixed);
options = options =
@ -110,6 +133,8 @@ const readConfigJS = makeStrongCache((filepath, cache) => {
} catch (err) { } catch (err) {
err.message = `${filepath}: Error while loading config - ${err.message}`; err.message = `${filepath}: Error while loading config - ${err.message}`;
throw err; throw err;
} finally {
LOADING_CONFIGS.delete(filepath);
} }
if (typeof options === "function") { if (typeof options === "function") {

View File

@ -4,9 +4,12 @@
* This file handles all logic for converting string-based configuration references into loaded objects. * This file handles all logic for converting string-based configuration references into loaded objects.
*/ */
import buildDebug from "debug";
import resolve from "resolve"; import resolve from "resolve";
import path from "path"; import path from "path";
const debug = buildDebug("babel:config:loading:files:plugins");
const EXACT_RE = /^module:/; const EXACT_RE = /^module:/;
const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/\/]+[/\/]|babel-plugin-)/; const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/\/]+[/\/]|babel-plugin-)/;
const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/\/]+[/\/]|babel-preset-)/; 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}`); throw new Error(`Plugin ${name} not found relative to ${dirname}`);
} }
return { const value = requireModule("plugin", filepath);
filepath, debug("Loaded plugin %o from %o.", name, dirname);
value: requireModule(filepath),
}; return { filepath, value };
} }
export function loadPreset( export function loadPreset(
@ -47,10 +50,11 @@ export function loadPreset(
throw new Error(`Preset ${name} not found relative to ${dirname}`); throw new Error(`Preset ${name} not found relative to ${dirname}`);
} }
return { const value = requireModule("preset", filepath);
filepath,
value: requireModule(filepath), debug("Loaded preset %o from %o.", name, dirname);
};
return { filepath, value };
} }
export function loadParser( export function loadParser(
@ -59,7 +63,7 @@ export function loadParser(
): { filepath: string, value: Function } { ): { filepath: string, value: Function } {
const filepath = resolve.sync(name, { basedir: dirname }); const filepath = resolve.sync(name, { basedir: dirname });
const mod = requireModule(filepath); const mod = requireModule("parser", filepath);
if (!mod) { if (!mod) {
throw new Error( throw new Error(
@ -71,10 +75,13 @@ export function loadParser(
`Parser ${name} relative to ${dirname} does not export a .parse function`, `Parser ${name} relative to ${dirname} does not export a .parse function`,
); );
} }
const value = mod.parse;
debug("Loaded parser %o from %o.", name, dirname);
return { return {
filepath, filepath,
value: mod.parse, value,
}; };
} }
@ -84,7 +91,7 @@ export function loadGenerator(
): { filepath: string, value: Function } { ): { filepath: string, value: Function } {
const filepath = resolve.sync(name, { basedir: dirname }); const filepath = resolve.sync(name, { basedir: dirname });
const mod = requireModule(filepath); const mod = requireModule("generator", filepath);
if (!mod) { if (!mod) {
throw new Error( throw new Error(
@ -96,10 +103,13 @@ export function loadGenerator(
`Generator ${name} relative to ${dirname} does not export a .print function`, `Generator ${name} relative to ${dirname} does not export a .print function`,
); );
} }
const value = mod.print;
debug("Loaded generator %o from %o.", name, dirname);
return { return {
filepath, filepath,
value: mod.print, value,
}; };
} }
@ -185,7 +195,20 @@ function resolveStandardizedName(
} }
} }
function requireModule(name: string): mixed { const LOADING_MODULES = new Set();
// $FlowIssue function requireModule(type: string, name: string): mixed {
return require(name); 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);
}
} }