Add targets and browserslist* options to @babel/core (#12189)
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
makeWeakCacheSync,
|
||||
type CacheConfigurator,
|
||||
} from "../caching";
|
||||
import makeAPI, { type PluginAPI } from "../helpers/config-api";
|
||||
import { makeConfigAPI, type ConfigAPI } from "../helpers/config-api";
|
||||
import { makeStaticFileCache } from "./utils";
|
||||
import loadCjsOrMjsDefault from "./module-types";
|
||||
import pathPatternToRegex from "../pattern-to-regex";
|
||||
@@ -203,7 +203,7 @@ const readConfigJS = makeStrongCache(function* readConfigJS(
|
||||
let assertCache = false;
|
||||
if (typeof options === "function") {
|
||||
yield* []; // if we want to make it possible to use async configs
|
||||
options = ((options: any): (api: PluginAPI) => {})(makeAPI(cache));
|
||||
options = ((options: any): (api: ConfigAPI) => {})(makeConfigAPI(cache));
|
||||
|
||||
assertCache = true;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type PresetInstance,
|
||||
} from "./config-chain";
|
||||
import type { UnloadedDescriptor } from "./config-descriptors";
|
||||
import type { Targets } from "@babel/helper-compilation-targets";
|
||||
import traverse from "@babel/traverse";
|
||||
import {
|
||||
makeWeakCache,
|
||||
@@ -27,7 +28,7 @@ import {
|
||||
type PluginItem,
|
||||
} from "./validation/options";
|
||||
import { validatePluginObject } from "./validation/plugins";
|
||||
import makeAPI from "./helpers/config-api";
|
||||
import { makePluginAPI } from "./helpers/config-api";
|
||||
|
||||
import loadPrivatePartialConfig from "./partial";
|
||||
import type { ValidatedOptions } from "./validation/options";
|
||||
@@ -39,6 +40,11 @@ type LoadedDescriptor = {
|
||||
alias: string,
|
||||
};
|
||||
|
||||
type PluginContext = {
|
||||
...ConfigContext,
|
||||
targets: Targets,
|
||||
};
|
||||
|
||||
export type { InputOptions } from "./validation/options";
|
||||
|
||||
export type ResolvedConfig = {
|
||||
@@ -55,6 +61,7 @@ export type PluginPasses = Array<PluginPassList>;
|
||||
type SimpleContext = {
|
||||
envName: string,
|
||||
caller: CallerMetadata | void,
|
||||
targets: Targets,
|
||||
};
|
||||
|
||||
export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
|
||||
@@ -78,6 +85,11 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
|
||||
throw new Error("Assertion failure - plugins and presets exist");
|
||||
}
|
||||
|
||||
const pluginContext: PluginContext = {
|
||||
...context,
|
||||
targets: options.targets,
|
||||
};
|
||||
|
||||
const toDescriptor = (item: PluginItem) => {
|
||||
const desc = getItemDescriptor(item);
|
||||
if (!desc) {
|
||||
@@ -112,12 +124,12 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
|
||||
// in the previous pass.
|
||||
if (descriptor.ownPass) {
|
||||
presets.push({
|
||||
preset: yield* loadPresetDescriptor(descriptor, context),
|
||||
preset: yield* loadPresetDescriptor(descriptor, pluginContext),
|
||||
pass: [],
|
||||
});
|
||||
} else {
|
||||
presets.unshift({
|
||||
preset: yield* loadPresetDescriptor(descriptor, context),
|
||||
preset: yield* loadPresetDescriptor(descriptor, pluginContext),
|
||||
pass: pluginDescriptorsPass,
|
||||
});
|
||||
}
|
||||
@@ -172,7 +184,7 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
|
||||
const descriptor: UnloadedDescriptor = descs[i];
|
||||
if (descriptor.options !== false) {
|
||||
try {
|
||||
pass.push(yield* loadPluginDescriptor(descriptor, context));
|
||||
pass.push(yield* loadPluginDescriptor(descriptor, pluginContext));
|
||||
} catch (e) {
|
||||
if (e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") {
|
||||
// print special message for `plugins: ["@babel/foo", { foo: "option" }]`
|
||||
@@ -235,7 +247,7 @@ const loadDescriptor = makeWeakCache(function* (
|
||||
|
||||
const api = {
|
||||
...context,
|
||||
...makeAPI(cache),
|
||||
...makePluginAPI(cache),
|
||||
};
|
||||
try {
|
||||
item = yield* factory(api, options, dirname);
|
||||
@@ -375,7 +387,7 @@ const validatePreset = (
|
||||
*/
|
||||
function* loadPresetDescriptor(
|
||||
descriptor: UnloadedDescriptor,
|
||||
context: ConfigContext,
|
||||
context: PluginContext,
|
||||
): Handler<ConfigChain | null> {
|
||||
const preset = instantiatePreset(yield* loadDescriptor(descriptor, context));
|
||||
validatePreset(preset, context, descriptor);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import semver from "semver";
|
||||
import type { Targets } from "@babel/helper-compilation-targets";
|
||||
|
||||
import { version as coreVersion } from "../../";
|
||||
import {
|
||||
assertSimpleType,
|
||||
@@ -20,7 +22,9 @@ type EnvFunction = {
|
||||
|
||||
type CallerFactory = ((CallerMetadata | void) => mixed) => SimpleType;
|
||||
|
||||
export type PluginAPI = {|
|
||||
type TargetsFunction = () => Targets;
|
||||
|
||||
export type ConfigAPI = {|
|
||||
version: string,
|
||||
cache: SimpleCacheConfigurator,
|
||||
env: EnvFunction,
|
||||
@@ -29,9 +33,14 @@ export type PluginAPI = {|
|
||||
caller?: CallerFactory,
|
||||
|};
|
||||
|
||||
export default function makeAPI(
|
||||
cache: CacheConfigurator<{ envName: string, caller: CallerMetadata | void }>,
|
||||
): PluginAPI {
|
||||
export type PluginAPI = {|
|
||||
...ConfigAPI,
|
||||
targets: TargetsFunction,
|
||||
|};
|
||||
|
||||
export function makeConfigAPI<
|
||||
SideChannel: { envName: string, caller: CallerMetadata | void },
|
||||
>(cache: CacheConfigurator<SideChannel>): ConfigAPI {
|
||||
const env: any = value =>
|
||||
cache.using(data => {
|
||||
if (typeof value === "undefined") return data.envName;
|
||||
@@ -61,6 +70,22 @@ export default function makeAPI(
|
||||
};
|
||||
}
|
||||
|
||||
export function makePluginAPI(
|
||||
cache: CacheConfigurator<{
|
||||
envName: string,
|
||||
caller: CallerMetadata | void,
|
||||
targets: Targets,
|
||||
}>,
|
||||
): PluginAPI {
|
||||
const targets = () =>
|
||||
// We are using JSON.parse/JSON.stringify because it's only possible to cache
|
||||
// primitive values. We can safely stringify the targets object because it
|
||||
// only contains strings as its properties.
|
||||
// Please make the Record and Tuple proposal happen!
|
||||
JSON.parse(cache.using(data => JSON.stringify(data.targets)));
|
||||
return { ...makeConfigAPI(cache), targets };
|
||||
}
|
||||
|
||||
function assertVersion(range: string | number): void {
|
||||
if (typeof range === "number") {
|
||||
if (!Number.isInteger(range)) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getEnv } from "./helpers/environment";
|
||||
import {
|
||||
validate,
|
||||
type ValidatedOptions,
|
||||
type NormalizedOptions,
|
||||
type RootMode,
|
||||
} from "./validation/options";
|
||||
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
type ConfigFile,
|
||||
type IgnoreFile,
|
||||
} from "./files";
|
||||
import { resolveTargets } from "./resolve-targets";
|
||||
|
||||
function* resolveRootMode(
|
||||
rootDir: string,
|
||||
@@ -61,7 +63,7 @@ function* resolveRootMode(
|
||||
}
|
||||
|
||||
type PrivPartialConfig = {
|
||||
options: ValidatedOptions,
|
||||
options: NormalizedOptions,
|
||||
context: ConfigContext,
|
||||
fileHandling: FileHandling,
|
||||
ignore: IgnoreFile | void,
|
||||
@@ -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;
|
||||
const options: NormalizedOptions = {
|
||||
...merged,
|
||||
targets: resolveTargets(merged, absoluteRootDir, filename),
|
||||
|
||||
options.plugins = configChain.plugins.map(descriptor =>
|
||||
createItemFromDescriptor(descriptor),
|
||||
);
|
||||
options.presets = configChain.presets.map(descriptor =>
|
||||
createItemFromDescriptor(descriptor),
|
||||
);
|
||||
// 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,
|
||||
@@ -201,7 +209,7 @@ 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;
|
||||
@@ -209,7 +217,7 @@ class PartialConfig {
|
||||
files: Set<string>;
|
||||
|
||||
constructor(
|
||||
options: ValidatedOptions,
|
||||
options: NormalizedOptions,
|
||||
babelrc: string | void,
|
||||
ignore: string | void,
|
||||
config: string | void,
|
||||
|
||||
26
packages/babel-core/src/config/resolve-targets-browser.js
Normal file
26
packages/babel-core/src/config/resolve-targets-browser.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// @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 };
|
||||
}
|
||||
// $FlowIgnore it thinks that targets.esmodules doesn't exist.
|
||||
if (targets && targets.esmodules) {
|
||||
targets = { ...targets, esmodules: "intersect" };
|
||||
}
|
||||
|
||||
return getTargets((targets: any), {
|
||||
ignoreBrowserslistConfig: true,
|
||||
browserslistEnv: options.browserslistEnv,
|
||||
});
|
||||
}
|
||||
39
packages/babel-core/src/config/resolve-targets.js
Normal file
39
packages/babel-core/src/config/resolve-targets.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// @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 };
|
||||
}
|
||||
// $FlowIgnore it thinks that targets.esmodules doesn't exist.
|
||||
if (targets && targets.esmodules) {
|
||||
targets = { ...targets, esmodules: "intersect" };
|
||||
}
|
||||
|
||||
let configFile;
|
||||
if (typeof options.browserslistConfigFile === "string") {
|
||||
configFile = path.resolve(root, options.browserslistConfigFile);
|
||||
}
|
||||
|
||||
return getTargets((targets: any), {
|
||||
ignoreBrowserslistConfig: options.browserslistConfigFile === false,
|
||||
configFile,
|
||||
configPath: filename ?? root,
|
||||
browserslistEnv: options.browserslistEnv,
|
||||
});
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
isBrowsersQueryValid,
|
||||
TargetNames,
|
||||
} from "@babel/helper-compilation-targets";
|
||||
|
||||
import type {
|
||||
ConfigFileSearch,
|
||||
BabelrcSearch,
|
||||
@@ -16,6 +21,7 @@ import type {
|
||||
NestingPath,
|
||||
CallerMetadata,
|
||||
RootMode,
|
||||
TargetsListOrObject,
|
||||
} from "./options";
|
||||
|
||||
export type { RootPath } from "./options";
|
||||
@@ -373,3 +379,55 @@ 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`,
|
||||
);
|
||||
}
|
||||
|
||||
const browsersLoc = access(loc, "browsers");
|
||||
const esmodulesLoc = access(loc, "esmodules");
|
||||
|
||||
assertBrowsersList(browsersLoc, value.browsers);
|
||||
assertBoolean(esmodulesLoc, 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`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import type { InputTargets, Targets } from "@babel/helper-compilation-targets";
|
||||
|
||||
import type { ConfigItem } from "../item";
|
||||
import Plugin from "../plugin";
|
||||
|
||||
@@ -23,6 +25,7 @@ import {
|
||||
assertSourceMaps,
|
||||
assertCompact,
|
||||
assertSourceType,
|
||||
assertTargets,
|
||||
type ValidatorSet,
|
||||
type Validator,
|
||||
type OptionPath,
|
||||
@@ -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 = {
|
||||
@@ -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,
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user