From ed9be9330e963b2f3f2b3163119fd5cdec2e8004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 10 Dec 2020 13:12:21 +0100 Subject: [PATCH] Add `targets` and `browserslist*` options to `@babel/core` (#12189) --- packages/babel-core/package.json | 3 + .../src/config/files/configuration.js | 4 +- packages/babel-core/src/config/full.js | 24 ++- .../src/config/helpers/config-api.js | 33 +++- packages/babel-core/src/config/partial.js | 56 ++++--- .../src/config/resolve-targets-browser.js | 26 +++ .../babel-core/src/config/resolve-targets.js | 39 +++++ packages/babel-core/src/config/util.js | 4 +- .../config/validation/option-assertions.js | 58 +++++++ .../src/config/validation/options.js | 28 ++++ packages/babel-core/test/config-chain.js | 2 + .../fixtures/plugins/targets/plugin/input.js | 1 + .../plugins/targets/plugin/options.json | 4 + .../fixtures/plugins/targets/plugin/output.js | 2 + .../fixtures/plugins/targets/plugin/plugin.js | 14 ++ .../fixtures/plugins/targets/preset/input.js | 1 + .../plugins/targets/preset/options.json | 4 + .../fixtures/plugins/targets/preset/output.js | 2 + .../fixtures/plugins/targets/preset/preset.js | 18 +++ .../test/fixtures/targets/.browserslistrc | 4 + .../fixtures/targets/.browserslistrc-firefox | 1 + .../fixtures/targets/nested/.browserslistrc | 1 + packages/babel-core/test/targets.js | 150 ++++++++++++++++++ .../src/index.js | 108 ++++++++----- .../src/types.js | 10 +- .../src/utils.js | 8 + .../__snapshots__/targets-parser.spec.js.snap | 21 +++ .../test/fixtures/.browserslistrc | 2 + .../test/targets-parser.spec.js | 43 +++++ .../babel-helper-plugin-utils/src/index.js | 32 ++-- packages/babel-preset-env/src/index.js | 89 +++++++---- .../top-level-targets-shadowed/input.mjs | 1 + .../top-level-targets-shadowed/options.json | 8 + .../top-level-targets-shadowed/stdout.txt | 26 +++ .../debug/top-level-targets/input.mjs | 1 + .../debug/top-level-targets/options.json | 8 + .../debug/top-level-targets/stdout.txt | 23 +++ yarn.lock | 1 + 38 files changed, 746 insertions(+), 114 deletions(-) create mode 100644 packages/babel-core/src/config/resolve-targets-browser.js create mode 100644 packages/babel-core/src/config/resolve-targets.js create mode 100644 packages/babel-core/test/fixtures/plugins/targets/plugin/input.js create mode 100644 packages/babel-core/test/fixtures/plugins/targets/plugin/options.json create mode 100644 packages/babel-core/test/fixtures/plugins/targets/plugin/output.js create mode 100644 packages/babel-core/test/fixtures/plugins/targets/plugin/plugin.js create mode 100644 packages/babel-core/test/fixtures/plugins/targets/preset/input.js create mode 100644 packages/babel-core/test/fixtures/plugins/targets/preset/options.json create mode 100644 packages/babel-core/test/fixtures/plugins/targets/preset/output.js create mode 100644 packages/babel-core/test/fixtures/plugins/targets/preset/preset.js create mode 100644 packages/babel-core/test/fixtures/targets/.browserslistrc create mode 100644 packages/babel-core/test/fixtures/targets/.browserslistrc-firefox create mode 100644 packages/babel-core/test/fixtures/targets/nested/.browserslistrc create mode 100644 packages/babel-core/test/targets.js create mode 100644 packages/babel-helper-compilation-targets/test/fixtures/.browserslistrc create mode 100644 packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/input.mjs create mode 100644 packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/options.json create mode 100644 packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/stdout.txt create mode 100644 packages/babel-preset-env/test/fixtures/debug/top-level-targets/input.mjs create mode 100644 packages/babel-preset-env/test/fixtures/debug/top-level-targets/options.json create mode 100644 packages/babel-preset-env/test/fixtures/debug/top-level-targets/stdout.txt diff --git a/packages/babel-core/package.json b/packages/babel-core/package.json index 4f74090dc16a..357492e25db4 100644 --- a/packages/babel-core/package.json +++ b/packages/babel-core/package.json @@ -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.10", + "@babel/helper-compilation-targets": "workspace:^7.12.5", "@babel/helper-module-transforms": "workspace:^7.12.1", "@babel/helpers": "workspace:^7.12.5", "@babel/parser": "workspace:^7.12.10", diff --git a/packages/babel-core/src/config/files/configuration.js b/packages/babel-core/src/config/files/configuration.js index 8a3bed914fab..4a9aa9d265c5 100644 --- a/packages/babel-core/src/config/files/configuration.js +++ b/packages/babel-core/src/config/files/configuration.js @@ -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; } diff --git a/packages/babel-core/src/config/full.js b/packages/babel-core/src/config/full.js index 3914f564ec3f..bc15858339cf 100644 --- a/packages/babel-core/src/config/full.js +++ b/packages/babel-core/src/config/full.js @@ -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; 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 { const preset = instantiatePreset(yield* loadDescriptor(descriptor, context)); validatePreset(preset, context, descriptor); diff --git a/packages/babel-core/src/config/helpers/config-api.js b/packages/babel-core/src/config/helpers/config-api.js index fea73139af68..70d793995d1a 100644 --- a/packages/babel-core/src/config/helpers/config-api.js +++ b/packages/babel-core/src/config/helpers/config-api.js @@ -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): 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)) { diff --git a/packages/babel-core/src/config/partial.js b/packages/babel-core/src/config/partial.js index b42b4ec042a3..4bc40e3249db 100644 --- a/packages/babel-core/src/config/partial.js +++ b/packages/babel-core/src/config/partial.js @@ -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; - - 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, @@ -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; constructor( - options: ValidatedOptions, + options: NormalizedOptions, babelrc: string | void, ignore: string | void, config: string | void, diff --git a/packages/babel-core/src/config/resolve-targets-browser.js b/packages/babel-core/src/config/resolve-targets-browser.js new file mode 100644 index 000000000000..751b4a2a160e --- /dev/null +++ b/packages/babel-core/src/config/resolve-targets-browser.js @@ -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, + }); +} diff --git a/packages/babel-core/src/config/resolve-targets.js b/packages/babel-core/src/config/resolve-targets.js new file mode 100644 index 000000000000..b65bab7ff3e9 --- /dev/null +++ b/packages/babel-core/src/config/resolve-targets.js @@ -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): $Exact); + +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, + }); +} diff --git a/packages/babel-core/src/config/util.js b/packages/babel-core/src/config/util.js index b9d3af143dd3..491a2ea950fa 100644 --- a/packages/babel-core/src/config/util.js +++ b/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) { diff --git a/packages/babel-core/src/config/validation/option-assertions.js b/packages/babel-core/src/config/validation/option-assertions.js index f3962f18945f..fefda1437759 100644 --- a/packages/babel-core/src/config/validation/option-assertions.js +++ b/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, @@ -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`); +} diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index 27caf3bcc1e2..56b7655583e4 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/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"; @@ -23,6 +25,7 @@ import { assertSourceMaps, assertCompact, assertSourceType, + assertTargets, type ValidatorSet, type Validator, type OptionPath, @@ -77,6 +80,16 @@ const NONPRESET_VALIDATORS: ValidatorSet = { $PropertyType, >), only: (assertIgnoreList: Validator<$PropertyType>), + + targets: (assertTargets: Validator< + $PropertyType, + >), + browserslistConfigFile: (assertConfigFileSearch: Validator< + $PropertyType, + >), + browserslistEnv: (assertString: Validator< + $PropertyType, + >), }; 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, + +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; + export type OptionsSource = | "arguments" | "configfile" diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 07b0eae64b90..653688e9ada8 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -976,6 +976,7 @@ describe("buildConfigChain", function () { const getDefaults = () => ({ babelrc: false, configFile: false, + browserslistConfigFile: false, cwd: process.cwd(), root: process.cwd(), envName: "development", @@ -983,6 +984,7 @@ describe("buildConfigChain", function () { plugins: [], presets: [], cloneInputAst: true, + targets: {}, }); const realEnv = process.env.NODE_ENV; const realBabelEnv = process.env.BABEL_ENV; diff --git a/packages/babel-core/test/fixtures/plugins/targets/plugin/input.js b/packages/babel-core/test/fixtures/plugins/targets/plugin/input.js new file mode 100644 index 000000000000..092bc2b04126 --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/plugin/input.js @@ -0,0 +1 @@ +; diff --git a/packages/babel-core/test/fixtures/plugins/targets/plugin/options.json b/packages/babel-core/test/fixtures/plugins/targets/plugin/options.json new file mode 100644 index 000000000000..ca5100967dc3 --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/plugin/options.json @@ -0,0 +1,4 @@ +{ + "targets": ["firefox 64", "node 8"], + "plugins": ["./plugin"] +} diff --git a/packages/babel-core/test/fixtures/plugins/targets/plugin/output.js b/packages/babel-core/test/fixtures/plugins/targets/plugin/output.js new file mode 100644 index 000000000000..4ae652ed9fa8 --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/plugin/output.js @@ -0,0 +1,2 @@ +; +"plugin: {\"firefox\":\"64.0.0\",\"node\":\"8.17.0\"}" diff --git a/packages/babel-core/test/fixtures/plugins/targets/plugin/plugin.js b/packages/babel-core/test/fixtures/plugins/targets/plugin/plugin.js new file mode 100644 index 000000000000..b268497cd97e --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/plugin/plugin.js @@ -0,0 +1,14 @@ +module.exports = function (api) { + const { types: t } = api; + + const targets = api.targets(); + + return { + visitor: { + Program(path) { + const output = t.stringLiteral(`plugin: ${JSON.stringify(targets)}`); + path.pushContainer("body", output); + }, + }, + }; +}; diff --git a/packages/babel-core/test/fixtures/plugins/targets/preset/input.js b/packages/babel-core/test/fixtures/plugins/targets/preset/input.js new file mode 100644 index 000000000000..092bc2b04126 --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/preset/input.js @@ -0,0 +1 @@ +; diff --git a/packages/babel-core/test/fixtures/plugins/targets/preset/options.json b/packages/babel-core/test/fixtures/plugins/targets/preset/options.json new file mode 100644 index 000000000000..2e86914c87e1 --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/preset/options.json @@ -0,0 +1,4 @@ +{ + "targets": ["firefox 64", "node 8"], + "presets": ["./preset"] +} diff --git a/packages/babel-core/test/fixtures/plugins/targets/preset/output.js b/packages/babel-core/test/fixtures/plugins/targets/preset/output.js new file mode 100644 index 000000000000..570eb08d2ee3 --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/preset/output.js @@ -0,0 +1,2 @@ +; +"preset: {\"firefox\":\"64.0.0\",\"node\":\"8.17.0\"}" diff --git a/packages/babel-core/test/fixtures/plugins/targets/preset/preset.js b/packages/babel-core/test/fixtures/plugins/targets/preset/preset.js new file mode 100644 index 000000000000..b35f4d5fa94e --- /dev/null +++ b/packages/babel-core/test/fixtures/plugins/targets/preset/preset.js @@ -0,0 +1,18 @@ +module.exports = function (api) { + const targets = api.targets(); + + return { + plugins: [plugin], + }; + + function plugin({ types: t }) { + return { + visitor: { + Program(path) { + const output = t.stringLiteral(`preset: ${JSON.stringify(targets)}`); + path.pushContainer("body", output); + }, + }, + }; + } +}; diff --git a/packages/babel-core/test/fixtures/targets/.browserslistrc b/packages/babel-core/test/fixtures/targets/.browserslistrc new file mode 100644 index 000000000000..6b3ad521fb07 --- /dev/null +++ b/packages/babel-core/test/fixtures/targets/.browserslistrc @@ -0,0 +1,4 @@ +chrome 80 + +[browserslist-loading-test] +chrome 70 diff --git a/packages/babel-core/test/fixtures/targets/.browserslistrc-firefox b/packages/babel-core/test/fixtures/targets/.browserslistrc-firefox new file mode 100644 index 000000000000..c2f0c40728a6 --- /dev/null +++ b/packages/babel-core/test/fixtures/targets/.browserslistrc-firefox @@ -0,0 +1 @@ +firefox 74 diff --git a/packages/babel-core/test/fixtures/targets/nested/.browserslistrc b/packages/babel-core/test/fixtures/targets/nested/.browserslistrc new file mode 100644 index 000000000000..124541b16eb8 --- /dev/null +++ b/packages/babel-core/test/fixtures/targets/nested/.browserslistrc @@ -0,0 +1 @@ +edge 14 diff --git a/packages/babel-core/test/targets.js b/packages/babel-core/test/targets.js new file mode 100644 index 000000000000..00214c9b7f06 --- /dev/null +++ b/packages/babel-core/test/targets.js @@ -0,0 +1,150 @@ +import { loadOptions as loadOptionsOrig } from "../lib"; +import { join } from "path"; + +function loadOptions(opts) { + return loadOptionsOrig({ cwd: __dirname, ...opts }); +} + +function withTargets(targets) { + return loadOptions({ targets }); +} + +describe("targets", () => { + it("throws if invalid type", () => { + expect(() => withTargets(2)).toThrow( + ".targets must be a string, an array of strings or an object", + ); + + expect(() => withTargets([2])).toThrow( + ".targets must be a string, an array of strings or an object", + ); + + expect(() => withTargets([{}])).toThrow( + ".targets must be a string, an array of strings or an object", + ); + + expect(() => withTargets([])).not.toThrow(); + expect(() => withTargets({})).not.toThrow(); + }); + + it("throws if invalid target", () => { + expect(() => withTargets({ uglify: "2.3" })).toThrow( + /\.targets\["uglify"\] is not a valid target/, + ); + + expect(() => withTargets({ foo: "bar" })).toThrow( + /\.targets\["foo"\] is not a valid target/, + ); + + expect(() => withTargets({ firefox: 71 })).not.toThrow(); + }); + + it("throws if invalid version", () => { + expect(() => withTargets({ node: 10.1 /* or 10.10? */ })).toThrow( + `.targets["node"] must be a string or an integer number`, + ); + + expect(() => withTargets({ node: true })).toThrow( + `.targets["node"] must be a string or an integer number`, + ); + + expect(() => withTargets({ node: "10.1" })).not.toThrow(); + + expect(() => withTargets({ node: "current" })).not.toThrow(); + }); + + it("esmodules", () => { + expect(() => withTargets({ esmodules: "7" })).toThrow( + `.targets["esmodules"] must be a boolean, or undefined`, + ); + + expect(() => withTargets({ esmodules: false })).not.toThrow(); + expect(() => withTargets({ esmodules: true })).not.toThrow(); + }); + + it("browsers", () => { + expect(() => withTargets({ browsers: 2 })).toThrow( + `.targets["browsers"] must be undefined, a string or an array of strings`, + ); + + expect(() => withTargets({ browsers: [2] })).toThrow( + `.targets["browsers"] must be undefined, a string or an array of strings`, + ); + + expect(() => withTargets({ browsers: {} })).toThrow( + `.targets["browsers"] must be undefined, a string or an array of strings`, + ); + + expect(() => withTargets({ browsers: [] })).not.toThrow(); + }); +}); + +describe("browserslist", () => { + it("loads .browserslistrc by default", () => { + expect( + loadOptions({ + cwd: join(__dirname, "fixtures", "targets"), + }).targets, + ).toEqual({ chrome: "80.0.0" }); + }); + + it("loads .browserslistrc relative to the input file", () => { + expect( + loadOptions({ + cwd: join(__dirname, "fixtures", "targets"), + filename: "./nested/test.js", + }).targets, + ).toEqual({ edge: "14.0.0" }); + }); + + describe("browserslistConfigFile", () => { + it("can disable config loading", () => { + expect( + loadOptions({ + cwd: join(__dirname, "fixtures", "targets"), + browserslistConfigFile: false, + }).targets, + ).toEqual({}); + }); + + it("can specify a custom file", () => { + expect( + loadOptions({ + cwd: join(__dirname, "fixtures", "targets"), + browserslistConfigFile: "./.browserslistrc-firefox", + }).targets, + ).toEqual({ firefox: "74.0.0" }); + }); + + it("is relative to the project root", () => { + expect( + loadOptions({ + cwd: join(__dirname, "fixtures", "targets"), + root: "..", + filename: "./nested/test.js", + browserslistConfigFile: "./targets/.browserslistrc-firefox", + }).targets, + ).toEqual({ firefox: "74.0.0" }); + }); + }); + + describe("browserslistEnv", () => { + it("is forwarded to browserslist", () => { + expect( + loadOptions({ + cwd: join(__dirname, "fixtures", "targets"), + browserslistEnv: "browserslist-loading-test", + }).targets, + ).toEqual({ chrome: "70.0.0" }); + }); + }); + + it("esmodules and browsers are intersected", () => { + expect( + withTargets({ + esmodules: true, + browsers: "chrome >= 80, firefox >= 30", + }).targets, + ).toEqual({ chrome: "80.0.0", firefox: "60.0.0" }); + }); +}); diff --git a/packages/babel-helper-compilation-targets/src/index.js b/packages/babel-helper-compilation-targets/src/index.js index 0241bcb90f26..e408e3b62b1a 100644 --- a/packages/babel-helper-compilation-targets/src/index.js +++ b/packages/babel-helper-compilation-targets/src/index.js @@ -9,6 +9,7 @@ import { semverMin, isUnreleasedVersion, getLowestUnreleased, + getHighestUnreleased, } from "./utils"; import { OptionValidator } from "@babel/helper-validator-option"; import { browserNameMap } from "./targets"; @@ -22,9 +23,11 @@ export { prettifyTargets } from "./pretty"; export { getInclusionReasons } from "./debug"; export { default as filterItems, isRequired } from "./filter-items"; export { unreleasedLabels } from "./targets"; +export { TargetNames }; + +const ESM_SUPPORT = browserModulesData["es6.module"]; const v = new OptionValidator(packageName); -const browserslistDefaults = browserslist.defaults; const validBrowserslistTargets = [ ...Object.keys(browserslist.data), @@ -55,8 +58,11 @@ function validateTargetNames(targets: Targets): TargetsTuple { return (targets: any); } -export function isBrowsersQueryValid(browsers: Browsers | Targets): boolean { - return typeof browsers === "string" || Array.isArray(browsers); +export function isBrowsersQueryValid(browsers: mixed): boolean %checks { + return ( + typeof browsers === "string" || + (Array.isArray(browsers) && browsers.every(b => typeof b === "string")) + ); } function validateBrowsers(browsers: Browsers | void) { @@ -168,55 +174,83 @@ function generateTargets(inputTargets: InputTargets): Targets { return ((input: any): Targets); } +function resolveTargets(queries: Browsers): Targets { + const resolved = browserslist(queries, { mobileToDesktop: true }); + return getLowestVersions(resolved); +} + +type GetTargetsOption = { + // This is not the path of the config file, but the path where start searching it from + configPath?: string, + + // The path of the config file + configFile?: string, + + // The env to pass to browserslist + browserslistEnv?: string, + + // true to disable config loading + ignoreBrowserslistConfig?: boolean, +}; + export default function getTargets( inputTargets: InputTargets = {}, - options: Object = {}, + options: GetTargetsOption = {}, ): Targets { - let { browsers } = inputTargets; - - // `esmodules` as a target indicates the specific set of browsers supporting ES Modules. - // These values OVERRIDE the `browsers` field. - if (inputTargets.esmodules) { - const supportsESModules = browserModulesData["es6.module"]; - browsers = Object.keys(supportsESModules) - .map(browser => `${browser} ${supportsESModules[browser]}`) - .join(", "); - } + let { browsers, esmodules } = inputTargets; - // Parse browsers target via browserslist - const browsersquery = validateBrowsers(browsers); + validateBrowsers(browsers); const input = generateTargets(inputTargets); let targets: TargetsTuple = validateTargetNames(input); - const shouldParseBrowsers = !!browsersquery; + const shouldParseBrowsers = !!browsers; const hasTargets = shouldParseBrowsers || Object.keys(targets).length > 0; const shouldSearchForConfig = !options.ignoreBrowserslistConfig && !hasTargets; - if (shouldParseBrowsers || shouldSearchForConfig) { - // If no targets are passed, we need to overwrite browserslist's defaults - // so that we enable all transforms (acting like the now deprecated - // preset-latest). - // - // Note, if browserslist resolves the config (ex. package.json), then usage - // of `defaults` in queries will be different since we don't want to break - // the behavior of "no targets is the same as preset-latest". - if (!hasTargets) { - browserslist.defaults = objectToBrowserslist(targets); - } + if (!browsers && shouldSearchForConfig) { + browsers = + browserslist.loadConfig({ + config: options.configFile, + path: options.configPath, + env: options.browserslistEnv, + }) ?? + // If no targets are passed, we need to overwrite browserslist's defaults + // so that we enable all transforms (acting like the now deprecated + // preset-latest). + objectToBrowserslist(targets); + } + + // `esmodules` as a target indicates the specific set of browsers supporting ES Modules. + // These values OVERRIDE the `browsers` field. + if (esmodules && (esmodules !== "intersect" || !browsers)) { + browsers = Object.keys(ESM_SUPPORT) + .map(browser => `${browser} >= ${ESM_SUPPORT[browser]}`) + .join(", "); + esmodules = false; + } - const browsers = browserslist(browsersquery, { - path: options.configPath, - mobileToDesktop: true, - env: options.browserslistEnv, - }); + if (browsers) { + const queryBrowsers = resolveTargets(browsers); + + if (esmodules === "intersect") { + for (const browser of Object.keys(queryBrowsers)) { + const version = queryBrowsers[browser]; + + if (ESM_SUPPORT[browser]) { + queryBrowsers[browser] = getHighestUnreleased( + version, + semverify(ESM_SUPPORT[browser]), + browser, + ); + } else { + delete queryBrowsers[browser]; + } + } + } - const queryBrowsers = getLowestVersions(browsers); targets = Object.assign(queryBrowsers, targets); - - // Reset browserslist defaults - browserslist.defaults = browserslistDefaults; } // Parse remaining targets diff --git a/packages/babel-helper-compilation-targets/src/types.js b/packages/babel-helper-compilation-targets/src/types.js index 9e0bc3a06f5e..16dd02dd140d 100644 --- a/packages/babel-helper-compilation-targets/src/types.js +++ b/packages/babel-helper-compilation-targets/src/types.js @@ -22,11 +22,17 @@ export type TargetsTuple = {| [target: Target]: string, |}; -export type Browsers = string | Array; +export type Browsers = string | $ReadOnlyArray; export type InputTargets = { ...Targets, browsers?: Browsers, - esmodules?: boolean, + + // When `true`, this completely replaces the `browsers` option. + // When `intersect`, this is intersected with the `browsers` + // option (giving the higher browsers as the result). + // TODO(Babel 8): Make `true` behave like `intersect` and + // remove `intersect`. + esmodules?: boolean | "intersect", }; diff --git a/packages/babel-helper-compilation-targets/src/utils.js b/packages/babel-helper-compilation-targets/src/utils.js index 96c607531869..743cc128d48b 100644 --- a/packages/babel-helper-compilation-targets/src/utils.js +++ b/packages/babel-helper-compilation-targets/src/utils.js @@ -52,6 +52,14 @@ export function getLowestUnreleased(a: string, b: string, env: string): string { return semverMin(a, b); } +export function getHighestUnreleased( + a: string, + b: string, + env: string, +): string { + return getLowestUnreleased(a, b, env) === a ? b : a; +} + export function getLowestImplementedVersion( plugin: Targets, environment: Target, diff --git a/packages/babel-helper-compilation-targets/test/__snapshots__/targets-parser.spec.js.snap b/packages/babel-helper-compilation-targets/test/__snapshots__/targets-parser.spec.js.snap index 9b49cb2af4b6..e6ea5cbace26 100644 --- a/packages/babel-helper-compilation-targets/test/__snapshots__/targets-parser.spec.js.snap +++ b/packages/babel-helper-compilation-targets/test/__snapshots__/targets-parser.spec.js.snap @@ -1,5 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`getTargets esmodules can be intersected with a .browserslistrc file 1`] = ` +Object { + "chrome": "70.0.0", + "firefox": "60.0.0", +} +`; + +exports[`getTargets esmodules can be intersected with the browsers option 1`] = ` +Object { + "chrome": "70.0.0", + "firefox": "60.0.0", +} +`; + +exports[`getTargets esmodules explicit browser versions have the precedence over 'esmodules' 1`] = ` +Object { + "chrome": "20.0.0", + "firefox": "70.0.0", +} +`; + exports[`getTargets esmodules returns browser supporting modules and keyed browser overrides 1`] = ` Object { "android": "61.0.0", diff --git a/packages/babel-helper-compilation-targets/test/fixtures/.browserslistrc b/packages/babel-helper-compilation-targets/test/fixtures/.browserslistrc new file mode 100644 index 000000000000..595f09dab553 --- /dev/null +++ b/packages/babel-helper-compilation-targets/test/fixtures/.browserslistrc @@ -0,0 +1,2 @@ +chrome >= 70 +firefox >= 30 diff --git a/packages/babel-helper-compilation-targets/test/targets-parser.spec.js b/packages/babel-helper-compilation-targets/test/targets-parser.spec.js index 562ee2fc3b87..2119db119928 100644 --- a/packages/babel-helper-compilation-targets/test/targets-parser.spec.js +++ b/packages/babel-helper-compilation-targets/test/targets-parser.spec.js @@ -1,4 +1,5 @@ import browserslist from "browserslist"; +import { join } from "path"; import getTargets from ".."; describe("getTargets", () => { @@ -233,6 +234,48 @@ describe("getTargets", () => { }), ).toMatchSnapshot(); }); + + it("can be intersected with the browsers option", () => { + expect( + getTargets({ + esmodules: "intersect", + browsers: ["chrome >= 70", "firefox >= 30"], + }), + ).toMatchSnapshot(); + }); + + it("can be intersected with a .browserslistrc file", () => { + expect( + getTargets( + { + esmodules: "intersect", + }, + { configPath: join(__dirname, "fixtures", "foo.js") }, + ), + ).toMatchSnapshot(); + }); + + it("explicit browser versions have the precedence over 'esmodules'", () => { + expect( + getTargets({ + browsers: "chrome 5, firefox 5", + esmodules: "intersect", + chrome: 20, + firefox: 70, + }), + ).toMatchSnapshot(); + }); + + it("'intersect' behaves like 'true' if no browsers are specified", () => { + expect( + getTargets( + { esmodules: "intersect" }, + { ignoreBrowserslistConfig: true }, + ), + ).toEqual( + getTargets({ esmodules: true }, { ignoreBrowserslistConfig: true }), + ); + }); }); describe("node", () => { diff --git a/packages/babel-helper-plugin-utils/src/index.js b/packages/babel-helper-plugin-utils/src/index.js index f0ecb83a22e2..55fc7d6761af 100644 --- a/packages/babel-helper-plugin-utils/src/index.js +++ b/packages/babel-helper-plugin-utils/src/index.js @@ -1,19 +1,33 @@ export function declare(builder) { return (api, options, dirname) => { - if (!api.assertVersion) { - // Inject a custom version of 'assertVersion' for Babel 6 and early - // versions of Babel 7's beta that didn't have it. - api = Object.assign(copyApiObject(api), { - assertVersion(range) { - throwVersionError(range, api.version); - }, - }); + let clonedApi; + + for (const name of Object.keys(apiPolyfills)) { + if (api[name]) continue; + + // TODO: Use ??= when flow lets us to do so + clonedApi = clonedApi ?? copyApiObject(api); + clonedApi[name] = apiPolyfills[name](clonedApi); } - return builder(api, options || {}, dirname); + return builder(clonedApi ?? api, options || {}, dirname); }; } +const apiPolyfills = { + // Not supported by Babel 7 and early versions of Babel 7 beta. + // It's important that this is polyfilled for older Babel versions + // since it's needed to report the version mismatch. + assertVersion: api => range => { + throwVersionError(range, api.version); + }, + // This is supported starting from Babel 7.13 + // TODO(Babel 8): Remove this polyfill + targets: () => () => { + return {}; + }, +}; + function copyApiObject(api) { // Babel >= 7 <= beta.41 passed the API as a new object that had // babel/core as the prototype. While slightly faster, it also diff --git a/packages/babel-preset-env/src/index.js b/packages/babel-preset-env/src/index.js index e3cfb7a623ce..3cdf046c5faa 100644 --- a/packages/babel-preset-env/src/index.js +++ b/packages/babel-preset-env/src/index.js @@ -206,6 +206,45 @@ export const getPolyfillPlugins = ({ return polyfillPlugins; }; +function getLocalTargets( + optionsTargets, + ignoreBrowserslistConfig, + configPath, + browserslistEnv, +) { + // TODO: remove this in next major + let hasUglifyTarget = false; + + if (optionsTargets?.uglify) { + hasUglifyTarget = true; + delete optionsTargets.uglify; + + console.log(""); + console.log("The uglify target has been deprecated. Set the top level"); + console.log("option `forceAllTransforms: true` instead."); + console.log(""); + } + + if (optionsTargets?.esmodules && optionsTargets.browsers) { + console.log(""); + console.log( + "@babel/preset-env: esmodules and browsers targets have been specified together.", + ); + console.log( + // $FlowIgnore + `\`browsers\` target, \`${optionsTargets.browsers}\` will be ignored.`, + ); + console.log(""); + } + + const localTargets = getTargets( + // $FlowIgnore optionsTargets doesn't have an "uglify" property anymore + (optionsTargets: InputTargets), + { ignoreBrowserslistConfig, configPath, browserslistEnv }, + ); + return { hasUglifyTarget, localTargets }; +} + function supportsStaticESM(caller) { return !!caller?.supportsStaticESM; } @@ -225,6 +264,8 @@ function supportsTopLevelAwait(caller) { export default declare((api, opts) => { api.assertVersion(7); + const babelTargets = api.targets(); + const { bugfixes, configPath, @@ -242,41 +283,33 @@ export default declare((api, opts) => { corejs: { version: corejs, proposals }, browserslistEnv, } = normalizeOptions(opts); - // TODO: remove this in next major - let hasUglifyTarget = false; - - if (optionsTargets?.uglify) { - hasUglifyTarget = true; - delete optionsTargets.uglify; - console.log(""); - console.log("The uglify target has been deprecated. Set the top level"); - console.log("option `forceAllTransforms: true` instead."); - console.log(""); - } - - if (optionsTargets?.esmodules && optionsTargets.browsers) { - console.log(""); - console.log( - "@babel/preset-env: esmodules and browsers targets have been specified together.", - ); - console.log( - // $FlowIgnore - `\`browsers\` target, \`${optionsTargets.browsers}\` will be ignored.`, + let targets = babelTargets; + let transformTargets = forceAllTransforms ? {} : babelTargets; + + if ( + // If any browserslist-related option is specified, fallback to the old + // behavior of not using the targets specified in the top-level options. + opts.targets || + opts.configPath || + opts.browserslistEnv || + opts.ignoreBrowserslistConfig + ) { + const { hasUglifyTarget, localTargets } = getLocalTargets( + optionsTargets, + ignoreBrowserslistConfig, + configPath, + browserslistEnv, ); - console.log(""); + + targets = localTargets; + if (hasUglifyTarget) transformTargets = {}; + else if (!forceAllTransforms) transformTargets = localTargets; } - const targets = getTargets( - // $FlowIgnore optionsTargets doesn't have an "uglify" property anymore - (optionsTargets: InputTargets), - { ignoreBrowserslistConfig, configPath, browserslistEnv }, - ); const include = transformIncludesAndExcludes(optionsInclude); const exclude = transformIncludesAndExcludes(optionsExclude); - const transformTargets = forceAllTransforms || hasUglifyTarget ? {} : targets; - const compatData = getPluginList(shippedProposals, bugfixes); const shouldSkipExportNamespaceFrom = (modules === "auto" && api.caller?.(supportsExportNamespaceFrom)) || diff --git a/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/input.mjs b/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/input.mjs new file mode 100644 index 000000000000..fbb8052d71ab --- /dev/null +++ b/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/input.mjs @@ -0,0 +1 @@ +foo?.bar; diff --git a/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/options.json b/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/options.json new file mode 100644 index 000000000000..bd767ffbae7b --- /dev/null +++ b/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/options.json @@ -0,0 +1,8 @@ +{ + "validateLogs": true, + "ignoreOutput": true, + "targets": "chrome 80", + "presets": [ + ["env", { "debug": true, "targets": "chrome 60" }] + ] +} diff --git a/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/stdout.txt b/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/stdout.txt new file mode 100644 index 000000000000..2548b104e672 --- /dev/null +++ b/packages/babel-preset-env/test/fixtures/debug/top-level-targets-shadowed/stdout.txt @@ -0,0 +1,26 @@ +@babel/preset-env: `DEBUG` option + +Using targets: +{ + "chrome": "60" +} + +Using modules transform: auto + +Using plugins: + proposal-numeric-separator { "chrome":"60" } + proposal-logical-assignment-operators { "chrome":"60" } + proposal-nullish-coalescing-operator { "chrome":"60" } + proposal-optional-chaining { "chrome":"60" } + proposal-json-strings { "chrome":"60" } + proposal-optional-catch-binding { "chrome":"60" } + proposal-async-generator-functions { "chrome":"60" } + syntax-object-rest-spread { "chrome":"60" } + transform-dotall-regex { "chrome":"60" } + proposal-unicode-property-regex { "chrome":"60" } + transform-named-capturing-groups-regex { "chrome":"60" } + proposal-export-namespace-from { "chrome":"60" } + transform-modules-commonjs { "chrome":"60" } + proposal-dynamic-import { "chrome":"60" } + +Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set. diff --git a/packages/babel-preset-env/test/fixtures/debug/top-level-targets/input.mjs b/packages/babel-preset-env/test/fixtures/debug/top-level-targets/input.mjs new file mode 100644 index 000000000000..fbb8052d71ab --- /dev/null +++ b/packages/babel-preset-env/test/fixtures/debug/top-level-targets/input.mjs @@ -0,0 +1 @@ +foo?.bar; diff --git a/packages/babel-preset-env/test/fixtures/debug/top-level-targets/options.json b/packages/babel-preset-env/test/fixtures/debug/top-level-targets/options.json new file mode 100644 index 000000000000..dbc78d327863 --- /dev/null +++ b/packages/babel-preset-env/test/fixtures/debug/top-level-targets/options.json @@ -0,0 +1,8 @@ +{ + "validateLogs": true, + "ignoreOutput": true, + "targets": "chrome 80", + "presets": [ + ["env", { "debug": true }] + ] +} diff --git a/packages/babel-preset-env/test/fixtures/debug/top-level-targets/stdout.txt b/packages/babel-preset-env/test/fixtures/debug/top-level-targets/stdout.txt new file mode 100644 index 000000000000..762aee9a191e --- /dev/null +++ b/packages/babel-preset-env/test/fixtures/debug/top-level-targets/stdout.txt @@ -0,0 +1,23 @@ +@babel/preset-env: `DEBUG` option + +Using targets: +{ + "chrome": "80" +} + +Using modules transform: auto + +Using plugins: + syntax-numeric-separator { "chrome":"80" } + proposal-logical-assignment-operators { "chrome":"80" } + syntax-nullish-coalescing-operator { "chrome":"80" } + syntax-optional-chaining { "chrome":"80" } + syntax-json-strings { "chrome":"80" } + syntax-optional-catch-binding { "chrome":"80" } + syntax-async-generators { "chrome":"80" } + syntax-object-rest-spread { "chrome":"80" } + transform-modules-commonjs { "chrome":"80" } + proposal-dynamic-import { "chrome":"80" } + proposal-export-namespace-from {} + +Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set. diff --git a/yarn.lock b/yarn.lock index a566ecc10a22..ce3b242b9102 100644 --- a/yarn.lock +++ b/yarn.lock @@ -129,6 +129,7 @@ __metadata: dependencies: "@babel/code-frame": "workspace:^7.10.4" "@babel/generator": "workspace:^7.12.10" + "@babel/helper-compilation-targets": "workspace:^7.12.5" "@babel/helper-module-transforms": "workspace:^7.12.1" "@babel/helper-transform-fixture-test-runner": "workspace:*" "@babel/helpers": "workspace:^7.12.5"