Merge pull request #8485 from loganfsmyth/caller-option

Allow preset-env to toggle module handling based on flags from the caller (like babel-loader)
This commit is contained in:
Logan Smyth 2018-08-20 10:44:11 -07:00 committed by GitHub
commit 2a4f162366
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 315 additions and 58 deletions

View File

@ -49,9 +49,14 @@ export function addSourceMappingUrl(code, loc) {
return code + "\n//# sourceMappingURL=" + path.basename(loc);
}
const CALLER = {
name: "@babel/cli",
};
export function transform(filename, code, opts) {
opts = {
...opts,
caller: CALLER,
filename,
};
@ -64,6 +69,11 @@ export function transform(filename, code, opts) {
}
export function compile(filename, opts) {
opts = {
...opts,
caller: CALLER,
};
return new Promise((resolve, reject) => {
babel.transformFile(filename, opts, (err, result) => {
if (err) reject(err);

View File

@ -206,12 +206,29 @@ function makeSimpleConfigurator(
return;
}
return cache.using(val);
return cache.using(() => assertSimpleType(val()));
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => cb());
cacheFn.invalidate = cb => cache.invalidate(() => cb());
cacheFn.using = cb => cache.using(() => assertSimpleType(cb()));
cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb()));
return (cacheFn: any);
}
// Types are limited here so that in the future these values can be used
// as part of Babel's caching logic.
type SimpleType = string | boolean | number | null | void;
export function assertSimpleType(value: mixed): SimpleType {
if (
value != null &&
typeof value !== "string" &&
typeof value !== "boolean" &&
typeof value !== "number"
) {
throw new Error(
"Cache keys must be either string, boolean, number, null, or undefined.",
);
}
return value;
}

View File

@ -8,6 +8,7 @@ import {
type IgnoreList,
type ConfigApplicableTest,
type BabelrcSearch,
type CallerMetadata,
} from "./validation/options";
import pathPatternToRegex from "./pattern-to-regex";
@ -50,12 +51,27 @@ export type ConfigContext = {
cwd: string,
root: string,
envName: string,
caller: CallerMetadata | void,
};
/**
* Build a config chain for a given preset.
*/
export const buildPresetChain: (
export function buildPresetChain(
arg: PresetInstance,
context: *,
): ConfigChain | null {
const chain = buildPresetChainWalker(arg, context);
if (!chain) return null;
return {
plugins: dedupDescriptors(chain.plugins),
presets: dedupDescriptors(chain.presets),
options: chain.options,
};
}
export const buildPresetChainWalker: (
arg: PresetInstance,
context: *,
) => * = makeChainWalker({
@ -128,9 +144,14 @@ export function buildRootChain(
let configFile;
if (typeof opts.configFile === "string") {
configFile = loadConfig(opts.configFile, context.cwd, context.envName);
configFile = loadConfig(
opts.configFile,
context.cwd,
context.envName,
context.caller,
);
} else if (opts.configFile !== false) {
configFile = findRootConfig(context.root, context.envName);
configFile = findRootConfig(context.root, context.envName, context.caller);
}
let { babelrc, babelrcRoots } = opts;
@ -234,7 +255,7 @@ function babelrcLoadEnabled(
if (typeof pat === "string") pat = pathPatternToRegex(pat, context.cwd);
return pkgData.directories.some(directory => {
return matchPattern(pat, context.cwd, directory);
return matchPattern(pat, context.cwd, directory, context);
});
});
}
@ -449,7 +470,12 @@ function mergeExtendsChain(
): boolean {
if (opts.extends === undefined) return true;
const file = loadConfig(opts.extends, dirname, context.envName);
const file = loadConfig(
opts.extends,
dirname,
context.envName,
context.caller,
);
if (files.has(file)) {
throw new Error(
@ -629,12 +655,23 @@ function matchesPatterns(
dirname: string,
): boolean {
return patterns.some(pattern =>
matchPattern(pattern, dirname, context.filename),
matchPattern(pattern, dirname, context.filename, context),
);
}
function matchPattern(pattern, dirname, pathToTest): boolean {
if (typeof pattern === "function") return !!pattern(pathToTest);
function matchPattern(
pattern,
dirname,
pathToTest,
context: ConfigContext,
): boolean {
if (typeof pattern === "function") {
return !!pattern(pathToTest, {
dirname,
envName: context.envName,
caller: context.caller,
});
}
if (typeof pathToTest !== "string") {
throw new Error(

View File

@ -14,6 +14,7 @@ import makeAPI from "../helpers/config-api";
import { makeStaticFileCache } from "./utils";
import pathPatternToRegex from "../pattern-to-regex";
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";
import type { CallerMetadata } from "../validation/options";
const debug = buildDebug("babel:config:loading:files:configuration");
@ -26,6 +27,7 @@ const BABELIGNORE_FILENAME = ".babelignore";
export function findRelativeConfig(
packageData: FilePackageData,
envName: string,
caller: CallerMetadata | void,
): RelativeConfig {
let config = null;
let ignore = null;
@ -37,7 +39,7 @@ export function findRelativeConfig(
config = [BABELRC_FILENAME, BABELRC_JS_FILENAME].reduce(
(previousConfig: ConfigFile | null, name) => {
const filepath = path.join(loc, name);
const config = readConfig(filepath, envName);
const config = readConfig(filepath, envName, caller);
if (config && previousConfig) {
throw new Error(
@ -91,10 +93,11 @@ export function findRelativeConfig(
export function findRootConfig(
dirname: string,
envName: string,
caller: CallerMetadata | void,
): ConfigFile | null {
const filepath = path.resolve(dirname, BABEL_CONFIG_JS_FILENAME);
const conf = readConfig(filepath, envName);
const conf = readConfig(filepath, envName, caller);
if (conf) {
debug("Found root config %o in $o.", BABEL_CONFIG_JS_FILENAME, dirname);
}
@ -105,10 +108,11 @@ export function loadConfig(
name: string,
dirname: string,
envName: string,
caller: CallerMetadata | void,
): ConfigFile {
const filepath = resolve.sync(name, { basedir: dirname });
const conf = readConfig(filepath, envName);
const conf = readConfig(filepath, envName, caller);
if (!conf) {
throw new Error(`Config file ${filepath} contains no configuration data`);
}
@ -121,16 +125,22 @@ export function loadConfig(
* Read the given config file, returning the result. Returns null if no config was found, but will
* throw if there are parsing errors while loading a config.
*/
function readConfig(filepath, envName): ConfigFile | null {
function readConfig(filepath, envName, caller): ConfigFile | null {
return path.extname(filepath) === ".js"
? readConfigJS(filepath, { envName })
? readConfigJS(filepath, { envName, caller })
: readConfigJSON5(filepath);
}
const LOADING_CONFIGS = new Set();
const readConfigJS = makeStrongCache(
(filepath, cache: CacheConfigurator<{ envName: string }>) => {
(
filepath,
cache: CacheConfigurator<{
envName: string,
caller: CallerMetadata | void,
}>,
) => {
if (!fs.existsSync(filepath)) {
cache.forever();
return null;

View File

@ -7,6 +7,8 @@ import type {
FilePackageData,
} from "./types";
import type { CallerMetadata } from "../validation/options";
export type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData };
export function findPackageData(filepath: string): FilePackageData {
@ -21,6 +23,7 @@ export function findPackageData(filepath: string): FilePackageData {
export function findRelativeConfig(
pkgData: FilePackageData, // eslint-disable-line no-unused-vars
envName: string, // eslint-disable-line no-unused-vars
caller: CallerMetadata | void, // eslint-disable-line no-unused-vars
): RelativeConfig {
return { pkg: null, config: null, ignore: null };
}
@ -28,6 +31,7 @@ export function findRelativeConfig(
export function findRootConfig(
dirname: string, // eslint-disable-line no-unused-vars
envName: string, // eslint-disable-line no-unused-vars
caller: CallerMetadata | void, // eslint-disable-line no-unused-vars
): ConfigFile | null {
return null;
}
@ -36,6 +40,7 @@ export function loadConfig(
name: string,
dirname: string,
envName: string, // eslint-disable-line no-unused-vars
caller: CallerMetadata | void, // eslint-disable-line no-unused-vars
): ConfigFile {
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
}

View File

@ -13,7 +13,7 @@ import {
import type { UnloadedDescriptor } from "./config-descriptors";
import traverse from "@babel/traverse";
import { makeWeakCache, type CacheConfigurator } from "./caching";
import { validate } from "./validation/options";
import { validate, type CallerMetadata } from "./validation/options";
import { validatePluginObject } from "./validation/plugins";
import makeAPI from "./helpers/config-api";
@ -41,6 +41,7 @@ export type PluginPasses = Array<PluginPassList>;
// process 'ignore'/'only' and other filename-based logic.
type SimpleContext = {
envName: string,
caller: CallerMetadata | void,
};
export default function loadFullConfig(

View File

@ -2,7 +2,13 @@
import semver from "semver";
import { version as coreVersion } from "../../";
import type { CacheConfigurator, SimpleCacheConfigurator } from "../caching";
import {
assertSimpleType,
type CacheConfigurator,
type SimpleCacheConfigurator,
} from "../caching";
import type { CallerMetadata } from "../validation/options";
type EnvFunction = {
(): string,
@ -20,12 +26,14 @@ export type PluginAPI = {
};
export default function makeAPI(
cache: CacheConfigurator<{ envName: string }>,
cache: CacheConfigurator<{ envName: string, caller: CallerMetadata | void }>,
): PluginAPI {
const env: any = value =>
cache.using(data => {
if (typeof value === "undefined") return data.envName;
if (typeof value === "function") return value(data.envName);
if (typeof value === "function") {
return assertSimpleType(value(data.envName));
}
if (!Array.isArray(value)) value = [value];
return value.some(entry => {
@ -36,12 +44,16 @@ export default function makeAPI(
});
});
const caller: any = cb =>
cache.using(data => assertSimpleType(cb(data.caller)));
return {
version: coreVersion,
cache: cache.simple(),
// Expose ".env()" so people can easily get the same env that we expose using the "env" key.
env,
async: () => false,
caller,
assertVersion,
};
}

View File

@ -28,7 +28,7 @@ export default function loadPrivatePartialConfig(
const args = inputOpts ? validate("arguments", inputOpts) : {};
const { envName = getEnv(), cwd = ".", root: rootDir = "." } = args;
const { envName = getEnv(), cwd = ".", root: rootDir = ".", caller } = args;
const absoluteCwd = path.resolve(cwd);
const absoluteRootDir = path.resolve(absoluteCwd, rootDir);
@ -40,6 +40,7 @@ export default function loadPrivatePartialConfig(
cwd: absoluteCwd,
root: absoluteRootDir,
envName,
caller,
};
const configChain = buildRootChain(args, context);

View File

@ -14,6 +14,7 @@ import type {
CompactOption,
RootInputSourceMapOption,
NestingPath,
CallerMetadata,
} from "./options";
export type ValidatorSet = {
@ -103,6 +104,41 @@ export function assertSourceType(
return value;
}
export function assertCallerMetadata(
loc: OptionPath,
value: mixed,
): CallerMetadata | void {
const obj = assertObject(loc, value);
if (obj) {
if (typeof obj[("name": string)] !== "string") {
throw new Error(
`${msg(loc)} set but does not contain "name" property string`,
);
}
for (const prop of Object.keys(obj)) {
const propLoc = access(loc, prop);
const value = obj[prop];
if (
value != null &&
typeof value !== "boolean" &&
typeof value !== "string" &&
typeof value !== "number"
) {
// NOTE(logan): I'm limiting the type here so that we can guarantee that
// the "caller" value will serialize to JSON nicely. We can always
// allow more complex structures later though.
throw new Error(
`${msg(
propLoc,
)} must be null, undefined, a boolean, a string, or a number.`,
);
}
}
}
return (value: any);
}
export function assertInputSourceMap(
loc: OptionPath,
value: mixed,

View File

@ -11,6 +11,7 @@ import {
assertBoolean,
assertObject,
assertArray,
assertCallerMetadata,
assertInputSourceMap,
assertIgnoreList,
assertPluginList,
@ -33,6 +34,9 @@ const ROOT_VALIDATORS: ValidatorSet = {
$PropertyType<ValidatedOptions, "configFile">,
>),
caller: (assertCallerMetadata: Validator<
$PropertyType<ValidatedOptions, "caller">,
>),
filename: (assertString: Validator<
$PropertyType<ValidatedOptions, "filename">,
>),
@ -176,6 +180,7 @@ export type ValidatedOptions = {
ast?: boolean,
inputSourceMap?: RootInputSourceMapOption,
envName?: string,
caller?: CallerMetadata,
extends?: string,
env?: EnvSet<ValidatedOptions>,
@ -225,6 +230,11 @@ export type ValidatedOptions = {
generatorOpts?: {},
};
export type CallerMetadata = {
// If 'caller' is specified, require that the name is given for debugging
// messages.
name: string,
};
export type EnvSet<T> = {
[string]: ?T,
};

View File

@ -323,4 +323,51 @@ describe("@babel/core config loading", () => {
}
});
});
describe("caller metadata", () => {
it("should pass caller data through", () => {
const options1 = loadConfig({
...makeOpts(),
caller: {
name: "babel-test",
someFlag: true,
},
}).options;
expect(options1.caller.name).toBe("babel-test");
expect(options1.caller.someFlag).toBe(true);
});
it("should pass unknown caller data through", () => {
const options1 = loadConfig({
...makeOpts(),
caller: undefined,
}).options;
expect(options1.caller).toBeUndefined();
});
it("should pass caller data to test functions", () => {
const options1 = loadConfig({
...makeOpts(),
caller: {
name: "babel-test",
someFlag: true,
},
overrides: [
{
test: (filename, { caller }) => caller.name === "babel-test",
comments: false,
},
{
test: (filename, { caller }) => caller.name !== "babel-test",
ast: false,
},
],
}).options;
expect(options1.comments).toBe(false);
expect(options1.ast).not.toBe(false);
});
});
});

View File

@ -60,6 +60,9 @@ program.usage("[options] [ -e script | script.js ] [arguments]");
program.parse(process.argv);
register({
caller: {
name: "@babel/node",
},
extensions: program.extensions,
ignore: program.ignore,
only: program.only,

View File

@ -155,6 +155,10 @@ const filterItems = (
return result;
};
function supportsStaticESM(caller) {
return !!(caller && caller.supportsStaticESM);
}
export default declare((api, opts) => {
api.assertVersion(7);
@ -232,9 +236,15 @@ export default declare((api, opts) => {
const plugins = [];
const pluginUseBuiltIns = useBuiltIns !== false;
// NOTE: not giving spec here yet to avoid compatibility issues when
// transform-modules-commonjs gets its spec mode
if (modules !== false && moduleTransformations[modules]) {
if (
modules !== false &&
moduleTransformations[modules] &&
// TODO: Remove the 'api.caller' check eventually. Just here to prevent
// unnecessary breakage in the short term for users on older betas/RCs.
(modules !== "auto" || !api.caller || !api.caller(supportsStaticESM))
) {
// NOTE: not giving spec here yet to avoid compatibility issues when
// transform-modules-commonjs gets its spec mode
plugins.push([getPlugin(moduleTransformations[modules]), { loose }]);
}

View File

@ -1,4 +1,5 @@
export default {
auto: "transform-modules-commonjs",
amd: "transform-modules-amd",
commonjs: "transform-modules-commonjs",
cjs: "transform-modules-commonjs",

View File

@ -128,13 +128,16 @@ export const validateIgnoreBrowserslistConfig = (
);
export const validateModulesOption = (
modulesOpt: ModuleOption = ModulesOption.commonjs,
modulesOpt: ModuleOption = ModulesOption.auto,
) => {
invariant(
ModulesOption[modulesOpt] ||
ModulesOption[modulesOpt] === ModulesOption.false,
`Invalid Option: The 'modules' option must be either 'false' to indicate no modules, or a
module type which can be be one of: 'commonjs' (default), 'amd', 'umd', 'systemjs'.`,
`Invalid Option: The 'modules' option must be one of \n` +
` - 'false' to indicate no module processing\n` +
` - a specific module type: 'commonjs', 'amd', 'umd', 'systemjs'` +
` - 'auto' (default) which will automatically select 'false' if the current\n` +
` process is known to support ES module syntax, or "commonjs" otherwise\n`,
);
return modulesOpt;

View File

@ -15,6 +15,7 @@ export const TopLevelOptions = {
export const ModulesOption = {
false: false,
auto: "auto",
amd: "amd",
commonjs: "commonjs",
cjs: "cjs",

View File

@ -5,7 +5,7 @@ Using targets:
"android": "4"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "android":"4" }

View File

@ -5,7 +5,7 @@ Using targets:
"node": "6"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-function-name { "node":"6" }

View File

@ -7,7 +7,7 @@ Using targets:
"node": "6"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"10" }

View File

@ -12,7 +12,7 @@ Using targets:
"electron": "0.36"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-function-name { "electron":"0.36" }

View File

@ -13,7 +13,7 @@ Using targets:
"node": "7.4"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-literals { "firefox":"52" }

View File

@ -5,7 +5,7 @@ Using targets:
"chrome": "60"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-dotall-regex { "chrome":"60" }

View File

@ -3,7 +3,7 @@
Using targets:
{}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals {}

View File

@ -10,7 +10,7 @@ Using targets:
"safari": "7"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"10", "safari":"7" }

View File

@ -7,7 +7,7 @@ Using targets:
"ie": "11"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"11" }

View File

@ -5,7 +5,7 @@ Using targets:
"chrome": "55"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-dotall-regex { "chrome":"55" }

View File

@ -7,7 +7,7 @@ Using targets:
"ie": "11"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"11" }

View File

@ -16,7 +16,7 @@ Using targets:
"node": "6.1"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"10" }

View File

@ -7,7 +7,7 @@ Using targets:
"node": "6.10"
}
Using modules transform: commonjs
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"10" }

View File

@ -0,0 +1,2 @@
import mod from "mod";
console.log(mod);

View File

@ -0,0 +1,9 @@
{
"caller": {
"name": "test-fixture",
"supportsStaticESM": false
},
"presets": [
"env"
]
}

View File

@ -0,0 +1,7 @@
"use strict";
var _mod = _interopRequireDefault(require("mod"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_mod.default);

View File

@ -0,0 +1,2 @@
import mod from "mod";
console.log(mod);

View File

@ -0,0 +1,9 @@
{
"caller": {
"name": "test-fixture",
"supportsStaticESM": true
},
"presets": [
"env"
]
}

View File

@ -0,0 +1,2 @@
import mod from "mod";
console.log(mod);

View File

@ -0,0 +1,2 @@
import mod from "mod";
console.log(mod);

View File

@ -0,0 +1,5 @@
{
"presets": [
"env"
]
}

View File

@ -0,0 +1,7 @@
"use strict";
var _mod = _interopRequireDefault(require("mod"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_mod.default);

View File

@ -134,16 +134,20 @@ describe("normalize-options", () => {
});
describe("validateModulesOption", () => {
it("`undefined` option returns commonjs", () => {
expect(validateModulesOption()).toBe("commonjs");
it("`undefined` option returns auto", () => {
expect(validateModulesOption()).toBe("auto");
});
it("`false` option returns commonjs", () => {
it("`false` option returns false", () => {
expect(validateModulesOption(false)).toBe(false);
});
it("auto option is valid", () => {
expect(validateModulesOption("auto")).toBe("auto");
});
it("commonjs option is valid", () => {
expect(validateModulesOption()).toBe("commonjs");
expect(validateModulesOption("commonjs")).toBe("commonjs");
});
it("systemjs option is valid", () => {

View File

@ -125,6 +125,10 @@ export default function register(opts?: Object = {}) {
transformOpts = {
...opts,
caller: {
name: "@babel/register",
...(opts.caller || {}),
},
};
let { cwd = "." } = transformOpts;