Restrict .babelrc resolution to within a given package.
This commit is contained in:
parent
e45b58dcb1
commit
f013dab5fb
@ -13,6 +13,7 @@ import {
|
||||
const debug = buildDebug("babel:config:config-chain");
|
||||
|
||||
import {
|
||||
findPackageData,
|
||||
findRelativeConfig,
|
||||
loadConfig,
|
||||
type ConfigFile,
|
||||
@ -125,15 +126,17 @@ export function buildRootChain(
|
||||
);
|
||||
if (!programmaticChain) return null;
|
||||
|
||||
let ignore, babelrc;
|
||||
const pkgData =
|
||||
typeof context.filename === "string"
|
||||
? findPackageData(context.filename)
|
||||
: null;
|
||||
|
||||
let ignore, babelrc;
|
||||
const fileChain = emptyChain();
|
||||
// resolve all .babelrc files
|
||||
if (opts.babelrc !== false && context.filename !== null) {
|
||||
const filename = context.filename;
|
||||
|
||||
if (opts.babelrc !== false && pkgData) {
|
||||
({ ignore, config: babelrc } = findRelativeConfig(
|
||||
filename,
|
||||
pkgData,
|
||||
context.envName,
|
||||
));
|
||||
|
||||
|
||||
@ -5,45 +5,33 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import json5 from "json5";
|
||||
import resolve from "resolve";
|
||||
import { makeStrongCache, type CacheConfigurator } from "../caching";
|
||||
import {
|
||||
makeStrongCache,
|
||||
makeWeakCache,
|
||||
type CacheConfigurator,
|
||||
} from "../caching";
|
||||
import makeAPI from "../helpers/config-api";
|
||||
import { makeStaticFileCache } from "./utils";
|
||||
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";
|
||||
|
||||
const debug = buildDebug("babel:config:loading:files:configuration");
|
||||
|
||||
export type ConfigFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
options: {},
|
||||
};
|
||||
|
||||
export type IgnoreFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
ignore: Array<string>,
|
||||
};
|
||||
|
||||
export type RelativeConfig = {
|
||||
config: ConfigFile | null,
|
||||
ignore: IgnoreFile | null,
|
||||
};
|
||||
|
||||
const BABELRC_FILENAME = ".babelrc";
|
||||
const BABELRC_JS_FILENAME = ".babelrc.js";
|
||||
const PACKAGE_FILENAME = "package.json";
|
||||
const BABELIGNORE_FILENAME = ".babelignore";
|
||||
|
||||
export function findRelativeConfig(
|
||||
filepath: string,
|
||||
packageData: FilePackageData,
|
||||
envName: string,
|
||||
): RelativeConfig {
|
||||
let config = null;
|
||||
let ignore = null;
|
||||
|
||||
const dirname = path.dirname(filepath);
|
||||
let loc = dirname;
|
||||
while (true) {
|
||||
const dirname = path.dirname(packageData.filepath);
|
||||
|
||||
for (const loc of packageData.directories) {
|
||||
if (!config) {
|
||||
config = [BABELRC_FILENAME, BABELRC_JS_FILENAME, PACKAGE_FILENAME].reduce(
|
||||
config = [BABELRC_FILENAME, BABELRC_JS_FILENAME].reduce(
|
||||
(previousConfig: ConfigFile | null, name) => {
|
||||
const filepath = path.join(loc, name);
|
||||
const config = readConfig(filepath, envName);
|
||||
@ -62,6 +50,23 @@ export function findRelativeConfig(
|
||||
null,
|
||||
);
|
||||
|
||||
const pkgConfig =
|
||||
packageData.pkg && packageData.pkg.dirname === loc
|
||||
? packageToBabelConfig(packageData.pkg)
|
||||
: null;
|
||||
|
||||
if (pkgConfig) {
|
||||
if (config) {
|
||||
throw new Error(
|
||||
`Multiple configuration files found. Please remove one:\n` +
|
||||
` - ${path.basename(pkgConfig.filepath)}#babel\n` +
|
||||
` - ${path.basename(config.filepath)}\n` +
|
||||
`from ${loc}`,
|
||||
);
|
||||
}
|
||||
config = pkgConfig;
|
||||
}
|
||||
|
||||
if (config) {
|
||||
debug("Found configuration %o from %o.", config.filepath, dirname);
|
||||
}
|
||||
@ -75,10 +80,6 @@ export function findRelativeConfig(
|
||||
debug("Found ignore %o from %o.", ignore.filepath, dirname);
|
||||
}
|
||||
}
|
||||
|
||||
const nextLoc = path.dirname(loc);
|
||||
if (loc === nextLoc) break;
|
||||
loc = nextLoc;
|
||||
}
|
||||
|
||||
return { config, ignore };
|
||||
@ -107,7 +108,7 @@ export function loadConfig(
|
||||
function readConfig(filepath, envName): ConfigFile | null {
|
||||
return path.extname(filepath) === ".js"
|
||||
? readConfigJS(filepath, { envName })
|
||||
: readConfigFile(filepath);
|
||||
: readConfigJSON5(filepath);
|
||||
}
|
||||
|
||||
const LOADING_CONFIGS = new Set();
|
||||
@ -180,27 +181,34 @@ const readConfigJS = makeStrongCache(
|
||||
},
|
||||
);
|
||||
|
||||
const readConfigFile = makeStaticFileCache((filepath, content) => {
|
||||
let options;
|
||||
if (path.basename(filepath) === PACKAGE_FILENAME) {
|
||||
try {
|
||||
options = JSON.parse(content).babel;
|
||||
} catch (err) {
|
||||
err.message = `${filepath}: Error while parsing JSON - ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
if (!options) return null;
|
||||
} else {
|
||||
try {
|
||||
options = json5.parse(content);
|
||||
} catch (err) {
|
||||
err.message = `${filepath}: Error while parsing config - ${err.message}`;
|
||||
throw err;
|
||||
const packageToBabelConfig = makeWeakCache(
|
||||
(file: ConfigFile): ConfigFile | null => {
|
||||
if (typeof file.options.babel === "undefined") return null;
|
||||
const babel = file.options.babel;
|
||||
|
||||
if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
|
||||
throw new Error(`${file.filepath}: .babel property must be an object`);
|
||||
}
|
||||
|
||||
if (!options) throw new Error(`${filepath}: No config detected`);
|
||||
return {
|
||||
filepath: file.filepath,
|
||||
dirname: file.dirname,
|
||||
options: babel,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const readConfigJSON5 = makeStaticFileCache((filepath, content) => {
|
||||
let options;
|
||||
try {
|
||||
options = json5.parse(content);
|
||||
} catch (err) {
|
||||
err.message = `${filepath}: Error while parsing config - ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!options) throw new Error(`${filepath}: No config detected`);
|
||||
|
||||
if (typeof options !== "object") {
|
||||
throw new Error(`${filepath}: Config returned typeof ${typeof options}`);
|
||||
}
|
||||
@ -228,27 +236,6 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => {
|
||||
};
|
||||
});
|
||||
|
||||
function makeStaticFileCache<T>(fn: (string, string) => T): string => T | null {
|
||||
return makeStrongCache((filepath, cache) => {
|
||||
if (cache.invalidate(() => fileMtime(filepath)) === null) {
|
||||
cache.forever();
|
||||
return null;
|
||||
}
|
||||
|
||||
return fn(filepath, fs.readFileSync(filepath, "utf8"));
|
||||
});
|
||||
}
|
||||
|
||||
function fileMtime(filepath: string): number | null {
|
||||
try {
|
||||
return +fs.statSync(filepath).mtime;
|
||||
} catch (e) {
|
||||
if (e.code !== "ENOENT") throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function throwConfigError() {
|
||||
throw new Error(`\
|
||||
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
|
||||
|
||||
@ -1,17 +1,35 @@
|
||||
// @flow
|
||||
|
||||
import type { ConfigFile, IgnoreFile, RelativeConfig } from "./configuration";
|
||||
import type {
|
||||
ConfigFile,
|
||||
IgnoreFile,
|
||||
RelativeConfig,
|
||||
FilePackageData,
|
||||
} from "./types";
|
||||
|
||||
export type { ConfigFile, IgnoreFile, RelativeConfig };
|
||||
export type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData };
|
||||
|
||||
export function findRelativeConfig(
|
||||
filepath: string,
|
||||
envName: string, // eslint-disable-line no-unused-vars
|
||||
): RelativeConfig {
|
||||
return { config: null, ignore: null };
|
||||
export function findPackageData(filepath: string): FilePackageData {
|
||||
return {
|
||||
filepath,
|
||||
directories: [],
|
||||
pkg: null,
|
||||
isPackage: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadConfig(name: string, dirname: string): ConfigFile {
|
||||
export function findRelativeConfig(
|
||||
pkgData: FilePackageData,
|
||||
envName: string, // eslint-disable-line no-unused-vars
|
||||
): RelativeConfig {
|
||||
return { pkg: null, config: null, ignore: null };
|
||||
}
|
||||
|
||||
export function loadConfig(
|
||||
name: string,
|
||||
dirname: string,
|
||||
envName: string, // eslint-disable-line no-unused-vars
|
||||
): ConfigFile {
|
||||
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
|
||||
}
|
||||
|
||||
|
||||
@ -7,5 +7,18 @@ import typeof * as indexType from "./index";
|
||||
// exports of index-browser, since this file may be replaced at bundle time with index-browser.
|
||||
((({}: any): $Exact<indexBrowserType>): $Exact<indexType>);
|
||||
|
||||
export * from "./configuration";
|
||||
export * from "./plugins";
|
||||
export { findPackageData } from "./package";
|
||||
|
||||
export { findRelativeConfig, loadConfig } from "./configuration";
|
||||
export type {
|
||||
ConfigFile,
|
||||
IgnoreFile,
|
||||
RelativeConfig,
|
||||
FilePackageData,
|
||||
} from "./types";
|
||||
export {
|
||||
resolvePlugin,
|
||||
resolvePreset,
|
||||
loadPlugin,
|
||||
loadPreset,
|
||||
} from "./plugins";
|
||||
|
||||
60
packages/babel-core/src/config/files/package.js
Normal file
60
packages/babel-core/src/config/files/package.js
Normal file
@ -0,0 +1,60 @@
|
||||
// @flow
|
||||
|
||||
import path from "path";
|
||||
import { makeStaticFileCache } from "./utils";
|
||||
|
||||
import type { ConfigFile, FilePackageData } from "./types";
|
||||
|
||||
const PACKAGE_FILENAME = "package.json";
|
||||
|
||||
/**
|
||||
* Find metadata about the package that this file is inside of. Resolution
|
||||
* of Babel's config requires general package information to decide when to
|
||||
* search for .babelrc files
|
||||
*/
|
||||
export function findPackageData(filepath: string): FilePackageData {
|
||||
let pkg = null;
|
||||
const directories = [];
|
||||
let isPackage = true;
|
||||
|
||||
let dirname = path.dirname(filepath);
|
||||
while (!pkg && path.basename(dirname) !== "node_modules") {
|
||||
directories.push(dirname);
|
||||
|
||||
pkg = readConfigPackage(path.join(dirname, PACKAGE_FILENAME));
|
||||
|
||||
const nextLoc = path.dirname(dirname);
|
||||
if (dirname === nextLoc) {
|
||||
isPackage = false;
|
||||
break;
|
||||
}
|
||||
dirname = nextLoc;
|
||||
}
|
||||
|
||||
return { filepath, directories, pkg, isPackage };
|
||||
}
|
||||
|
||||
const readConfigPackage = makeStaticFileCache(
|
||||
(filepath, content): ConfigFile => {
|
||||
let options;
|
||||
try {
|
||||
options = JSON.parse(content);
|
||||
} catch (err) {
|
||||
err.message = `${filepath}: Error while parsing JSON - ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (typeof options !== "object") {
|
||||
throw new Error(`${filepath}: Config returned typeof ${typeof options}`);
|
||||
}
|
||||
if (Array.isArray(options)) {
|
||||
throw new Error(`${filepath}: Expected config object but found array`);
|
||||
}
|
||||
|
||||
return {
|
||||
filepath,
|
||||
dirname: path.dirname(filepath),
|
||||
options,
|
||||
};
|
||||
},
|
||||
);
|
||||
38
packages/babel-core/src/config/files/types.js
Normal file
38
packages/babel-core/src/config/files/types.js
Normal file
@ -0,0 +1,38 @@
|
||||
// @flow
|
||||
|
||||
export type ConfigFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
options: {},
|
||||
};
|
||||
|
||||
export type IgnoreFile = {
|
||||
filepath: string,
|
||||
dirname: string,
|
||||
ignore: Array<string>,
|
||||
};
|
||||
|
||||
export type RelativeConfig = {
|
||||
// The actual config, either from package.json#babel, .babelrc, or
|
||||
// .babelrc.js, if there was one.
|
||||
config: ConfigFile | null,
|
||||
|
||||
// The .babelignore, if there was one.
|
||||
ignore: IgnoreFile | null,
|
||||
};
|
||||
|
||||
export type FilePackageData = {
|
||||
// The file in the package.
|
||||
filepath: string,
|
||||
|
||||
// Any ancestor directories of the file that are within the package.
|
||||
directories: Array<string>,
|
||||
|
||||
// The contents of the package.json. May not be found if the package just
|
||||
// terminated at a node_modules folder without finding one.
|
||||
pkg: ConfigFile | null,
|
||||
|
||||
// True if a package.json or node_modules folder was found while traversing
|
||||
// the directory structure.
|
||||
isPackage: boolean,
|
||||
};
|
||||
27
packages/babel-core/src/config/files/utils.js
Normal file
27
packages/babel-core/src/config/files/utils.js
Normal file
@ -0,0 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import fs from "fs";
|
||||
import { makeStrongCache } from "../caching";
|
||||
|
||||
export function makeStaticFileCache<T>(
|
||||
fn: (string, string) => T,
|
||||
): string => T | null {
|
||||
return makeStrongCache((filepath, cache) => {
|
||||
if (cache.invalidate(() => fileMtime(filepath)) === null) {
|
||||
cache.forever();
|
||||
return null;
|
||||
}
|
||||
|
||||
return fn(filepath, fs.readFileSync(filepath, "utf8"));
|
||||
});
|
||||
}
|
||||
|
||||
function fileMtime(filepath: string): number | null {
|
||||
try {
|
||||
return +fs.statSync(filepath).mtime;
|
||||
} catch (e) {
|
||||
if (e.code !== "ENOENT" && e.code !== "ENOTDIR") throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user