Expose the partial Babel config for people to load and mutate.

This commit is contained in:
Logan Smyth 2018-03-02 16:46:04 -08:00
parent 53825f8152
commit fef5c7e523
10 changed files with 722 additions and 355 deletions

View File

@ -123,6 +123,78 @@ const parsedAst = babylon.parse(sourceCode, { allowReturnOutsideFunction: true }
const { code, map, ast } = babel.transformFromAstSync(parsedAst, sourceCode, options);
```
## babel.parse(code: string, [options?](#options): Object)
Given some code, parse it using Babel's standard behavior. Referenced presets and
plugins will be loaded such that optional syntax plugins are automatically
enabled.
## Advanced APIs
Many systems that wrap Babel like to automatically inject plugins and presets,
or override options. To accomplish this goal, Babel exposes several functions
that aid in loading the configuration part-way without transforming.
### babel.loadOptions([options?](#options): Object)
Resolve Babel's options fully, resulting in an options object where:
* `opts.plugins` is a full list of `Plugin` instances.
* `opts.presets` is empty and all presets are flattened into `opts`.
* It can be safely passed back to Babel. Fields like `babelrc` have been set to
false so that later calls to Babel will not make a second attempt to load
config files.
`Plugin` instances aren't meant to be manipulated directly, but often
callers will serialize this `opts` to JSON to use it as a cache key representing
the options Babel has received. Caching on this isn't 100% guaranteed to
invalidate properly, but it is the best we have at the moment.
### babel.loadPartialConfig([options?](#options): Object): PartialConfig
To allow systems to easily manipulate and validate a user's config, this function
resolves the plugins and presets and proceeds no further. The expectation is
that callers will take the config's `.options`, manipulate it as then see fit
and pass it back to Babel again.
* `babelrc: string | void` - The path of the `.babelrc` file, if there was one.
* `babelignore: string | void` - The path of the `.babelignore` file, if there was one.
* `options: ValidatedOptions` - The partially resolved options, which can be manipulated and passed back to Babel again.
* `plugins: Array<ConfigItem>` - See below.
* `presets: Array<ConfigItem>` - See below.
* It can be safely passed back to Babel. Fields like `babelrc` have been set
to false so that later calls to Babel will not make a second attempt to
load config files.
[`ConfigItem`](#configitem-type) instances expose properties to introspect the values, but each
item should be treated as immutable. If changes are desired, the item should be
removed from the list and replaced with either a normal Babel config value, or
with a replacement item created by `babel.createConfigItem`. See that
function for information about `ConfigItem` fields.
### babel.createConfigItem(value: string | {} | Function, options?: {}, { dirname?: string, name?: string, type?: "preset" | "plugin" }): ConfigItem
Allows build tooling to create and cache config items up front. If this function
is called multiple times for a given plugin, Babel will call the plugin's function itself
multiple times. If you have a clear set of expected plugins and presets to
inject, pre-constructing the config items would be recommended.
### `ConfigItem` type
Each `ConfigItem` exposes all of the information Babel knows. The fields are:
* `value: {} | Function` - The resolved value of the plugin.
* `options: {} | void` - The options object passed to the plugin.
* `dirname: string` - The path that the options are relative to.
* `name: string | void` - The name that the user gave the plugin instance, e.g. `plugins: [ ['env', {}, 'my-env'] ]`
* `file: Object | void` - Information about the plugin's file, if Babel knows it.
* `request: string` - The file that the user requested, e.g. `"@babel/env"`
* `resolved: string` - The full path of the resolved file, e.g. `"/tmp/node_modules/@babel/preset-env/lib/index.js"`
## Options

View File

@ -12,7 +12,12 @@ import {
const debug = buildDebug("babel:config:config-chain");
import { findRelativeConfig, loadConfig, type ConfigFile } from "./files";
import {
findRelativeConfig,
loadConfig,
type ConfigFile,
type IgnoreFile,
} from "./files";
import { makeWeakCache, makeStrongCache } from "./caching";
@ -99,13 +104,18 @@ const loadPresetOverridesEnvDescriptors = makeWeakCache(
),
);
export type RootConfigChain = ConfigChain & {
babelrc: ConfigFile | void,
ignore: IgnoreFile | void,
};
/**
* Build a config chain for Babel's full root configuration.
*/
export function buildRootChain(
opts: ValidatedOptions,
context: ConfigContext,
): ConfigChain | null {
): RootConfigChain | null {
const programmaticChain = loadProgrammaticChain(
{
options: opts,
@ -115,19 +125,24 @@ export function buildRootChain(
);
if (!programmaticChain) return null;
let ignore, babelrc;
const fileChain = emptyChain();
// resolve all .babelrc files
if (opts.babelrc !== false && context.filename !== null) {
const filename = context.filename;
const { ignore, config } = findRelativeConfig(filename, context.envName);
({ ignore, config: babelrc } = findRelativeConfig(
filename,
context.envName,
));
if (ignore && shouldIgnore(context, ignore.ignore, null, ignore.dirname)) {
return null;
}
if (config) {
const result = loadFileChain(config, context);
if (babelrc) {
const result = loadFileChain(babelrc, context);
if (!result) return null;
mergeChain(fileChain, result);
@ -145,6 +160,8 @@ export function buildRootChain(
plugins: dedupDescriptors(chain.plugins),
presets: dedupDescriptors(chain.presets),
options: chain.options.map(o => normalizeOptions(o)),
ignore: ignore || undefined,
babelrc: babelrc || undefined,
};
}

View File

@ -2,6 +2,8 @@
import { loadPlugin, loadPreset } from "./files";
import { getItemDescriptor } from "./item";
import {
makeWeakCache,
makeStrongCache,
@ -33,6 +35,10 @@ export type UnloadedDescriptor = {
dirname: string,
alias: string,
ownPass?: boolean,
file?: {
request: string,
resolved: string,
} | void,
};
export type ValidatedFile = {
@ -152,16 +158,11 @@ function createDescriptors(
ownPass?: boolean,
): Array<UnloadedDescriptor> {
const descriptors = items.map((item, index) =>
createDescriptor(
item,
type === "plugin" ? loadPlugin : loadPreset,
dirname,
{
index,
alias,
ownPass: !!ownPass,
},
),
createDescriptor(item, dirname, {
type,
alias: `${alias}$${index}`,
ownPass: !!ownPass,
}),
);
assertNoDuplicates(descriptors);
@ -172,20 +173,24 @@ function createDescriptors(
/**
* Given a plugin/preset item, resolve it into a standard format.
*/
function createDescriptor(
export function createDescriptor(
pair: PluginItem,
resolver,
dirname,
dirname: string,
{
index,
type,
alias,
ownPass,
}: {
index: number,
type?: "plugin" | "preset",
alias: string,
ownPass?: boolean,
},
): UnloadedDescriptor {
const desc = getItemDescriptor(pair);
if (desc) {
return desc;
}
let name;
let options;
let value = pair;
@ -198,9 +203,23 @@ function createDescriptor(
}
}
let file = undefined;
let filepath = null;
if (typeof value === "string") {
if (typeof type !== "string") {
throw new Error(
"To resolve a string-based item, the type of item must be given",
);
}
const resolver = type === "plugin" ? loadPlugin : loadPreset;
const request = value;
({ filepath, value } = resolver(value, dirname));
file = {
request,
resolved: filepath,
};
}
if (!value) {
@ -232,11 +251,12 @@ function createDescriptor(
return {
name,
alias: filepath || `${alias}$${index}`,
alias: filepath || alias,
value,
options,
dirname,
ownPass,
file,
};
}

View File

@ -0,0 +1,300 @@
// @flow
import { mergeOptions } from "./util";
import * as context from "../index";
import Plugin from "./plugin";
import { getItemDescriptor } from "./item";
import {
buildPresetChain,
type ConfigContext,
type ConfigChain,
type PresetInstance,
} from "./config-chain";
import type { UnloadedDescriptor } from "./config-descriptors";
import traverse from "@babel/traverse";
import { makeWeakCache, type CacheConfigurator } from "./caching";
import { validate } from "./validation/options";
import { validatePluginObject } from "./validation/plugins";
import makeAPI from "./helpers/config-api";
import loadPrivatePartialConfig from "./partial";
type LoadedDescriptor = {
value: {},
options: {},
dirname: string,
alias: string,
};
export type { InputOptions } from "./validation/options";
export type ResolvedConfig = {
options: Object,
passes: PluginPasses,
};
export type { Plugin };
export type PluginPassList = Array<Plugin>;
export type PluginPasses = Array<PluginPassList>;
// Context not including filename since it is used in places that cannot
// process 'ignore'/'only' and other filename-based logic.
type SimpleContext = {
envName: string,
};
export default function loadFullConfig(
inputOpts: mixed,
): ResolvedConfig | null {
const result = loadPrivatePartialConfig(inputOpts);
if (!result) {
return null;
}
const { options, context } = result;
const optionDefaults = {};
const passes = [[]];
try {
const { plugins, presets } = options;
if (!plugins || !presets) {
throw new Error("Assertion failure - plugins and presets exist");
}
const ignored = (function recurseDescriptors(
config: {
plugins: Array<UnloadedDescriptor>,
presets: Array<UnloadedDescriptor>,
},
pass: Array<Plugin>,
) {
const plugins = config.plugins.map(descriptor => {
return loadPluginDescriptor(descriptor, context);
});
const presets = config.presets.map(descriptor => {
return {
preset: loadPresetDescriptor(descriptor, context),
pass: descriptor.ownPass ? [] : pass,
};
});
// resolve presets
if (presets.length > 0) {
// The passes are created in the same order as the preset list, but are inserted before any
// existing additional passes.
passes.splice(
1,
0,
...presets.map(o => o.pass).filter(p => p !== pass),
);
for (const { preset, pass } of presets) {
if (!preset) return true;
const ignored = recurseDescriptors(
{
plugins: preset.plugins,
presets: preset.presets,
},
pass,
);
if (ignored) return true;
preset.options.forEach(opts => {
mergeOptions(optionDefaults, opts);
});
}
}
// resolve plugins
if (plugins.length > 0) {
pass.unshift(...plugins);
}
})(
{
plugins: plugins.map(item => {
const desc = getItemDescriptor(item);
if (!desc) {
throw new Error("Assertion failure - must be config item");
}
return desc;
}),
presets: presets.map(item => {
const desc = getItemDescriptor(item);
if (!desc) {
throw new Error("Assertion failure - must be config item");
}
return desc;
}),
},
passes[0],
);
if (ignored) return null;
} catch (e) {
// There are a few case where thrown errors will try to annotate themselves multiple times, so
// to keep things simple we just bail out if re-wrapping the message.
if (!/^\[BABEL\]/.test(e.message)) {
e.message = `[BABEL] ${context.filename || "unknown"}: ${e.message}`;
}
throw e;
}
const opts: Object = optionDefaults;
mergeOptions(opts, options);
opts.plugins = passes[0];
opts.presets = passes
.slice(1)
.filter(plugins => plugins.length > 0)
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;
return {
options: opts,
passes: passes,
};
}
/**
* Load a generic plugin/preset from the given descriptor loaded from the config object.
*/
const loadDescriptor = makeWeakCache(
(
{ value, options, dirname, alias }: UnloadedDescriptor,
cache: CacheConfigurator<SimpleContext>,
): LoadedDescriptor => {
// Disabled presets should already have been filtered out
if (options === false) throw new Error("Assertion failure");
options = options || {};
let item = value;
if (typeof value === "function") {
const api = Object.assign(Object.create(context), makeAPI(cache));
try {
item = value(api, options, dirname);
} catch (e) {
if (alias) {
e.message += ` (While processing: ${JSON.stringify(alias)})`;
}
throw e;
}
}
if (!item || typeof item !== "object") {
throw new Error("Plugin/Preset did not return an object.");
}
if (typeof item.then === "function") {
throw new Error(
`You appear to be using an async plugin, ` +
`which your current version of Babel does not support.` +
`If you're using a published plugin, ` +
`you may need to upgrade your @babel/core version.`,
);
}
return { value: item, options, dirname, alias };
},
);
/**
* Instantiate a plugin for the given descriptor, returning the plugin/options pair.
*/
function loadPluginDescriptor(
descriptor: UnloadedDescriptor,
context: SimpleContext,
): Plugin {
if (descriptor.value instanceof Plugin) {
if (descriptor.options) {
throw new Error(
"Passed options to an existing Plugin instance will not work.",
);
}
return descriptor.value;
}
return instantiatePlugin(loadDescriptor(descriptor, context), context);
}
const instantiatePlugin = makeWeakCache(
(
{ value, options, dirname, alias }: LoadedDescriptor,
cache: CacheConfigurator<SimpleContext>,
): Plugin => {
const pluginObj = validatePluginObject(value);
const plugin = Object.assign({}, pluginObj);
if (plugin.visitor) {
plugin.visitor = traverse.explode(Object.assign({}, plugin.visitor));
}
if (plugin.inherits) {
const inheritsDescriptor = {
name: undefined,
alias: `${alias}$inherits`,
value: plugin.inherits,
options,
dirname,
};
// If the inherited plugin changes, reinstantiate this plugin.
const inherits = cache.invalidate(data =>
loadPluginDescriptor(inheritsDescriptor, data),
);
plugin.pre = chain(inherits.pre, plugin.pre);
plugin.post = chain(inherits.post, plugin.post);
plugin.manipulateOptions = chain(
inherits.manipulateOptions,
plugin.manipulateOptions,
);
plugin.visitor = traverse.visitors.merge([
inherits.visitor || {},
plugin.visitor || {},
]);
}
return new Plugin(plugin, options, alias);
},
);
/**
* Generate a config object that will act as the root of a new nested config.
*/
const loadPresetDescriptor = (
descriptor: UnloadedDescriptor,
context: ConfigContext,
): ConfigChain | null => {
return buildPresetChain(
instantiatePreset(loadDescriptor(descriptor, context)),
context,
);
};
const instantiatePreset = makeWeakCache(
({ value, dirname, alias }: LoadedDescriptor): PresetInstance => {
return {
options: validate("preset", value),
alias,
dirname,
};
},
);
function chain(a, b) {
const fns = [a, b].filter(Boolean);
if (fns.length <= 1) return fns[0];
return function(...args) {
for (const fn of fns) {
fn.apply(this, args);
}
};
}

View File

@ -1,329 +1,26 @@
// @flow
import path from "path";
import * as context from "../index";
import Plugin from "./plugin";
import {
buildRootChain,
buildPresetChain,
type ConfigContext,
type ConfigChain,
type PresetInstance,
} from "./config-chain";
import type { UnloadedDescriptor } from "./config-descriptors";
import traverse from "@babel/traverse";
import { makeWeakCache, type CacheConfigurator } from "./caching";
import { getEnv } from "./helpers/environment";
import { validate, type ValidatedOptions } from "./validation/options";
import { validatePluginObject } from "./validation/plugins";
import makeAPI from "./helpers/config-api";
import loadFullConfig from "./full";
export type {
ResolvedConfig,
InputOptions,
PluginPasses,
Plugin,
} from "./full";
type LoadedDescriptor = {
value: {},
options: {},
dirname: string,
alias: string,
};
export { loadFullConfig as default };
export { loadPartialConfig } from "./partial";
export type { PartialConfig } from "./partial";
export type { InputOptions } from "./validation/options";
export function loadOptions(opts: {}): Object | null {
const config = loadFullConfig(opts);
export type ResolvedConfig = {
options: Object,
passes: PluginPasses,
};
export type { Plugin };
export type PluginPassList = Array<Plugin>;
export type PluginPasses = Array<PluginPassList>;
// Context not including filename since it is used in places that cannot
// process 'ignore'/'only' and other filename-based logic.
type SimpleContext = {
envName: string,
};
export default function loadConfig(inputOpts: mixed): ResolvedConfig | null {
if (
inputOpts != null &&
(typeof inputOpts !== "object" || Array.isArray(inputOpts))
) {
throw new Error("Babel options must be an object, null, or undefined");
}
const args = inputOpts ? validate("arguments", inputOpts) : {};
const { envName = getEnv(), cwd = "." } = args;
const absoluteCwd = path.resolve(cwd);
const context: ConfigContext = {
filename: args.filename ? path.resolve(cwd, args.filename) : null,
cwd: absoluteCwd,
envName,
};
const configChain = buildRootChain(args, context);
if (!configChain) return null;
const optionDefaults = {};
const options = {};
const passes = [[]];
try {
const ignored = (function recurseDescriptors(
config: {
plugins: Array<UnloadedDescriptor>,
presets: Array<UnloadedDescriptor>,
},
pass: Array<Plugin>,
) {
const plugins = config.plugins.map(descriptor =>
loadPluginDescriptor(descriptor, context),
);
const presets = config.presets.map(descriptor => {
return {
preset: loadPresetDescriptor(descriptor, context),
pass: descriptor.ownPass ? [] : pass,
};
});
// resolve presets
if (presets.length > 0) {
// The passes are created in the same order as the preset list, but are inserted before any
// existing additional passes.
passes.splice(
1,
0,
...presets.map(o => o.pass).filter(p => p !== pass),
);
for (const { preset, pass } of presets) {
if (!preset) return true;
const ignored = recurseDescriptors(
{
plugins: preset.plugins,
presets: preset.presets,
},
pass,
);
if (ignored) return true;
preset.options.forEach(opts => {
mergeOptions(optionDefaults, opts);
});
}
}
// resolve plugins
if (plugins.length > 0) {
pass.unshift(...plugins);
}
})(
{
plugins: configChain.plugins,
presets: configChain.presets,
},
passes[0],
);
if (ignored) return null;
configChain.options.forEach(opts => {
mergeOptions(options, opts);
});
} catch (e) {
// There are a few case where thrown errors will try to annotate themselves multiple times, so
// to keep things simple we just bail out if re-wrapping the message.
if (!/^\[BABEL\]/.test(e.message)) {
e.message = `[BABEL] ${args.filename || "unknown"}: ${e.message}`;
}
throw e;
}
const opts: Object = optionDefaults;
mergeOptions(opts, options);
// 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.
opts.babelrc = false;
opts.plugins = passes[0];
opts.presets = passes
.slice(1)
.filter(plugins => plugins.length > 0)
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;
opts.envName = envName;
opts.cwd = absoluteCwd;
return {
options: opts,
passes: passes,
};
return config ? config.options : null;
}
function mergeOptions(
target: ValidatedOptions,
source: ValidatedOptions,
): void {
for (const k of Object.keys(source)) {
if (k === "parserOpts" && source.parserOpts) {
const parserOpts = source.parserOpts;
const targetObj = (target.parserOpts = target.parserOpts || {});
mergeDefaultFields(targetObj, parserOpts);
} else if (k === "generatorOpts" && source.generatorOpts) {
const generatorOpts = source.generatorOpts;
const targetObj = (target.generatorOpts = target.generatorOpts || {});
mergeDefaultFields(targetObj, generatorOpts);
} else {
const val = source[k];
if (val !== undefined) target[k] = (val: any);
}
// For easier backward-compatibility, provide an API like the one we exposed in Babel 6.
export class OptionManager {
init(opts: {}) {
return loadOptions(opts);
}
}
function mergeDefaultFields<T: {}>(target: T, source: T) {
for (const k of Object.keys(source)) {
const val = source[k];
if (val !== undefined) target[k] = (val: any);
}
}
/**
* Load a generic plugin/preset from the given descriptor loaded from the config object.
*/
const loadDescriptor = makeWeakCache(
(
{ value, options, dirname, alias }: UnloadedDescriptor,
cache: CacheConfigurator<SimpleContext>,
): LoadedDescriptor => {
// Disabled presets should already have been filtered out
if (options === false) throw new Error("Assertion failure");
options = options || {};
let item = value;
if (typeof value === "function") {
const api = Object.assign(Object.create(context), makeAPI(cache));
try {
item = value(api, options, dirname);
} catch (e) {
if (alias) {
e.message += ` (While processing: ${JSON.stringify(alias)})`;
}
throw e;
}
}
if (!item || typeof item !== "object") {
throw new Error("Plugin/Preset did not return an object.");
}
if (typeof item.then === "function") {
throw new Error(
`You appear to be using an async plugin, ` +
`which your current version of Babel does not support.` +
`If you're using a published plugin, ` +
`you may need to upgrade your @babel/core version.`,
);
}
return { value: item, options, dirname, alias };
},
);
/**
* Instantiate a plugin for the given descriptor, returning the plugin/options pair.
*/
function loadPluginDescriptor(
descriptor: UnloadedDescriptor,
context: SimpleContext,
): Plugin {
if (descriptor.value instanceof Plugin) {
if (descriptor.options) {
throw new Error(
"Passed options to an existing Plugin instance will not work.",
);
}
return descriptor.value;
}
return instantiatePlugin(loadDescriptor(descriptor, context), context);
}
const instantiatePlugin = makeWeakCache(
(
{ value, options, dirname, alias }: LoadedDescriptor,
cache: CacheConfigurator<SimpleContext>,
): Plugin => {
const pluginObj = validatePluginObject(value);
const plugin = Object.assign({}, pluginObj);
if (plugin.visitor) {
plugin.visitor = traverse.explode(Object.assign({}, plugin.visitor));
}
if (plugin.inherits) {
const inheritsDescriptor = {
name: undefined,
alias: `${alias}$inherits`,
value: plugin.inherits,
options,
dirname,
};
// If the inherited plugin changes, reinstantiate this plugin.
const inherits = cache.invalidate(data =>
loadPluginDescriptor(inheritsDescriptor, data),
);
plugin.pre = chain(inherits.pre, plugin.pre);
plugin.post = chain(inherits.post, plugin.post);
plugin.manipulateOptions = chain(
inherits.manipulateOptions,
plugin.manipulateOptions,
);
plugin.visitor = traverse.visitors.merge([
inherits.visitor || {},
plugin.visitor || {},
]);
}
return new Plugin(plugin, options, alias);
},
);
/**
* Generate a config object that will act as the root of a new nested config.
*/
const loadPresetDescriptor = (
descriptor: UnloadedDescriptor,
context: ConfigContext,
): ConfigChain | null => {
return buildPresetChain(
instantiatePreset(loadDescriptor(descriptor, context)),
context,
);
};
const instantiatePreset = makeWeakCache(
({ value, dirname, alias }: LoadedDescriptor): PresetInstance => {
return {
options: validate("preset", value),
alias,
dirname,
};
},
);
function chain(a, b) {
const fns = [a, b].filter(Boolean);
if (fns.length <= 1) return fns[0];
return function(...args) {
for (const fn of fns) {
fn.apply(this, args);
}
};
}

View File

@ -0,0 +1,120 @@
// @flow
import path from "path";
import {
createDescriptor,
type UnloadedDescriptor,
} from "./config-descriptors";
export function createItemFromDescriptor(desc: UnloadedDescriptor): ConfigItem {
return new ConfigItem(desc);
}
export function createConfigItem(
value: string | {} | Function,
options?: {} | void,
{
dirname = ".",
name,
type,
}: {
dirname?: string,
name?: string,
type?: "preset" | "plugin",
} = {},
): ConfigItem {
const descriptor = createDescriptor(
[value, options, name],
path.resolve(dirname),
{
type,
alias: "programmatic item",
},
);
return createItemFromDescriptor(descriptor);
}
export function getItemDescriptor(item: mixed): UnloadedDescriptor | void {
if (item instanceof ConfigItem) {
return item._descriptor;
}
return undefined;
}
/**
* A public representation of a plugin/preset that will _eventually_ be load.
* Users can use this to interact with the results of a loaded Babel
* configuration.
*/
export class ConfigItem {
_descriptor: UnloadedDescriptor;
constructor(descriptor: UnloadedDescriptor) {
this._descriptor = descriptor;
// Make people less likely to stumble onto this if they are exploring
// programmatically.
enumerable(this, "_descriptor", false);
}
/**
* The resolved value of the item itself.
*/
get value(): {} | Function {
return this._descriptor.value;
}
/**
* The options, if any, that were passed to the item.
* Mutating this will lead to undefined behavior. If you need
*/
get options(): {} | void {
const options = this._descriptor.options;
if (options === false) {
throw new Error("Assertion failure - unexpected false options");
}
return options;
}
/**
* The directory that the options for this item are relative to.
*/
get dirname(): string {
return this._descriptor.dirname;
}
/**
* Get the name of the plugin, if the user gave it one.
*/
get name(): string | void {
return this._descriptor.name;
}
get file(): {
request: string,
resolved: string,
} | void {
const file = this._descriptor.file;
if (!file) return undefined;
return {
request: file.request,
resolved: file.resolved,
};
}
}
// Make these slightly easier for people to find if they are exploring the
// API programmatically.
enumerable(ConfigItem.prototype, "value", true);
enumerable(ConfigItem.prototype, "options", true);
enumerable(ConfigItem.prototype, "dirname", true);
enumerable(ConfigItem.prototype, "name", true);
enumerable(ConfigItem.prototype, "file", true);
function enumerable(obj: {}, prop: string, enumerable: boolean) {
Object.defineProperty(obj, prop, { enumerable });
}

View File

@ -0,0 +1,118 @@
// @flow
import path from "path";
import Plugin from "./plugin";
import { mergeOptions } from "./util";
import { createItemFromDescriptor } from "./item";
import { buildRootChain, type ConfigContext } from "./config-chain";
import { getEnv } from "./helpers/environment";
import { validate, type ValidatedOptions } from "./validation/options";
import type { ConfigFile, IgnoreFile } from "./files";
export default function loadPrivatePartialConfig(
inputOpts: mixed,
): {
options: ValidatedOptions,
context: ConfigContext,
ignore: IgnoreFile | void,
babelrc: ConfigFile | void,
} | null {
if (
inputOpts != null &&
(typeof inputOpts !== "object" || Array.isArray(inputOpts))
) {
throw new Error("Babel options must be an object, null, or undefined");
}
const args = inputOpts ? validate("arguments", inputOpts) : {};
const { envName = getEnv(), cwd = "." } = args;
const absoluteCwd = path.resolve(cwd);
const context: ConfigContext = {
filename: args.filename ? path.resolve(cwd, args.filename) : null,
cwd: absoluteCwd,
envName,
};
const configChain = buildRootChain(args, context);
if (!configChain) return null;
const options = {};
configChain.options.forEach(opts => {
mergeOptions(options, 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.babelrc = false;
options.envName = envName;
options.cwd = absoluteCwd;
options.passPerPreset = false;
options.plugins = configChain.plugins.map(descriptor =>
createItemFromDescriptor(descriptor),
);
options.presets = configChain.presets.map(descriptor =>
createItemFromDescriptor(descriptor),
);
return {
options,
context,
ignore: configChain.ignore,
babelrc: configChain.babelrc,
};
}
export function loadPartialConfig(inputOpts: mixed): PartialConfig | null {
const result = loadPrivatePartialConfig(inputOpts);
if (!result) return null;
const { options, babelrc, ignore } = result;
(options.plugins || []).forEach(item => {
if (item.value instanceof Plugin) {
throw new Error(
"Passing cached plugin instances is not supported in " +
"babel.loadPartialConfig()",
);
}
});
return new PartialConfig(
options,
babelrc ? babelrc.filepath : undefined,
ignore ? ignore.filepath : undefined,
);
}
export type { PartialConfig };
class PartialConfig {
_options: ValidatedOptions;
_babelrc: string | void;
_babelignore: string | void;
constructor(
options: ValidatedOptions,
babelrc: string | void,
ignore: string | void,
) {
this._options = options;
this._babelignore = ignore;
this._babelrc = babelrc;
}
get babelignore(): string | void {
return this._babelignore;
}
get babelrc(): string | void {
return this._babelrc;
}
get options(): ValidatedOptions {
return this._options;
}
}

View File

@ -0,0 +1,30 @@
// @flow
import type { ValidatedOptions } from "./validation/options";
export function mergeOptions(
target: ValidatedOptions,
source: ValidatedOptions,
): void {
for (const k of Object.keys(source)) {
if (k === "parserOpts" && source.parserOpts) {
const parserOpts = source.parserOpts;
const targetObj = (target.parserOpts = target.parserOpts || {});
mergeDefaultFields(targetObj, parserOpts);
} else if (k === "generatorOpts" && source.generatorOpts) {
const generatorOpts = source.generatorOpts;
const targetObj = (target.generatorOpts = target.generatorOpts || {});
mergeDefaultFields(targetObj, generatorOpts);
} else {
const val = source[k];
if (val !== undefined) target[k] = (val: any);
}
}
}
function mergeDefaultFields<T: {}>(target: T, source: T) {
for (const k of Object.keys(source)) {
const val = source[k];
if (val !== undefined) target[k] = (val: any);
}
}

View File

@ -1,5 +1,8 @@
// @flow
import { ConfigItem } from "../item";
import Plugin from "../plugin";
import removed from "./removed";
import {
assertString,
@ -214,10 +217,11 @@ export type IgnoreList = $ReadOnlyArray<IgnoreItem>;
export type PluginOptions = {} | void | false;
export type PluginTarget = string | {} | Function;
export type PluginItem =
| ConfigItem
| Plugin
| PluginTarget
| [PluginTarget, PluginOptions]
| [PluginTarget, PluginOptions, string];
| [PluginTarget, PluginOptions, string | void];
export type PluginList = $ReadOnlyArray<PluginItem>;
export type OverridesList = Array<ValidatedOptions>;

View File

@ -13,20 +13,9 @@ export * as types from "@babel/types";
export { default as traverse } from "@babel/traverse";
export { default as template } from "@babel/template";
import loadConfig from "./config";
export { loadPartialConfig, loadOptions, OptionManager } from "./config";
export function loadOptions(opts: {}): Object | null {
const config = loadConfig(opts);
return config ? config.options : null;
}
// For easier backward-compatibility, provide an API like the one we exposed in Babel 6.
export class OptionManager {
init(opts: {}) {
return loadOptions(opts);
}
}
export { createConfigItem } from "./config/item";
export function Plugin(alias: string) {
throw new Error(