314 lines
7.9 KiB
JavaScript

// @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, type CallerMetadata } 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,
caller: CallerMetadata | void,
};
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.reduce((acc, descriptor) => {
if (descriptor.options !== false) {
acc.push(loadPluginDescriptor(descriptor, context));
}
return acc;
}, []);
const presets = config.presets.reduce((acc, descriptor) => {
if (descriptor.options !== false) {
acc.push({
preset: loadPresetDescriptor(descriptor, context),
pass: descriptor.ownPass ? [] : pass,
});
}
return acc;
}, []);
// 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 = {
...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 = {
...pluginObj,
};
if (plugin.visitor) {
plugin.visitor = traverse.explode({
...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);
}
};
}