Expose the partial Babel config for people to load and mutate.
This commit is contained in:
parent
53825f8152
commit
fef5c7e523
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
300
packages/babel-core/src/config/full.js
Normal file
300
packages/babel-core/src/config/full.js
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
120
packages/babel-core/src/config/item.js
Normal file
120
packages/babel-core/src/config/item.js
Normal 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 });
|
||||
}
|
||||
118
packages/babel-core/src/config/partial.js
Normal file
118
packages/babel-core/src/config/partial.js
Normal 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;
|
||||
}
|
||||
}
|
||||
30
packages/babel-core/src/config/util.js
Normal file
30
packages/babel-core/src/config/util.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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>;
|
||||
|
||||
@ -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(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user