Skip to content

Commit cb404e4

Browse files
Add targets and browserslist* options to @babel/core (#12189)
1 parent 31ca15e commit cb404e4

38 files changed

Lines changed: 736 additions & 109 deletions

File tree

packages/babel-core/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@
3939
},
4040
"browser": {
4141
"./lib/config/files/index.js": "./lib/config/files/index-browser.js",
42+
"./lib/config/resolve-targets.js": "./lib/config/resolve-targets-browser.js",
4243
"./lib/transform-file.js": "./lib/transform-file-browser.js",
4344
"./src/config/files/index.js": "./src/config/files/index-browser.js",
45+
"./src/config/resolve-targets.js": "./src/config/resolve-targets-browser.js",
4446
"./src/transform-file.js": "./src/transform-file-browser.js"
4547
},
4648
"dependencies": {
4749
"@babel/code-frame": "workspace:^7.12.13",
4850
"@babel/generator": "workspace:^7.12.17",
51+
"@babel/helper-compilation-targets": "workspace:^7.12.17",
4952
"@babel/helper-module-transforms": "workspace:^7.12.17",
5053
"@babel/helpers": "workspace:^7.12.17",
5154
"@babel/parser": "workspace:^7.12.17",

packages/babel-core/src/config/files/configuration.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
makeWeakCacheSync,
1010
type CacheConfigurator,
1111
} from "../caching";
12-
import makeAPI, { type PluginAPI } from "../helpers/config-api";
12+
import { makeConfigAPI, type ConfigAPI } from "../helpers/config-api";
1313
import { makeStaticFileCache } from "./utils";
1414
import loadCjsOrMjsDefault from "./module-types";
1515
import pathPatternToRegex from "../pattern-to-regex";
@@ -203,7 +203,7 @@ const readConfigJS = makeStrongCache(function* readConfigJS(
203203
let assertCache = false;
204204
if (typeof options === "function") {
205205
yield* []; // if we want to make it possible to use async configs
206-
options = ((options: any): (api: PluginAPI) => {})(makeAPI(cache));
206+
options = ((options: any): (api: ConfigAPI) => {})(makeConfigAPI(cache));
207207

208208
assertCache = true;
209209
}

packages/babel-core/src/config/full.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
type PresetInstance,
1515
} from "./config-chain";
1616
import type { UnloadedDescriptor } from "./config-descriptors";
17+
import type { Targets } from "@babel/helper-compilation-targets";
1718
import traverse from "@babel/traverse";
1819
import {
1920
makeWeakCache,
@@ -27,7 +28,7 @@ import {
2728
type PluginItem,
2829
} from "./validation/options";
2930
import { validatePluginObject } from "./validation/plugins";
30-
import makeAPI from "./helpers/config-api";
31+
import { makePluginAPI } from "./helpers/config-api";
3132

3233
import loadPrivatePartialConfig from "./partial";
3334
import type { ValidatedOptions } from "./validation/options";
@@ -39,6 +40,11 @@ type LoadedDescriptor = {
3940
alias: string,
4041
};
4142

43+
type PluginContext = {
44+
...ConfigContext,
45+
targets: Targets,
46+
};
47+
4248
export type { InputOptions } from "./validation/options";
4349

4450
export type ResolvedConfig = {
@@ -55,6 +61,7 @@ export type PluginPasses = Array<PluginPassList>;
5561
type SimpleContext = {
5662
envName: string,
5763
caller: CallerMetadata | void,
64+
targets: Targets,
5865
};
5966

6067
export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
@@ -78,6 +85,11 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
7885
throw new Error("Assertion failure - plugins and presets exist");
7986
}
8087

88+
const pluginContext: PluginContext = {
89+
...context,
90+
targets: options.targets,
91+
};
92+
8193
const toDescriptor = (item: PluginItem) => {
8294
const desc = getItemDescriptor(item);
8395
if (!desc) {
@@ -112,12 +124,12 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
112124
// in the previous pass.
113125
if (descriptor.ownPass) {
114126
presets.push({
115-
preset: yield* loadPresetDescriptor(descriptor, context),
127+
preset: yield* loadPresetDescriptor(descriptor, pluginContext),
116128
pass: [],
117129
});
118130
} else {
119131
presets.unshift({
120-
preset: yield* loadPresetDescriptor(descriptor, context),
132+
preset: yield* loadPresetDescriptor(descriptor, pluginContext),
121133
pass: pluginDescriptorsPass,
122134
});
123135
}
@@ -172,7 +184,7 @@ export default gensync<[any], ResolvedConfig | null>(function* loadFullConfig(
172184
const descriptor: UnloadedDescriptor = descs[i];
173185
if (descriptor.options !== false) {
174186
try {
175-
pass.push(yield* loadPluginDescriptor(descriptor, context));
187+
pass.push(yield* loadPluginDescriptor(descriptor, pluginContext));
176188
} catch (e) {
177189
if (e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") {
178190
// print special message for `plugins: ["@babel/foo", { foo: "option" }]`
@@ -235,7 +247,7 @@ const loadDescriptor = makeWeakCache(function* (
235247

236248
const api = {
237249
...context,
238-
...makeAPI(cache),
250+
...makePluginAPI(cache),
239251
};
240252
try {
241253
item = yield* factory(api, options, dirname);
@@ -375,7 +387,7 @@ const validatePreset = (
375387
*/
376388
function* loadPresetDescriptor(
377389
descriptor: UnloadedDescriptor,
378-
context: ConfigContext,
390+
context: PluginContext,
379391
): Handler<ConfigChain | null> {
380392
const preset = instantiatePreset(yield* loadDescriptor(descriptor, context));
381393
validatePreset(preset, context, descriptor);

packages/babel-core/src/config/helpers/config-api.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// @flow
22

33
import semver from "semver";
4+
import type { Targets } from "@babel/helper-compilation-targets";
5+
46
import { version as coreVersion } from "../../";
57
import {
68
assertSimpleType,
@@ -20,7 +22,9 @@ type EnvFunction = {
2022

2123
type CallerFactory = ((CallerMetadata | void) => mixed) => SimpleType;
2224

23-
export type PluginAPI = {|
25+
type TargetsFunction = () => Targets;
26+
27+
export type ConfigAPI = {|
2428
version: string,
2529
cache: SimpleCacheConfigurator,
2630
env: EnvFunction,
@@ -29,9 +33,14 @@ export type PluginAPI = {|
2933
caller?: CallerFactory,
3034
|};
3135

32-
export default function makeAPI(
33-
cache: CacheConfigurator<{ envName: string, caller: CallerMetadata | void }>,
34-
): PluginAPI {
36+
export type PluginAPI = {|
37+
...ConfigAPI,
38+
targets: TargetsFunction,
39+
|};
40+
41+
export function makeConfigAPI<
42+
SideChannel: { envName: string, caller: CallerMetadata | void },
43+
>(cache: CacheConfigurator<SideChannel>): ConfigAPI {
3544
const env: any = value =>
3645
cache.using(data => {
3746
if (typeof value === "undefined") return data.envName;
@@ -61,6 +70,22 @@ export default function makeAPI(
6170
};
6271
}
6372

73+
export function makePluginAPI(
74+
cache: CacheConfigurator<{
75+
envName: string,
76+
caller: CallerMetadata | void,
77+
targets: Targets,
78+
}>,
79+
): PluginAPI {
80+
const targets = () =>
81+
// We are using JSON.parse/JSON.stringify because it's only possible to cache
82+
// primitive values. We can safely stringify the targets object because it
83+
// only contains strings as its properties.
84+
// Please make the Record and Tuple proposal happen!
85+
JSON.parse(cache.using(data => JSON.stringify(data.targets)));
86+
return { ...makeConfigAPI(cache), targets };
87+
}
88+
6489
function assertVersion(range: string | number): void {
6590
if (typeof range === "number") {
6691
if (!Number.isInteger(range)) {

packages/babel-core/src/config/partial.js

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getEnv } from "./helpers/environment";
1414
import {
1515
validate,
1616
type ValidatedOptions,
17+
type NormalizedOptions,
1718
type RootMode,
1819
} from "./validation/options";
1920

@@ -24,6 +25,7 @@ import {
2425
type ConfigFile,
2526
type IgnoreFile,
2627
} from "./files";
28+
import { resolveTargets } from "./resolve-targets";
2729

2830
function* resolveRootMode(
2931
rootDir: string,
@@ -61,7 +63,7 @@ function* resolveRootMode(
6163
}
6264

6365
type PrivPartialConfig = {
64-
options: ValidatedOptions,
66+
options: NormalizedOptions,
6567
context: ConfigContext,
6668
fileHandling: FileHandling,
6769
ignore: IgnoreFile | void,
@@ -115,30 +117,36 @@ export default function* loadPrivatePartialConfig(
115117
const configChain = yield* buildRootChain(args, context);
116118
if (!configChain) return null;
117119

118-
const options = {};
120+
const merged: ValidatedOptions = {};
119121
configChain.options.forEach(opts => {
120-
mergeOptions(options, opts);
122+
mergeOptions((merged: any), opts);
121123
});
122124

123-
// Tack the passes onto the object itself so that, if this object is
124-
// passed back to Babel a second time, it will be in the right structure
125-
// to not change behavior.
126-
options.cloneInputAst = cloneInputAst;
127-
options.babelrc = false;
128-
options.configFile = false;
129-
options.passPerPreset = false;
130-
options.envName = context.envName;
131-
options.cwd = context.cwd;
132-
options.root = context.root;
133-
options.filename =
134-
typeof context.filename === "string" ? context.filename : undefined;
135-
136-
options.plugins = configChain.plugins.map(descriptor =>
137-
createItemFromDescriptor(descriptor),
138-
);
139-
options.presets = configChain.presets.map(descriptor =>
140-
createItemFromDescriptor(descriptor),
141-
);
125+
const options: NormalizedOptions = {
126+
...merged,
127+
targets: resolveTargets(merged, absoluteRootDir, filename),
128+
129+
// Tack the passes onto the object itself so that, if this object is
130+
// passed back to Babel a second time, it will be in the right structure
131+
// to not change behavior.
132+
cloneInputAst,
133+
babelrc: false,
134+
configFile: false,
135+
browserslistConfigFile: false,
136+
passPerPreset: false,
137+
envName: context.envName,
138+
cwd: context.cwd,
139+
root: context.root,
140+
filename:
141+
typeof context.filename === "string" ? context.filename : undefined,
142+
143+
plugins: configChain.plugins.map(descriptor =>
144+
createItemFromDescriptor(descriptor),
145+
),
146+
presets: configChain.presets.map(descriptor =>
147+
createItemFromDescriptor(descriptor),
148+
),
149+
};
142150

143151
return {
144152
options,
@@ -201,15 +209,15 @@ class PartialConfig {
201209
* These properties are public, so any changes to them should be considered
202210
* a breaking change to Babel's API.
203211
*/
204-
options: ValidatedOptions;
212+
options: NormalizedOptions;
205213
babelrc: string | void;
206214
babelignore: string | void;
207215
config: string | void;
208216
fileHandling: FileHandling;
209217
files: Set<string>;
210218

211219
constructor(
212-
options: ValidatedOptions,
220+
options: NormalizedOptions,
213221
babelrc: string | void,
214222
ignore: string | void,
215223
config: string | void,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @flow
2+
3+
import type { ValidatedOptions } from "./validation/options";
4+
import getTargets, { type Targets } from "@babel/helper-compilation-targets";
5+
6+
export function resolveTargets(
7+
options: ValidatedOptions,
8+
// eslint-disable-next-line no-unused-vars
9+
root: string,
10+
// eslint-disable-next-line no-unused-vars
11+
filename: string | void,
12+
): Targets {
13+
let { targets } = options;
14+
if (typeof targets === "string" || Array.isArray(targets)) {
15+
targets = { browsers: targets };
16+
}
17+
// $FlowIgnore it thinks that targets.esmodules doesn't exist.
18+
if (targets && targets.esmodules) {
19+
targets = { ...targets, esmodules: "intersect" };
20+
}
21+
22+
return getTargets((targets: any), {
23+
ignoreBrowserslistConfig: true,
24+
browserslistEnv: options.browserslistEnv,
25+
});
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// @flow
2+
3+
import typeof * as browserType from "./resolve-targets-browser";
4+
import typeof * as nodeType from "./resolve-targets";
5+
6+
// Kind of gross, but essentially asserting that the exports of this module are the same as the
7+
// exports of index-browser, since this file may be replaced at bundle time with index-browser.
8+
((({}: any): $Exact<browserType>): $Exact<nodeType>);
9+
10+
import type { ValidatedOptions } from "./validation/options";
11+
import path from "path";
12+
import getTargets, { type Targets } from "@babel/helper-compilation-targets";
13+
14+
export function resolveTargets(
15+
options: ValidatedOptions,
16+
root: string,
17+
filename: string | void,
18+
): Targets {
19+
let { targets } = options;
20+
if (typeof targets === "string" || Array.isArray(targets)) {
21+
targets = { browsers: targets };
22+
}
23+
// $FlowIgnore it thinks that targets.esmodules doesn't exist.
24+
if (targets && targets.esmodules) {
25+
targets = { ...targets, esmodules: "intersect" };
26+
}
27+
28+
let configFile;
29+
if (typeof options.browserslistConfigFile === "string") {
30+
configFile = path.resolve(root, options.browserslistConfigFile);
31+
}
32+
33+
return getTargets((targets: any), {
34+
ignoreBrowserslistConfig: options.browserslistConfigFile === false,
35+
configFile,
36+
configPath: filename ?? root,
37+
browserslistEnv: options.browserslistEnv,
38+
});
39+
}

packages/babel-core/src/config/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// @flow
22

3-
import type { ValidatedOptions } from "./validation/options";
3+
import type { ValidatedOptions, NormalizedOptions } from "./validation/options";
44

55
export function mergeOptions(
66
target: ValidatedOptions,
7-
source: ValidatedOptions,
7+
source: ValidatedOptions | NormalizedOptions,
88
): void {
99
for (const k of Object.keys(source)) {
1010
if (k === "parserOpts" && source.parserOpts) {

0 commit comments

Comments
 (0)