Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better support monorepos by allowing users to opt into automatically resolving 'root' with rootMode: "upward". #8660

Merged
merged 1 commit into from Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/babel-cli/src/babel/options.js
Expand Up @@ -28,6 +28,11 @@ commander.option(
"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'.",
);
commander.option(
"--root-mode [mode]",
"The project-root resolution mode. " +
"One of 'root' (the default), 'upward', or 'upward-optional'.",
xtuc marked this conversation as resolved.
Show resolved Hide resolved
);

// Basic file input configuration.
commander.option("--source-type [script|module]", "");
Expand Down Expand Up @@ -220,6 +225,7 @@ export default function parseArgv(args: Array<string>) {
babelOptions: {
presets: opts.presets,
plugins: opts.plugins,
rootMode: opts.rootMode,
configFile: opts.configFile,
envName: opts.envName,
sourceType: opts.sourceType,
Expand Down
15 changes: 15 additions & 0 deletions packages/babel-core/src/config/files/configuration.js
Expand Up @@ -24,6 +24,21 @@ const BABELRC_FILENAME = ".babelrc";
const BABELRC_JS_FILENAME = ".babelrc.js";
const BABELIGNORE_FILENAME = ".babelignore";

export function findConfigUpwards(rootDir: string): string | null {
let dirname = rootDir;
while (true) {
if (fs.existsSync(path.join(dirname, BABEL_CONFIG_JS_FILENAME))) {
return dirname;
}

const nextDir = path.dirname(dirname);
if (dirname === nextDir) break;
dirname = nextDir;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could set a max limit, we can be almost sure to not find any config after 30 iterations. That will avoid tranversing the whole system in some special setups.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do this type of loop in several places during config resolution. I'm not sure it's worth it to over-optimize up front, especially since you have to opt into the upward-optional mode for it to have been wasted time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that make sense, i'm just worried about some undebuggable issues that it could cause.

}

return null;
}

export function findRelativeConfig(
packageData: FilePackageData,
envName: string,
Expand Down
6 changes: 6 additions & 0 deletions packages/babel-core/src/config/files/index-browser.js
Expand Up @@ -11,6 +11,12 @@ import type { CallerMetadata } from "../validation/options";

export type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData };

export function findConfigUpwards(
rootDir: string, // eslint-disable-line no-unused-vars
): string | null {
return null;
}

export function findPackageData(filepath: string): FilePackageData {
return {
filepath,
Expand Down
1 change: 1 addition & 0 deletions packages/babel-core/src/config/files/index.js
Expand Up @@ -10,6 +10,7 @@ import typeof * as indexType from "./index";
export { findPackageData } from "./package";

export {
findConfigUpwards,
findRelativeConfig,
findRootConfig,
loadConfig,
Expand Down
51 changes: 47 additions & 4 deletions packages/babel-core/src/config/partial.js
Expand Up @@ -6,9 +6,43 @@ import { mergeOptions } from "./util";
import { createItemFromDescriptor } from "./item";
import { buildRootChain, type ConfigContext } from "./config-chain";
import { getEnv } from "./helpers/environment";
import { validate, type ValidatedOptions } from "./validation/options";
import {
validate,
type ValidatedOptions,
type RootMode,
} from "./validation/options";

import { findConfigUpwards, type ConfigFile, type IgnoreFile } from "./files";

function resolveRootMode(rootDir: string, rootMode: RootMode): string {
switch (rootMode) {
case "root":
return rootDir;

case "upward-optional": {
const upwardRootDir = findConfigUpwards(rootDir);
return upwardRootDir === null ? rootDir : upwardRootDir;
}

import type { ConfigFile, IgnoreFile } from "./files";
case "upward": {
const upwardRootDir = findConfigUpwards(rootDir);
if (upwardRootDir !== null) return upwardRootDir;

throw Object.assign(
(new Error(
`Babel was run with rootMode:"upward" but a root could not ` +
xtuc marked this conversation as resolved.
Show resolved Hide resolved
`be found when searching upward from "${rootDir}"`,
): any),
{
code: "BABEL_ROOT_NOT_FOUND",
dirname: rootDir,
},
);
}
default:
throw new Error(`Assertion failure - unknown rootMode value`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also link to the website to learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't generally do that with assertions. This is mostly here to satisfy Flow. The user should never see this and if we got here, it means Flow didn't catch a type error somewhere, since it would mean we got a RootMode type that didn't match the type definition.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got you, there's a config validation before that anyway (assertRootMode).

Just FYI I used to use https://github.com/xtuc/mamacro#examples for only assertion, they are quite close to C ones.

}
}

export default function loadPrivatePartialConfig(
inputOpts: mixed,
Expand All @@ -28,9 +62,18 @@ export default function loadPrivatePartialConfig(

const args = inputOpts ? validate("arguments", inputOpts) : {};

const { envName = getEnv(), cwd = ".", root: rootDir = ".", caller } = args;
const {
envName = getEnv(),
cwd = ".",
root: rootDir = ".",
rootMode = "root",
caller,
} = args;
const absoluteCwd = path.resolve(cwd);
const absoluteRootDir = path.resolve(absoluteCwd, rootDir);
const absoluteRootDir = resolveRootMode(
path.resolve(absoluteCwd, rootDir),
rootMode,
);

const context: ConfigContext = {
filename:
Expand Down
15 changes: 15 additions & 0 deletions packages/babel-core/src/config/validation/option-assertions.js
Expand Up @@ -15,6 +15,7 @@ import type {
RootInputSourceMapOption,
NestingPath,
CallerMetadata,
RootMode,
} from "./options";

export type ValidatorSet = {
Expand Down Expand Up @@ -60,6 +61,20 @@ type AccessPath = $ReadOnly<{
}>;
type GeneralPath = OptionPath | AccessPath;

export function assertRootMode(loc: OptionPath, value: mixed): RootMode | void {
if (
value !== undefined &&
value !== "root" &&
value !== "upward" &&
value !== "upward-optional"
) {
throw new Error(
`${msg(loc)} must be a "root", "upward", "upward-optional" or undefined`,
);
}
return value;
}

export function assertSourceMaps(
loc: OptionPath,
value: mixed,
Expand Down
6 changes: 6 additions & 0 deletions packages/babel-core/src/config/validation/options.js
Expand Up @@ -19,6 +19,7 @@ import {
assertConfigFileSearch,
assertBabelrcSearch,
assertFunction,
assertRootMode,
assertSourceMaps,
assertCompact,
assertSourceType,
Expand All @@ -30,6 +31,9 @@ import {
const ROOT_VALIDATORS: ValidatorSet = {
cwd: (assertString: Validator<$PropertyType<ValidatedOptions, "cwd">>),
root: (assertString: Validator<$PropertyType<ValidatedOptions, "root">>),
rootMode: (assertRootMode: Validator<
$PropertyType<ValidatedOptions, "rootMode">,
>),
configFile: (assertConfigFileSearch: Validator<
$PropertyType<ValidatedOptions, "configFile">,
>),
Expand Down Expand Up @@ -176,6 +180,7 @@ export type ValidatedOptions = {
babelrcRoots?: BabelrcSearch,
configFile?: ConfigFileSearch,
root?: string,
rootMode?: RootMode,
code?: boolean,
ast?: boolean,
inputSourceMap?: RootInputSourceMapOption,
Expand Down Expand Up @@ -260,6 +265,7 @@ export type SourceMapsOption = boolean | "inline" | "both";
export type SourceTypeOption = "module" | "script" | "unambiguous";
export type CompactOption = boolean | "auto";
export type RootInputSourceMapOption = {} | boolean;
export type RootMode = "root" | "upward" | "upward-optional";

export type OptionsSource =
| "arguments"
Expand Down