// @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; export type PluginPasses = Array; // 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, presets: Array, }, pass: Array, ) { 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, ): 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, ): 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); } }; }