From 9cb16711dda17d1e53c975cd117d70b2c381e422 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Wed, 29 Apr 2015 00:20:49 +0100 Subject: [PATCH] abstract plugin initialisation to a plugin manager --- src/babel/messages.js | 10 +- src/babel/transformation/file/index.js | 59 +---------- .../transformation/file/plugin-manager.js | 99 +++++++++++++++++++ src/babel/transformation/transformer.js | 2 +- 4 files changed, 113 insertions(+), 57 deletions(-) create mode 100644 src/babel/transformation/file/plugin-manager.js diff --git a/src/babel/messages.js b/src/babel/messages.js index 7926eda3c3..ec426958fa 100644 --- a/src/babel/messages.js +++ b/src/babel/messages.js @@ -26,7 +26,15 @@ export const MESSAGES = { traverseNeedsParent: "Must pass a scope and parentPath unless traversing a Program/File got a $1 node", traverseVerifyRootFunction: "You passed `traverse()` a function when it expected a visitor object, are you sure you didn't mean `{ enter: Function }`?", traverseVerifyVisitorFunction: "Hey! You passed \`traverse()\` a visitor object with the key $1 that's a straight up `Function` instead of `{ enter: Function }`. You need to normalise it with `traverse.explode(visitor)`.", - traverseVerifyVisitorProperty: "You passed `traverse()` a visitor object with the property $1 that has the invalid property $2" + traverseVerifyVisitorProperty: "You passed `traverse()` a visitor object with the property $1 that has the invalid property $2", + + pluginIllegalKind: "Illegal kind $1 for plugin $2", + pluginIllegalPosition: "Illegal position $1 for plugin $2", + pluginKeyCollision: "The plugin $1 collides with another of the same name", + pluginNotTransformer: "The plugin $1 didn't export a Transformer instance", + pluginUnknown: "Unknown plugin $1", + + transformerNotFile: "Transformer $1 is resolving to a different Babel version to what is doing the actual transformation..." }; export function get(key: String, ...args) { diff --git a/src/babel/transformation/file/index.js b/src/babel/transformation/file/index.js index 159dd396fe..1bcf55b3a8 100644 --- a/src/babel/transformation/file/index.js +++ b/src/babel/transformation/file/index.js @@ -1,5 +1,6 @@ import convertSourceMap from "convert-source-map"; import * as optionParsers from "./option-parsers"; +import PluginManager from "./plugin-manager"; import shebangRegex from "shebang-regex"; import TraversalPath from "../../traversal/path"; import isFunction from "lodash/lang/isFunction"; @@ -18,6 +19,7 @@ import Scope from "../../traversal/scope"; import slash from "slash"; import clone from "lodash/lang/clone"; import * as util from "../../util"; +import * as api from "../../api/node"; import path from "path"; import each from "lodash/collection/each"; import * as t from "../../types"; @@ -217,8 +219,9 @@ export default class File { // init plugins! var beforePlugins = []; var afterPlugins = []; + var pluginManager = new PluginManager(this.transformers, beforePlugins, afterPlugins); for (var i = 0; i < file.opts.plugins.length; i++) { - this.addPlugin(file.opts.plugins[i], beforePlugins, afterPlugins); + pluginManager.add(file.opts.plugins[i]); } stack = beforePlugins.concat(stack, afterPlugins); @@ -241,60 +244,6 @@ export default class File { return new ModuleFormatter(this); } - addPlugin(name, before, after) { - var position = "before"; - var plugin; - - if (name) { - if (typeof name === "object" && name.transformer) { - plugin = name.transformer; - position = name.position || position; - } else if (typeof name === "string") { - // this is a plugin in the form of "foobar" or "foobar:after" - // where the optional colon is the delimiter for plugin position in the transformer stack - - [name, position = "before"] = name.split(":"); - - var loc = util.resolveRelative(name) || util.resolveRelative(`babel-plugin-${name}`); - if (loc) { - plugin = require(loc) - } else { - throw new ReferenceError(`Unknown plugin ${JSON.stringify(name)}`); - } - } else { - // not a string so we'll just assume that it's a direct Transformer instance, if not then - // the checks later on will complain - plugin = name; - } - } else { - throw new TypeError(`Ilegal kind ${typeof name} for plugin name ${JSON.stringify(name)}`); - } - - // validate position - if (position !== "before" && position !== "after") { - throw new TypeError(`Plugin ${JSON.stringify(name)} has an illegal position of ${JSON.stringify(position)}`); - } - - // validate transformer key - var key = plugin.key; - if (this.transformers[key]) { - throw new ReferenceError(`The key for plugin ${JSON.stringify(name)} of ${key} collides with an existing plugin`); - } - - // validate Transformer instance - if (!plugin.buildPass || plugin.constructor.name !== "Transformer") { - throw new TypeError(`Plugin ${JSON.stringify(name)} didn't export a default Transformer instance`); - } - - // build! - var pass = this.transformers[key] = plugin.buildPass(this); - if (pass.canTransform()) { - var stack = before; - if (position === "after") stack = after; - stack.push(pass); - } - } - parseInputSourceMap(code: string) { var opts = this.opts; diff --git a/src/babel/transformation/file/plugin-manager.js b/src/babel/transformation/file/plugin-manager.js new file mode 100644 index 0000000000..57f4bfd853 --- /dev/null +++ b/src/babel/transformation/file/plugin-manager.js @@ -0,0 +1,99 @@ +import * as messages from "../../messages"; + +export default class PluginManager { + static memoisedPlugins = []; + + static memoisePluginContainer(fn) { + for (var i = 0; i < PluginManager.memoisedPlugins.length; i++) { + var plugin = PluginManager.memoisedPlugins[i]; + if (plugin.container === fn) return plugin.transformer; + } + + var transformer = fn(node); + PluginManager.memoisedPlugins.push({ + container: fn, + transformer: transformer + }); + return transformer; + } + + constructor(transformers, before, after) { + this.transformers = transformers; + this.before = before; + this.after = after; + } + + subnormaliseString(name) { + // this is a plugin in the form of "foobar" or "foobar:after" + // where the optional colon is the delimiter for plugin position in the transformer stack + + var [name, position] = name.split(":"); + + var loc = util.resolveRelative(name) || util.resolveRelative(`babel-plugin-${name}`); + if (loc) { + return { + position: position, + plugin: require(loc) + }; + } else { + throw new ReferenceError(messages.get("pluginUnknown", name)); + } + } + + validate(plugin) { + // validate transformer key + var key = plugin.key; + if (this.transformers[key]) { + throw new ReferenceError(messages.get("pluginKeyCollision", key)); + } + + // validate Transformer instance + if (!plugin.buildPass || plugin.constructor.name !== "Transformer") { + throw new TypeError(messages.get("pluginNotTransformer", name)); + } + } + + add(name) { + var position; + var plugin; + + if (name) { + if (typeof name === "object" && name.transformer) { + ({ plugin: name, position } = name); + } else if (typeof name !== "string") { + // not a string so we'll just assume that it's a direct Transformer instance, if not then + // the checks later on will complain + plugin = name; + } + + if (typeof name === "string") { + ({ plugin, position } = this.subnormaliseString(name)); + } + } else { + throw new TypeError(messages.get("pluginIllegalKind", typeof name, name)); + } + + // default position + position = position || "before"; + + // validate position + if (position !== "before" && position !== "after") { + throw new TypeError(messages.get("pluginIllegalPosition", position, name)); + } + + // allow plugin containers to be specified so they don't have to manually require + if (typeof plugin === "function") { + plugin = PluginManager.memoisePluginContainer(plugin); + } + + // + this.validate(plugin); + + // build! + var pass = this.transformers[key] = plugin.buildPass(this); + if (pass.canTransform()) { + var stack = position === "before" ? this.before : this.after; + stack.push(pass); + } + } +} diff --git a/src/babel/transformation/transformer.js b/src/babel/transformation/transformer.js index f895b285f8..4d5825a7ee 100644 --- a/src/babel/transformation/transformer.js +++ b/src/babel/transformation/transformer.js @@ -87,7 +87,7 @@ export default class Transformer { buildPass(file: File): TransformerPass { // validate Transformer instance if (!(file instanceof File)) { - throw new TypeError(`Transformer ${this.key} is resolving to a different Babel version to what is doing the actual transformation...`); + throw new TypeError(messages.get("transformerNotFile", this.key)); } return new TransformerPass(file, this);