diff --git a/packages/ember-template-compiler/lib/system/compile-options.js b/packages/ember-template-compiler/lib/system/compile-options.js index bb1deaaa862..8652309da12 100644 --- a/packages/ember-template-compiler/lib/system/compile-options.js +++ b/packages/ember-template-compiler/lib/system/compile-options.js @@ -16,48 +16,68 @@ export default function compileOptions(_options) { options.plugins = { ast: [...USER_PLUGINS, ...PLUGINS] }; } else { let potententialPugins = [...USER_PLUGINS, ...PLUGINS]; + let providedPlugins = options.plugins.ast.map(plugin => wrapLegacyPluginIfNeeded(plugin)); let pluginsToAdd = potententialPugins.filter((plugin) => { return options.plugins.ast.indexOf(plugin) === -1; }); - options.plugins.ast = options.plugins.ast.slice().concat(pluginsToAdd); + options.plugins.ast = providedPlugins.concat(pluginsToAdd); } return options; } -export function registerPlugin(type, _plugin) { - if (type !== 'ast') { - throw new Error(`Attempting to register ${_plugin} as "${type}" which is not a valid Glimmer plugin type.`); - } - - let plugin; +function wrapLegacyPluginIfNeeded(_plugin) { + let plugin = _plugin; if (_plugin.prototype && _plugin.prototype.transform) { plugin = (env) => { + let pluginInstantiated = false; + return { name: _plugin.constructor && _plugin.constructor.name, visitors: { Program(node) { - let plugin = new _plugin(env); + if (!pluginInstantiated) { + + pluginInstantiated = true; + let plugin = new _plugin(env); - plugin.syntax = env.syntax; + plugin.syntax = env.syntax; - return plugin.transform(node); + return plugin.transform(node); + } } } - }; + }; }; - } else { - plugin = _plugin; + + plugin.__raw = _plugin; } + return plugin; +} + +export function registerPlugin(type, _plugin) { + if (type !== 'ast') { + throw new Error(`Attempting to register ${_plugin} as "${type}" which is not a valid Glimmer plugin type.`); + } + + for (let i = 0; i < USER_PLUGINS.length; i++) { + let PLUGIN = USER_PLUGINS[i]; + if (PLUGIN === _plugin || PLUGIN.__raw === _plugin) { + return; + } + } + + let plugin = wrapLegacyPluginIfNeeded(_plugin); + USER_PLUGINS = [plugin, ...USER_PLUGINS]; } -export function removePlugin(type, PluginClass) { +export function unregisterPlugin(type, PluginClass) { if (type !== 'ast') { throw new Error(`Attempting to unregister ${PluginClass} as "${type}" which is not a valid Glimmer plugin type.`); } - USER_PLUGINS = USER_PLUGINS.filter((plugin) => plugin !== PluginClass); + USER_PLUGINS = USER_PLUGINS.filter((plugin) => plugin !== PluginClass && plugin.__raw !== PluginClass); } diff --git a/packages/ember-template-compiler/tests/system/compile_options_test.js b/packages/ember-template-compiler/tests/system/compile_options_test.js index ec6b86ff4e9..aaa423b010c 100644 --- a/packages/ember-template-compiler/tests/system/compile_options_test.js +++ b/packages/ember-template-compiler/tests/system/compile_options_test.js @@ -1,6 +1,5 @@ -import { compileOptions } from '../../index'; -import { defaultPlugins } from '../../index'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { compile, compileOptions, defaultPlugins, registerPlugin, unregisterPlugin } from '../../index'; +import { moduleFor, AbstractTestCase, RenderingTestCase } from 'internal-test-helpers'; moduleFor('ember-template-compiler: default compile options', class extends AbstractTestCase { ['@test default options are a new copy'](assert) { @@ -18,3 +17,81 @@ moduleFor('ember-template-compiler: default compile options', class extends Abst } } }); + +let customTransformCounter = 0; +class CustomTransform { + constructor(options) { + customTransformCounter++; + this.options = options; + this.syntax = null; + } + + transform(ast) { + let walker = new this.syntax.Walker(); + + walker.visit(ast, node => { + if (node.type !== 'ElementNode') { + return; + } + + for (var i = 0; i < node.attributes.length; i++) { + let attribute = node.attributes[i]; + + if (attribute.name === 'data-test') { + node.attributes.splice(i, 1); + } + } + }); + + return ast; + } +} + +class CustomPluginsTests extends RenderingTestCase { + afterEach() { + customTransformCounter = 0; + return super.afterEach(); + } + + ['@test custom plugins can be used']() { + this.render('
'); + this.assertElement(this.firstChild, { + tagName: 'div', + attrs: { class: 'hahaha', 'data-blah': 'derp' }, + content: '' + }); + } + + ['@test wrapped plugins are only invoked once per template'](assert) { + this.render('
{{#if falsey}}nope{{/if}}
'); + assert.equal(customTransformCounter, 1, 'transform should only be instantiated once'); + } +} + +moduleFor('ember-template-compiler: registerPlugin with a custom plugins', class extends CustomPluginsTests { + beforeEach() { + registerPlugin('ast', CustomTransform); + } + + afterEach() { + unregisterPlugin('ast', CustomTransform); + return super.afterEach(); + } + + ['@test custom registered plugins are deduplicated'](assert) { + registerPlugin('ast', CustomTransform); + this.registerTemplate('application', '
'); + assert.equal(customTransformCounter, 1, 'transform should only be instantiated once'); + } +}); + +moduleFor('ember-template-compiler: custom plugins passed to compile', class extends RenderingTestCase { + // override so that we can provide custom AST plugins to compile + compile(templateString) { + return compile(templateString, { + plugins: { + ast: [CustomTransform] + } + }); + } +});