Skip to content

Commit

Permalink
Merge pull request #6350 from loganfsmyth/plugin-preset-caching-updated
Browse files Browse the repository at this point in the history
Cache plugins and presets based on their identity
  • Loading branch information
loganfsmyth committed Oct 2, 2017
2 parents cc8109c + 35312dc commit 3d43a6e
Show file tree
Hide file tree
Showing 19 changed files with 433 additions and 87 deletions.
2 changes: 1 addition & 1 deletion packages/babel-core/src/config/index.js
Expand Up @@ -9,7 +9,7 @@ export type ResolvedConfig = {
};

export type { Plugin };
export type PluginPassList = Array<[Plugin, ?{}]>;
export type PluginPassList = Array<Plugin>;
export type PluginPasses = Array<PluginPassList>;

/**
Expand Down
155 changes: 81 additions & 74 deletions packages/babel-core/src/config/option-manager.js
Expand Up @@ -9,6 +9,8 @@ import buildConfigChain from "./build-config-chain";
import path from "path";
import traverse from "babel-traverse";
import clone from "lodash/clone";
import { makeWeakCache } from "./caching";
import { getEnv } from "./helpers/environment";

import {
loadPlugin,
Expand Down Expand Up @@ -77,7 +79,7 @@ const ALLOWED_PLUGIN_KEYS = new Set([

export default function manageOptions(opts: {}): {
options: Object,
passes: Array<Array<[Plugin, ?{}]>>,
passes: Array<Array<Plugin>>,
} | null {
return new OptionManager().init(opts);
}
Expand All @@ -89,7 +91,7 @@ class OptionManager {
}

options: Object;
passes: Array<Array<[Plugin, ?{}]>>;
passes: Array<Array<Plugin>>;

/**
* This is called when we want to merge the input `opts` into the
Expand All @@ -100,7 +102,7 @@ class OptionManager {
* - `dirname` is used to resolve plugins relative to it.
*/

mergeOptions(config: MergeOptions, pass?: Array<[Plugin, ?{}]>) {
mergeOptions(config: MergeOptions, pass?: Array<Plugin>) {
const result = loadConfig(config);

const plugins = result.plugins.map(descriptor =>
Expand Down Expand Up @@ -221,13 +223,11 @@ type BasicDescriptor = {
/**
* Load and validate the given config into a set of options, plugins, and presets.
*/
function loadConfig(
config,
): {
const loadConfig = makeWeakCache((config): {
options: {},
plugins: Array<BasicDescriptor>,
presets: Array<BasicDescriptor>,
} {
} => {
const options = normalizeOptions(config);

if (
Expand Down Expand Up @@ -277,24 +277,25 @@ function loadConfig(
});

return { options, plugins, presets };
}
});

/**
* Load a generic plugin/preset from the given descriptor loaded from the config object.
*/
function loadDescriptor(descriptor, skipOptions) {
const loadDescriptor = makeWeakCache((descriptor, cache) => {
if (typeof descriptor.value !== "function") {
return { value: descriptor.value, descriptor };
}

const { value, options } = descriptor;

const api = Object.assign(Object.create(context), {
cache,
env: () => cache.using(() => getEnv()),
});

let item;
try {
if (skipOptions) {
item = value(context);
} else {
item = value(context, options, { dirname: descriptor.dirname });
}
item = value(api, options, { dirname: descriptor.dirname });
} catch (e) {
if (descriptor.alias) {
e.message += ` (While processing: ${JSON.stringify(descriptor.alias)})`;
Expand All @@ -307,92 +308,98 @@ function loadDescriptor(descriptor, skipOptions) {
}

return { value: item, descriptor };
}
});

/**
* Instantiate a plugin for the given descriptor, returning the plugin/options pair.
*/
const PLUGIN_CACHE = new WeakMap();
function loadPluginDescriptor(descriptor) {
function loadPluginDescriptor(descriptor: BasicDescriptor) {
if (descriptor.value instanceof Plugin) {
return [descriptor.value, descriptor.options];
}
if (descriptor.options) {
throw new Error(
"Passed options to an existing Plugin instance will not work.",
);
}

let result = PLUGIN_CACHE.get(descriptor.value);
if (!result) {
result = instantiatePlugin(
loadDescriptor(descriptor, true /* skipOptions */),
);
PLUGIN_CACHE.set(descriptor.value, result);
return descriptor.value;
}

return [result, descriptor.options];
return instantiatePlugin(loadDescriptor(descriptor));
}

function instantiatePlugin({ value: pluginObj, descriptor }) {
Object.keys(pluginObj).forEach(key => {
if (!ALLOWED_PLUGIN_KEYS.has(key)) {
const instantiatePlugin = makeWeakCache(
({ value: pluginObj, descriptor }, cache) => {
Object.keys(pluginObj).forEach(key => {
if (!ALLOWED_PLUGIN_KEYS.has(key)) {
throw new Error(
`Plugin ${descriptor.alias} provided an invalid property of ${key}`,
);
}
});
if (
pluginObj.visitor &&
(pluginObj.visitor.enter || pluginObj.visitor.exit)
) {
throw new Error(
`Plugin ${descriptor.alias} provided an invalid property of ${key}`,
"Plugins aren't allowed to specify catch-all enter/exit handlers. " +
"Please target individual nodes.",
);
}
});
if (
pluginObj.visitor &&
(pluginObj.visitor.enter || pluginObj.visitor.exit)
) {
throw new Error(
"Plugins aren't allowed to specify catch-all enter/exit handlers. " +
"Please target individual nodes.",
);
}

const plugin = Object.assign({}, pluginObj, {
visitor: clone(pluginObj.visitor || {}),
});

traverse.explode(plugin.visitor);

let inheritsDescriptor;
let inherits;
if (plugin.inherits) {
inheritsDescriptor = {
alias: `${descriptor.loc}$inherits`,
loc: descriptor.loc,
value: plugin.inherits,
options: descriptor.options,
dirname: descriptor.dirname,
};
const plugin = Object.assign({}, pluginObj, {
visitor: clone(pluginObj.visitor || {}),
});

inherits = loadPluginDescriptor(inheritsDescriptor)[0];
traverse.explode(plugin.visitor);

let inheritsDescriptor;
let inherits;
if (plugin.inherits) {
inheritsDescriptor = {
alias: `${descriptor.loc}$inherits`,
loc: descriptor.loc,
value: plugin.inherits,
options: descriptor.options,
dirname: descriptor.dirname,
};

// If the inherited plugin changes, reinstantiate this plugin.
inherits = cache.invalidate(() =>
loadPluginDescriptor(inheritsDescriptor),
);

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,
]);
}
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, descriptor.alias);
}
return new Plugin(plugin, descriptor.options, descriptor.alias);
},
);

/**
* Generate a config object that will act as the root of a new nested config.
*/
function loadPresetDescriptor(descriptor): MergeOptions {
const loadPresetDescriptor = (descriptor: BasicDescriptor): MergeOptions => {
return instantiatePreset(loadDescriptor(descriptor));
};

const instantiatePreset = makeWeakCache(({ value, descriptor }) => {
return {
type: "preset",
options: loadDescriptor(descriptor).value,
options: value,
alias: descriptor.alias,
loc: descriptor.loc,
dirname: descriptor.dirname,
};
}
});

/**
* Validate and return the options object for the config.
Expand Down
5 changes: 4 additions & 1 deletion packages/babel-core/src/config/plugin.js
Expand Up @@ -7,7 +7,9 @@ export default class Plugin {
pre: ?Function;
visitor: ?{};

constructor(plugin: {}, key?: string) {
options: {} | void;

constructor(plugin: {}, options: ?{}, key?: string) {
if (plugin.name != null && typeof plugin.name !== "string") {
throw new Error("Plugin .name must be a string, null, or undefined");
}
Expand Down Expand Up @@ -35,5 +37,6 @@ export default class Plugin {
this.post = plugin.post;
this.pre = plugin.pre;
this.visitor = plugin.visitor;
this.options = options || undefined;
}
}
6 changes: 3 additions & 3 deletions packages/babel-core/src/transformation/block-hoist-plugin.js
Expand Up @@ -6,7 +6,7 @@ import loadConfig, { type Plugin } from "../config";

let LOADED_PLUGIN: Plugin | void;

export default function loadBlockHoistPlugin(): [Plugin, void] {
export default function loadBlockHoistPlugin(): Plugin {
if (!LOADED_PLUGIN) {
// Lazy-init the internal plugin to remove the init-time circular
// dependency between plugins being passed babel-core's export object,
Expand All @@ -15,11 +15,11 @@ export default function loadBlockHoistPlugin(): [Plugin, void] {
babelrc: false,
plugins: [blockHoistPlugin],
});
LOADED_PLUGIN = config ? config.passes[0][0][0] : undefined;
LOADED_PLUGIN = config ? config.passes[0][0] : undefined;
if (!LOADED_PLUGIN) throw new Error("Assertion failure");
}

return [LOADED_PLUGIN, undefined];
return LOADED_PLUGIN;
}

const blockHoistPlugin = {
Expand Down
6 changes: 2 additions & 4 deletions packages/babel-core/src/transformation/index.js
Expand Up @@ -49,10 +49,8 @@ function transformFile(file: File, pluginPasses: PluginPasses): void {
const passes = [];
const visitors = [];

for (const [plugin, pluginOpts] of pluginPairs.concat([
loadBlockHoistPlugin(),
])) {
const pass = new PluginPass(file, plugin.key, pluginOpts);
for (const plugin of pluginPairs.concat([loadBlockHoistPlugin()])) {
const pass = new PluginPass(file, plugin.key, plugin.options);

passPairs.push([plugin, pass]);
passes.push(pass);
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-core/src/transformation/normalize-opts.js
Expand Up @@ -15,7 +15,7 @@ export default function normalizeOptions(config: ResolvedConfig): {} {
});

for (const pluginPairs of config.passes) {
for (const [plugin] of pluginPairs) {
for (const plugin of pluginPairs) {
if (plugin.manipulateOptions) {
plugin.manipulateOptions(options, options.parserOpts);
}
Expand Down
5 changes: 2 additions & 3 deletions packages/babel-core/test/api.js
Expand Up @@ -154,9 +154,8 @@ describe("api", function() {
plugins: [__dirname + "/../../babel-plugin-syntax-jsx"],
}).then(function(result) {
assert.ok(
result.options.plugins[0][0].manipulateOptions
.toString()
.indexOf("jsx") >= 0,
result.options.plugins[0].manipulateOptions.toString().indexOf("jsx") >=
0,
);
});
});
Expand Down

0 comments on commit 3d43a6e

Please sign in to comment.