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

@@ -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(