Skip to content

Commit

Permalink
Add targets and browserslist* options to @babel/core
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Oct 15, 2020
1 parent 6cb0056 commit 96e0bbf
Show file tree
Hide file tree
Showing 16 changed files with 350 additions and 33 deletions.
3 changes: 3 additions & 0 deletions packages/babel-core/package.json
Expand Up @@ -38,13 +38,16 @@
},
"browser": {
"./lib/config/files/index.js": "./lib/config/files/index-browser.js",
"./lib/config/resolve-targets.js": "./lib/config/resolve-targets-browser.js",
"./lib/transform-file.js": "./lib/transform-file-browser.js",
"./src/config/files/index.js": "./src/config/files/index-browser.js",
"./src/config/resolve-targets.js": "./src/config/resolve-targets-browser.js",
"./src/transform-file.js": "./src/transform-file-browser.js"
},
"dependencies": {
"@babel/code-frame": "workspace:^7.10.4",
"@babel/generator": "workspace:^7.12.0",
"@babel/helper-compilation-targets": "workspace:^7.12.0",
"@babel/helper-module-transforms": "workspace:^7.12.0",
"@babel/helpers": "workspace:^7.10.4",
"@babel/parser": "workspace:^7.12.0",
Expand Down
15 changes: 13 additions & 2 deletions packages/babel-core/src/config/config-chain.js
Expand Up @@ -3,6 +3,7 @@
import path from "path";
import buildDebug from "debug";
import type { Handler } from "gensync";
import getTargets from "@babel/helper-compilation-targets";
import {
validate,
type ValidatedOptions,
Expand Down Expand Up @@ -678,7 +679,7 @@ function emptyChain(): ConfigChain {
}

function normalizeOptions(opts: ValidatedOptions): ValidatedOptions {
const options = {
const options: ValidatedOptions = {
...opts,
};
delete options.extends;
Expand All @@ -699,7 +700,17 @@ function normalizeOptions(opts: ValidatedOptions): ValidatedOptions {
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
return options;

const { targets } = options;
if (typeof targets === "string") {
options.targets = getTargets({ browsers: [targets] });
} else if (Array.isArray(targets)) {
options.targets = getTargets({ browsers: targets });
} else if (targets) {
options.targets = getTargets((targets: any));
}

return (options: any);
}

function dedupDescriptors(
Expand Down
56 changes: 32 additions & 24 deletions packages/babel-core/src/config/partial.js
Expand Up @@ -14,6 +14,7 @@ import { getEnv } from "./helpers/environment";
import {
validate,
type ValidatedOptions,
type NormalizedOptions,
type RootMode,
} from "./validation/options";

Expand All @@ -24,6 +25,7 @@ import {
type ConfigFile,
type IgnoreFile,
} from "./files";
import { resolveTargets } from "./resolve-targets";

function* resolveRootMode(
rootDir: string,
Expand Down Expand Up @@ -61,7 +63,7 @@ function* resolveRootMode(
}

type PrivPartialConfig = {
options: ValidatedOptions,
options: NormalizedOptions,
context: ConfigContext,
fileHandling: FileHandling,
ignore: IgnoreFile | void,
Expand Down Expand Up @@ -115,30 +117,36 @@ export default function* loadPrivatePartialConfig(
const configChain = yield* buildRootChain(args, context);
if (!configChain) return null;

const options = {};
const merged: ValidatedOptions = {};
configChain.options.forEach(opts => {
mergeOptions(options, opts);
mergeOptions((merged: any), opts);
});

// Tack the passes onto the object itself so that, if this object is
// passed back to Babel a second time, it will be in the right structure
// to not change behavior.
options.cloneInputAst = cloneInputAst;
options.babelrc = false;
options.configFile = false;
options.passPerPreset = false;
options.envName = context.envName;
options.cwd = context.cwd;
options.root = context.root;
options.filename =
typeof context.filename === "string" ? context.filename : undefined;

options.plugins = configChain.plugins.map(descriptor =>
createItemFromDescriptor(descriptor),
);
options.presets = configChain.presets.map(descriptor =>
createItemFromDescriptor(descriptor),
);
const options: NormalizedOptions = {
...merged,
targets: resolveTargets(merged, absoluteRootDir, filename),

// Tack the passes onto the object itself so that, if this object is
// passed back to Babel a second time, it will be in the right structure
// to not change behavior.
cloneInputAst,
babelrc: false,
configFile: false,
browserslistConfigFile: false,
passPerPreset: false,
envName: context.envName,
cwd: context.cwd,
root: context.root,
filename:
typeof context.filename === "string" ? context.filename : undefined,

plugins: configChain.plugins.map(descriptor =>
createItemFromDescriptor(descriptor),
),
presets: configChain.presets.map(descriptor =>
createItemFromDescriptor(descriptor),
),
};

return {
options,
Expand Down Expand Up @@ -195,15 +203,15 @@ class PartialConfig {
* These properties are public, so any changes to them should be considered
* a breaking change to Babel's API.
*/
options: ValidatedOptions;
options: NormalizedOptions;
babelrc: string | void;
babelignore: string | void;
config: string | void;
fileHandling: FileHandling;
files: Set<string>;

constructor(
options: ValidatedOptions,
options: NormalizedOptions,
babelrc: string | void,
ignore: string | void,
config: string | void,
Expand Down
22 changes: 22 additions & 0 deletions packages/babel-core/src/config/resolve-targets-browser.js
@@ -0,0 +1,22 @@
// @flow

import type { ValidatedOptions } from "./validation/options";
import getTargets, { type Targets } from "@babel/helper-compilation-targets";

export function resolveTargets(
options: ValidatedOptions,
// eslint-disable-next-line no-unused-vars
root: string,
// eslint-disable-next-line no-unused-vars
filename: string | void,
): Targets {
let { targets } = options;
if (typeof targets === "string" || Array.isArray(targets)) {
targets = { browsers: targets };
}

return getTargets((targets: any), {
ignoreBrowserslistConfig: true,
env: options.browserslistEnv,
});
}
35 changes: 35 additions & 0 deletions packages/babel-core/src/config/resolve-targets.js
@@ -0,0 +1,35 @@
// @flow

import typeof * as browserType from "./resolve-targets-browser";
import typeof * as nodeType from "./resolve-targets";

// Kind of gross, but essentially asserting that the exports of this module are the same as the
// exports of index-browser, since this file may be replaced at bundle time with index-browser.
((({}: any): $Exact<browserType>): $Exact<nodeType>);

import type { ValidatedOptions } from "./validation/options";
import path from "path";
import getTargets, { type Targets } from "@babel/helper-compilation-targets";

export function resolveTargets(
options: ValidatedOptions,
root: string,
filename: string | void,
): Targets {
let { targets } = options;
if (typeof targets === "string" || Array.isArray(targets)) {
targets = { browsers: targets };
}

let config;
if (typeof options.browserslistConfigFile === "string") {
config = path.resolve(root, options.browserslistConfigFile);
}

return getTargets((targets: any), {
ignoreBrowserslistConfig: options.browserslistConfigFile === false,
config,
path: filename ?? root,
env: options.browserslistEnv,
});
}
4 changes: 2 additions & 2 deletions packages/babel-core/src/config/util.js
@@ -1,10 +1,10 @@
// @flow

import type { ValidatedOptions } from "./validation/options";
import type { ValidatedOptions, NormalizedOptions } from "./validation/options";

export function mergeOptions(
target: ValidatedOptions,
source: ValidatedOptions,
source: ValidatedOptions | NormalizedOptions,
): void {
for (const k of Object.keys(source)) {
if (k === "parserOpts" && source.parserOpts) {
Expand Down
55 changes: 55 additions & 0 deletions packages/babel-core/src/config/validation/option-assertions.js
@@ -1,5 +1,10 @@
// @flow

import {
isBrowsersQueryValid,
TargetNames,
} from "@babel/helper-compilation-targets";

import type {
ConfigFileSearch,
BabelrcSearch,
Expand All @@ -16,6 +21,7 @@ import type {
NestingPath,
CallerMetadata,
RootMode,
TargetsListOrObject,
} from "./options";

export type { RootPath } from "./options";
Expand Down Expand Up @@ -373,3 +379,52 @@ function assertPluginTarget(loc: GeneralPath, value: mixed): PluginTarget {
}
return value;
}

export function assertTargets(
loc: GeneralPath,
value: mixed,
): TargetsListOrObject {
if (isBrowsersQueryValid(value)) return (value: any);

if (typeof value !== "object" || !value || Array.isArray(value)) {
throw new Error(
`${msg(loc)} must be a string, an array of strings or an object`,
);
}

assertBrowsersList(access(loc, "browsers"), value.browsers);
assertBoolean(access(loc, "esmodules"), value.esmodules);

for (const key of Object.keys(value)) {
const val = value[key];
const subLoc = access(loc, key);

if (key === "esmodules") assertBoolean(subLoc, val);
else if (key === "browsers") assertBrowsersList(subLoc, val);
else if (!Object.hasOwnProperty.call(TargetNames, key)) {
const validTargets = Object.keys(TargetNames).join(", ");
throw new Error(
`${msg(
subLoc,
)} is not a valid target. Supported targets are ${validTargets}`,
);
} else assertBrowserVersion(subLoc, val);
}

return (value: any);
}

function assertBrowsersList(loc: GeneralPath, value: mixed) {
if (value !== undefined && !isBrowsersQueryValid(value)) {
throw new Error(
`${msg(loc)} must be undefined, a string or an array of strings`,
);
}
}

function assertBrowserVersion(loc: GeneralPath, value: mixed) {
if (typeof value === "number" && Math.round(value) === value) return;
if (typeof value === "string") return;

throw new Error(`${msg(loc)} must be a string or an integer number`);
}
28 changes: 28 additions & 0 deletions packages/babel-core/src/config/validation/options.js
@@ -1,5 +1,7 @@
// @flow

import type { InputTargets, Targets } from "@babel/helper-compilation-targets";

import type { ConfigItem } from "../item";
import Plugin from "../plugin";

Expand All @@ -23,6 +25,7 @@ import {
assertSourceMaps,
assertCompact,
assertSourceType,
assertTargets,
type ValidatorSet,
type Validator,
type OptionPath,
Expand Down Expand Up @@ -77,6 +80,16 @@ const NONPRESET_VALIDATORS: ValidatorSet = {
$PropertyType<ValidatedOptions, "ignore">,
>),
only: (assertIgnoreList: Validator<$PropertyType<ValidatedOptions, "only">>),

targets: (assertTargets: Validator<
$PropertyType<ValidatedOptions, "targets">,
>),
browserslistConfigFile: (assertConfigFileSearch: Validator<
$PropertyType<ValidatedOptions, "browserslistConfigFile">,
>),
browserslistEnv: (assertString: Validator<
$PropertyType<ValidatedOptions, "browserslistEnv">,
>),
};

const COMMON_VALIDATORS: ValidatorSet = {
Expand Down Expand Up @@ -208,6 +221,11 @@ export type ValidatedOptions = {
plugins?: PluginList,
passPerPreset?: boolean,

// browserslists-related options
targets?: TargetsListOrObject,
browserslistConfigFile?: ConfigFileSearch,
browserslistEnv?: string,

// Options for @babel/generator
retainLines?: boolean,
comments?: boolean,
Expand Down Expand Up @@ -241,6 +259,11 @@ export type ValidatedOptions = {
generatorOpts?: {},
};

export type NormalizedOptions = {
...$Diff<ValidatedOptions, { targets: any }>,
+targets: Targets,
};

export type CallerMetadata = {
// If 'caller' is specified, require that the name is given for debugging
// messages.
Expand Down Expand Up @@ -273,6 +296,11 @@ export type CompactOption = boolean | "auto";
export type RootInputSourceMapOption = {} | boolean;
export type RootMode = "root" | "upward" | "upward-optional";

export type TargetsListOrObject =
| Targets
| InputTargets
| $PropertyType<InputTargets, "browsers">;

export type OptionsSource =
| "arguments"
| "configfile"
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-core/test/config-chain.js
Expand Up @@ -1004,13 +1004,15 @@ describe("buildConfigChain", function () {
const getDefaults = () => ({
babelrc: false,
configFile: false,
browserslistConfigFile: false,
cwd: process.cwd(),
root: process.cwd(),
envName: "development",
passPerPreset: false,
plugins: [],
presets: [],
cloneInputAst: true,
targets: {},
});
const realEnv = process.env.NODE_ENV;
const realBabelEnv = process.env.BABEL_ENV;
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-core/test/fixtures/targets/.browserslistrc
@@ -0,0 +1,4 @@
chrome 80

[browserslist-loading-test]
chrome 70
@@ -0,0 +1 @@
firefox 74
@@ -0,0 +1 @@
edge 14

0 comments on commit 96e0bbf

Please sign in to comment.