diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6c1ddc..0c377ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## v4.5.0 (2021-03-03) + +#### :rocket: Enhancement +* [#673](https://github.com/ember-cli/ember-cli-htmlbars/pull/673) Backport template compiler improvements from 5.x ([@rwjblue](https://github.com/rwjblue)) +* [#661](https://github.com/ember-cli/ember-cli-htmlbars/pull/661) Remove usage of registerPlugin / unregisterPlugin ([@rwjblue](https://github.com/rwjblue)) +* [#660](https://github.com/ember-cli/ember-cli-htmlbars/pull/660) Replace `purgeModule` cache busting with `vm` based sandboxing ([@rwjblue](https://github.com/rwjblue)) + +#### Committers: 1 +- Robert Jackson ([@rwjblue](https://github.com/rwjblue)) + +## v4.4.1 (2021-02-05) + +#### :rocket: Enhancement +* [#598](https://github.com/ember-cli/ember-cli-htmlbars/pull/657) Make `cacheKey` calculation lazy ([@krisselden](https://github.com/krisselden)) + +#### Committers: 1 +- Kris Selden ([@krisselden](https://github.com/krisselden)) + +## v4.4.0 (2020-08-11) + +#### :rocket: Enhancement +* [#598](https://github.com/ember-cli/ember-cli-htmlbars/pull/598) Pass `isProduction` to Ember template compiler. ([@rwjblue](https://github.com/rwjblue)) + +#### Committers: 1 +- Robert Jackson ([@rwjblue](https://github.com/rwjblue)) + ## v4.3.1 (2020-04-09) #### :bug: Bug Fix diff --git a/lib/ember-addon-main.js b/lib/ember-addon-main.js index 4ba9c714..24a7ba44 100644 --- a/lib/ember-addon-main.js +++ b/lib/ember-addon-main.js @@ -60,6 +60,8 @@ module.exports = { // ensure that broccoli-ember-hbs-template-compiler is not processing hbs files registry.remove('template', 'broccoli-ember-hbs-template-compiler'); + let isProduction = process.env.EMBER_ENV === 'production'; + // when this.parent === this.project, `this.parent.name` is a function 😭 let parentName = typeof this.parent.name === 'function' ? this.parent.name() : this.parent.name; @@ -73,7 +75,7 @@ module.exports = { ); let shouldColocateTemplates = this._addon._shouldColocateTemplates(); - let htmlbarsOptions = this._addon.htmlbarsOptions(); + let htmlbarsOptions = Object.assign({ isProduction }, this._addon.htmlbarsOptions()); let inputTree = debugTree(tree, '01-input'); @@ -87,10 +89,15 @@ module.exports = { return debugTree(new TemplateCompiler(inputTree, htmlbarsOptions), '03-output'); }, - precompile(string, options) { + precompile(string, _options) { + let options = _options; let htmlbarsOptions = this._addon.htmlbarsOptions(); let templateCompiler = htmlbarsOptions.templateCompiler; + if (isProduction) { + options = Object.assign({ isProduction }, _options); + } + return utils.template(templateCompiler, string, options); }, }); @@ -145,6 +152,8 @@ module.exports = { addonOptions.babel.plugins = addonOptions.babel.plugins || []; let babelPlugins = addonOptions.babel.plugins; + let isProduction = process.env.EMBER_ENV === 'production'; + // add the babel-plugin-htmlbars-inline-precompile to the list of plugins // used by `ember-cli-babel` addon if (!utils.isInlinePrecompileBabelPluginRegistered(babelPlugins)) { @@ -156,7 +165,8 @@ module.exports = { let htmlbarsInlinePrecompilePlugin = utils.buildParalleizedBabelPlugin( pluginInfo, - templateCompilerPath + templateCompilerPath, + isProduction ); babelPlugins.push(htmlbarsInlinePrecompilePlugin); @@ -165,6 +175,7 @@ module.exports = { this.logger.debug('Prevented by these plugins: ' + pluginInfo.unparallelizableWrappers); let htmlBarsPlugin = utils.setup(pluginInfo, { + isProduction, projectConfig: this.projectConfig(), templateCompilerPath, }); diff --git a/lib/template-compiler-plugin.js b/lib/template-compiler-plugin.js index 2b395db7..3195b768 100644 --- a/lib/template-compiler-plugin.js +++ b/lib/template-compiler-plugin.js @@ -1,6 +1,5 @@ 'use strict'; -const fs = require('fs'); const path = require('path'); const utils = require('./utils'); const Filter = require('broccoli-persistent-filter'); @@ -40,9 +39,8 @@ class TemplateCompiler extends Filter { // TODO: do we need this? this.precompile = this.options.templateCompiler.precompile; - let { templateCompiler, plugins, EmberENV } = options; + let { templateCompiler, EmberENV } = options; - utils.registerPlugins(templateCompiler, plugins); utils.initializeEmberENV(templateCompiler, EmberENV); } @@ -50,35 +48,42 @@ class TemplateCompiler extends Filter { return __dirname; } - unregisterPlugins() { - let { templateCompiler, plugins } = this.options; - - utils.unregisterPlugins(templateCompiler, plugins); - } - - registeredASTPlugins() { - // This is a super obtuse way to get access to the plugins we've registered - // it also returns other plugins that are registered by ember itself. - let options = this.options.templateCompiler.compileOptions(); - return (options.plugins && options.plugins.ast) || []; - } - processString(string, relativePath) { let srcDir = this.inputPaths[0]; let srcName = path.join(srcDir, relativePath); try { + // we have to reverse these for reasons that are a bit bonkers. the initial + // version of this system used `registeredPlugin` from + // `ember-template-compiler.js` to set up these plugins (because Ember ~ 1.13 + // only had `registerPlugin`, and there was no way to pass plugins directly + // to the call to `compile`/`precompile`). calling `registerPlugin` + // unfortunately **inverted** the order of plugins (it essentially did + // `PLUGINS = [plugin, ...PLUGINS]`). + // + // sooooooo...... we are forced to maintain that **absolutely bonkers** ordering + let astPlugins = this.options.plugins ? [].concat(this.options.plugins.ast).reverse() : []; + let result = 'export default ' + utils.template(this.options.templateCompiler, stripBom(string), { contents: string, + isProduction: this.options.isProduction, moduleName: relativePath, parseOptions: { srcName: srcName, }, + + // intentionally not using `plugins: this.options.plugins` here + // because if we do, Ember will mutate the shared plugins object (adding + // all of the built in AST transforms into plugins.ast, which breaks + // persistent caching) + plugins: { + ast: astPlugins, + }, }) + ';'; if (this.options.dependencyInvalidation) { - let plugins = pluginsWithDependencies(this.registeredASTPlugins()); + let plugins = pluginsWithDependencies(this.options.plugins.ast); let dependencies = []; for (let i = 0; i < plugins.length; i++) { let pluginDeps = plugins[i].getDependencies(relativePath); @@ -104,20 +109,16 @@ class TemplateCompiler extends Filter { return strippedOptions; } - _templateCompilerContents() { - if (this.options.templateCompilerPath) { - return fs.readFileSync(this.options.templateCompilerPath, { encoding: 'utf8' }); - } else { - return ''; - } - } - optionsHash() { if (!this._optionsHash) { + let templateCompilerCacheKey = utils.getTemplateCompilerCacheKey( + this.options.templateCompilerPath + ); + this._optionsHash = crypto .createHash('md5') .update(stringify(this._buildOptionsForHash()), 'utf8') - .update(stringify(this._templateCompilerContents()), 'utf8') + .update(templateCompilerCacheKey, 'utf8') .digest('hex'); } diff --git a/lib/utils.js b/lib/utils.js index 4f9e5df6..9aec3b15 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,11 +1,15 @@ 'use strict'; +const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const hashForDep = require('hash-for-dep'); const debugGenerator = require('heimdalljs-logger'); const logger = debugGenerator('ember-cli-htmlbars:utils'); const addDependencyTracker = require('./addDependencyTracker'); +const vm = require('vm'); + +const TemplateCompilerCache = new Map(); const INLINE_PRECOMPILE_MODULES = Object.freeze({ 'ember-cli-htmlbars': 'hbs', @@ -46,42 +50,43 @@ function isColocatedBabelPluginRegistered(plugins) { ); } -function buildParalleizedBabelPlugin(pluginInfo, templateCompilerPath) { +function buildParalleizedBabelPlugin(pluginInfo, templateCompilerPath, isProduction) { let parallelBabelInfo = { requireFile: require.resolve('./require-from-worker'), buildUsing: 'build', params: { templateCompilerPath, + isProduction, parallelConfigs: pluginInfo.parallelConfigs, modules: INLINE_PRECOMPILE_MODULES, }, }; // parallelBabelInfo will not be used in the cache unless it is explicitly included - let cacheKey = makeCacheKey(templateCompilerPath, pluginInfo, JSON.stringify(parallelBabelInfo)); - + let cacheKey; return { _parallelBabel: parallelBabelInfo, baseDir: () => __dirname, - cacheKey: () => cacheKey, + cacheKey: () => { + if (cacheKey === undefined) { + cacheKey = makeCacheKey( + templateCompilerPath, + pluginInfo, + JSON.stringify(parallelBabelInfo) + ); + } + return cacheKey; + }, }; } function buildOptions(projectConfig, templateCompilerPath, pluginInfo) { let EmberENV = projectConfig.EmberENV || {}; - purgeModule(templateCompilerPath); - - // do a full clone of the EmberENV (it is guaranteed to be structured - // cloneable) to prevent ember-template-compiler.js from mutating - // the shared global config - let clonedEmberENV = JSON.parse(JSON.stringify(EmberENV)); - global.EmberENV = clonedEmberENV; // Needed for eval time feature flag checks - let htmlbarsOptions = { isHTMLBars: true, EmberENV: EmberENV, - templateCompiler: require(templateCompilerPath), + templateCompiler: getTemplateCompiler(templateCompilerPath, EmberENV), templateCompilerPath: templateCompilerPath, plugins: { @@ -93,57 +98,64 @@ function buildOptions(projectConfig, templateCompilerPath, pluginInfo) { pluginCacheKey: pluginInfo.cacheKeys, }; - purgeModule(templateCompilerPath); - - delete global.Ember; - delete global.EmberENV; - return htmlbarsOptions; } -function purgeModule(templateCompilerPath) { - // ensure we get a fresh templateCompilerModuleInstance per ember-addon - // instance NOTE: this is a quick hack, and will only work as long as - // templateCompilerPath is a single file bundle - // - // (╯°□°)╯︵ ɹǝqɯǝ - // - // we will also fix this in ember for future releases - - // Module will be cached in .parent.children as well. So deleting from require.cache alone is not sufficient. - let mod = require.cache[templateCompilerPath]; - if (mod && mod.parent) { - let index = mod.parent.children.indexOf(mod); - if (index >= 0) { - mod.parent.children.splice(index, 1); - } else { - throw new TypeError( - `ember-cli-htmlbars attempted to purge '${templateCompilerPath}' but something went wrong.` - ); - } - } +function getTemplateCompiler(templateCompilerPath, EmberENV = {}) { + let templateCompilerFullPath = require.resolve(templateCompilerPath); + let cacheData = TemplateCompilerCache.get(templateCompilerFullPath); - delete require.cache[templateCompilerPath]; -} + if (cacheData === undefined) { + let templateCompilerContents = fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' }); + let templateCompilerCacheKey = crypto + .createHash('md5') + .update(templateCompilerContents) + .digest('hex'); -function registerPlugins(templateCompiler, plugins) { - if (plugins) { - for (let type in plugins) { - for (let i = 0, l = plugins[type].length; i < l; i++) { - templateCompiler.registerPlugin(type, plugins[type][i]); - } - } + cacheData = { + script: new vm.Script(templateCompilerContents, { + filename: templateCompilerPath, + }), + + templateCompilerCacheKey, + }; + + TemplateCompilerCache.set(templateCompilerFullPath, cacheData); } -} -function unregisterPlugins(templateCompiler, plugins) { - if (plugins) { - for (let type in plugins) { - for (let i = 0, l = plugins[type].length; i < l; i++) { - templateCompiler.unregisterPlugin(type, plugins[type][i]); - } - } + let { script } = cacheData; + + // do a full clone of the EmberENV (it is guaranteed to be structured + // cloneable) to prevent ember-template-compiler.js from mutating + // the shared global config + let clonedEmberENV = JSON.parse(JSON.stringify(EmberENV)); + + let sandbox = { + EmberENV: clonedEmberENV, + + // Older versions of ember-template-compiler (up until ember-source@3.1.0) + // eagerly access `setTimeout` without checking via `typeof` first + setTimeout, + clearTimeout, + + // fake the module into thinking we are running inside a Node context + module: { require, exports: {} }, + require, + }; + + // if we are running on a Node version _without_ a globalThis + // we must provide a `global` + // + // this is due to https://git.io/Jtb7s (Ember 3.27+) + if (typeof globalThis === 'undefined') { + sandbox.global = sandbox; } + + let context = vm.createContext(sandbox); + + script.runInContext(context); + + return context.module.exports; } function initializeEmberENV(templateCompiler, EmberENV) { @@ -176,6 +188,7 @@ function initializeEmberENV(templateCompiler, EmberENV) { function template(templateCompiler, string, options) { let precompiled = templateCompiler.precompile(string, options); + return 'Ember.HTMLBars.template(' + precompiled + ')'; } @@ -186,29 +199,57 @@ function setup(pluginInfo, options) { let htmlbarsOptions = buildOptions(projectConfig, templateCompilerPath, pluginInfo); let { templateCompiler } = htmlbarsOptions; - let cacheKey = makeCacheKey(templateCompilerPath, pluginInfo); + let templatePrecompile = templateCompiler.precompile; - registerPlugins(templateCompiler, { - ast: pluginInfo.plugins, - }); + let precompile = (template, options) => { + let plugins = pluginInfo.plugins || []; + // concat so we ensure we don't mutate the original plugins + // reverse to ensure that original AST plugin ordering is preserved + let astPlugins = [].concat(plugins).reverse(); + + options = options || {}; + options.plugins = { + ast: astPlugins, + }; + + return templatePrecompile(template, options); + }; - let { precompile } = templateCompiler; precompile.baseDir = () => path.resolve(__dirname, '..'); - precompile.cacheKey = () => cacheKey; + + let cacheKey; + precompile.cacheKey = () => { + if (cacheKey === undefined) { + cacheKey = makeCacheKey(templateCompilerPath, pluginInfo); + } + cacheKey; + }; let plugin = [ require.resolve('babel-plugin-htmlbars-inline-precompile'), - { precompile, modules: INLINE_PRECOMPILE_MODULES }, + { precompile, isProduction: options.isProduction, modules: INLINE_PRECOMPILE_MODULES }, 'ember-cli-htmlbars:inline-precompile', ]; return plugin; } -function makeCacheKey(templateCompilerPath, pluginInfo, extra) { +function getTemplateCompilerCacheKey(templateCompilerPath) { let templateCompilerFullPath = require.resolve(templateCompilerPath); - let templateCompilerCacheKey = fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' }); + let cacheData = TemplateCompilerCache.get(templateCompilerFullPath); + + if (cacheData === undefined) { + getTemplateCompiler(templateCompilerFullPath); + cacheData = TemplateCompilerCache.get(templateCompilerFullPath); + } + + return cacheData.templateCompilerCacheKey; +} + +function makeCacheKey(templateCompilerPath, pluginInfo, extra) { + let templateCompilerCacheKey = getTemplateCompilerCacheKey(templateCompilerPath); let cacheItems = [templateCompilerCacheKey, extra].concat(pluginInfo.cacheKeys.sort()); + // extra may be undefined return cacheItems.filter(Boolean).join('|'); } @@ -273,9 +314,6 @@ function setupPlugins(wrappers) { module.exports = { buildOptions, - purgeModule, - registerPlugins, - unregisterPlugins, initializeEmberENV, template, setup, @@ -284,4 +322,6 @@ module.exports = { isColocatedBabelPluginRegistered, isInlinePrecompileBabelPluginRegistered, buildParalleizedBabelPlugin, + getTemplateCompiler, + getTemplateCompilerCacheKey, }; diff --git a/node-tests/ast_plugins_test.js b/node-tests/ast_plugins_test.js index 658b41c7..5edc0ddc 100644 --- a/node-tests/ast_plugins_test.js +++ b/node-tests/ast_plugins_test.js @@ -10,7 +10,6 @@ const { createTempDir, createBuilder } = require('broccoli-test-helper'); const fixturify = require('fixturify'); const addDependencyTracker = require('../lib/addDependencyTracker'); const templateCompiler = require('ember-source/dist/ember-template-compiler.js'); -const CANNOT_UNREGISTER_PLUGINS = !templateCompiler.unregisterPlugin; describe('AST plugins', function() { const they = it; @@ -18,6 +17,12 @@ describe('AST plugins', function() { let input, output, builder, tree, htmlbarsOptions; + let clearTreeCache = co.wrap(function* clearTreeCache(tree) { + if (tree && tree.processor.processor._cache) { + yield tree.processor.processor._cache.clear(); + } + }); + beforeEach( co.wrap(function*() { rewriterCallCount = 0; @@ -26,18 +31,14 @@ describe('AST plugins', function() { htmlbarsOptions = { isHTMLBars: true, templateCompiler: templateCompiler, + templateCompilerPath: require.resolve('ember-source/dist/ember-template-compiler.js'), }; }) ); afterEach( co.wrap(function*() { - if (tree) { - tree.unregisterPlugins(); - if (tree.processor.processor._cache) { - yield tree.processor.processor._cache.clear(); - } - } + yield clearTreeCache(tree); if (builder) { builder.cleanup(); @@ -103,9 +104,6 @@ describe('AST plugins', function() { they( 'are accepted and used.', co.wrap(function*() { - if (CANNOT_UNREGISTER_PLUGINS) { - this.skip(); - } htmlbarsOptions.plugins = { ast: [DivRewriter], }; @@ -125,9 +123,6 @@ describe('AST plugins', function() { they( 'will bust the hot cache if the dependency changes.', co.wrap(function*() { - if (CANNOT_UNREGISTER_PLUGINS) { - this.skip(); - } Object.assign(htmlbarsOptions, { plugins: { ast: [DivRewriter], @@ -184,9 +179,6 @@ describe('AST plugins', function() { they( 'will bust the persistent cache if the template cache key changes.', co.wrap(function*() { - if (CANNOT_UNREGISTER_PLUGINS) { - this.skip(); - } Object.assign(htmlbarsOptions, { plugins: { ast: [DivRewriter], @@ -194,55 +186,60 @@ describe('AST plugins', function() { dependencyInvalidation: true, }); - let firstTree = new TemplateCompiler(input.path(), htmlbarsOptions); + let firstTree, secondTree, thirdTree; try { - output = createBuilder(firstTree); - yield output.build(); - - let templateOutput = output.readText('template.js'); - assert.ok(!templateOutput.match(/div/)); - assert.ok(templateOutput.match(/my-custom-element/)); - assert.strictEqual(rewriterCallCount, 1); - } finally { - yield output.dispose(); - firstTree.unregisterPlugins(); - } - - // The state didn't change. the output should be cached - // and the rewriter shouldn't be invoked. - let secondTree = new TemplateCompiler(input.path(), htmlbarsOptions); - try { - let output = createBuilder(secondTree); - yield output.build(); - assert.deepStrictEqual(output.changes()['template.js'], 'create'); - // the "new" file is read from cache. - let templateOutput = output.readText('template.js'); - assert.ok(!templateOutput.match(/div/)); - assert.ok(templateOutput.match(/my-custom-element/)); - assert.strictEqual(rewriterCallCount, 1); - } finally { - yield output.dispose(); - secondTree.unregisterPlugins(); - } + firstTree = new TemplateCompiler(input.path(), htmlbarsOptions); + + try { + output = createBuilder(firstTree); + yield output.build(); + + let templateOutput = output.readText('template.js'); + assert.ok(!templateOutput.match(/div/)); + assert.ok(templateOutput.match(/my-custom-element/)); + assert.strictEqual(rewriterCallCount, 1); + } finally { + yield output.dispose(); + } - // The state changes. the cache key updates and the template - // should be recompiled. - input.write({ - 'template.tagname': 'MyChangedElement', - }); + // The state didn't change. the output should be cached + // and the rewriter shouldn't be invoked. + secondTree = new TemplateCompiler(input.path(), htmlbarsOptions); + try { + let output = createBuilder(secondTree); + yield output.build(); + assert.deepStrictEqual(output.changes()['template.js'], 'create'); + // the "new" file is read from cache. + let templateOutput = output.readText('template.js'); + assert.ok(!templateOutput.match(/div/)); + assert.ok(templateOutput.match(/my-custom-element/)); + assert.strictEqual(rewriterCallCount, 1); + } finally { + yield output.dispose(); + } - let thirdTree = new TemplateCompiler(input.path(), htmlbarsOptions); - try { - let output = createBuilder(thirdTree); - yield output.build(); - let templateOutput = output.readText('template.js'); - assert.strictEqual(rewriterCallCount, 2); - assert.ok(templateOutput.match(/my-changed-element/)); - assert.strictEqual(rewriterCallCount, 2); + // The state changes. the cache key updates and the template + // should be recompiled. + input.write({ + 'template.tagname': 'MyChangedElement', + }); + + thirdTree = new TemplateCompiler(input.path(), htmlbarsOptions); + try { + let output = createBuilder(thirdTree); + yield output.build(); + let templateOutput = output.readText('template.js'); + assert.strictEqual(rewriterCallCount, 2); + assert.ok(templateOutput.match(/my-changed-element/)); + assert.strictEqual(rewriterCallCount, 2); + } finally { + yield output.dispose(); + } } finally { - yield output.dispose(); - thirdTree.unregisterPlugins(); + clearTreeCache(firstTree); + clearTreeCache(secondTree); + clearTreeCache(thirdTree); } }) ); diff --git a/node-tests/purge-module-test.js b/node-tests/purge-module-test.js deleted file mode 100644 index 4d247cd5..00000000 --- a/node-tests/purge-module-test.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -const purgeModule = require('../lib/utils').purgeModule; -const expect = require('chai').expect; - -describe('purgeModule', function() { - const FIXTURE_COMPILER_PATH = require.resolve('./fixtures/compiler'); - - it('it works correctly', function() { - expect(purgeModule('asdfasdfasdfaf-unknown-file')).to.eql(undefined); - - expect(require.cache[FIXTURE_COMPILER_PATH]).to.eql(undefined); - - require(FIXTURE_COMPILER_PATH); - - const mod = require.cache[FIXTURE_COMPILER_PATH]; - - expect(mod.parent).to.eql(module); - expect(mod.parent.children).to.include(mod); - - purgeModule(FIXTURE_COMPILER_PATH); - - expect(require.cache[FIXTURE_COMPILER_PATH]).to.eql(undefined); - expect(mod.parent.children).to.not.include(mod); - - require(FIXTURE_COMPILER_PATH); - - const freshModule = require.cache[FIXTURE_COMPILER_PATH]; - - expect(freshModule.parent).to.eql(module); - expect(freshModule.parent.children).to.include(freshModule); - - purgeModule(FIXTURE_COMPILER_PATH); - - expect(require.cache[FIXTURE_COMPILER_PATH]).to.eql(undefined); - expect(freshModule.parent.children).to.not.include(mod); - }); -}); diff --git a/node-tests/template_compiler_test.js b/node-tests/template_compiler_test.js index 1f88bb78..9336ab4d 100644 --- a/node-tests/template_compiler_test.js +++ b/node-tests/template_compiler_test.js @@ -38,6 +38,7 @@ describe('TemplateCompiler', function() { htmlbarsOptions = { isHTMLBars: true, templateCompiler: require('ember-source/dist/ember-template-compiler.js'), + templateCompilerPath: require.resolve('ember-source/dist/ember-template-compiler.js'), }; htmlbarsPrecompile = htmlbarsOptions.templateCompiler.precompile; @@ -59,6 +60,74 @@ describe('TemplateCompiler', function() { }) ); + it('invokes AST plugins', async function() { + let source = '{{foo-bar}}'; + input.write({ + 'template.hbs': source, + }); + let plugin = env => { + return { + name: 'fake-ast-plugin', + + visitor: { + MustacheStatement() { + return env.syntax.builders.text('Huzzah!'); + }, + }, + }; + }; + + htmlbarsOptions.plugins = { + ast: [plugin], + }; + + let tree = new TemplateCompiler(input.path(), htmlbarsOptions); + + output = createBuilder(tree); + await output.build(); + + let expected = `export default Ember.HTMLBars.template(${htmlbarsPrecompile(source, { + moduleName: 'template.hbs', + plugins: { + ast: [plugin], + }, + })});`; + + let outputString = output.readText('template.js'); + assert.strictEqual(outputString, expected); + assert.ok(outputString.includes('Huzzah!')); + }); + + it('AST Plugins have access to `isProduction` status', async function() { + let source = '{{foo-bar}}'; + input.write({ + 'template.hbs': source, + }); + + let wasProduction = false; + let plugin = env => { + wasProduction = env.isProduction; + + return { + name: 'fake-ast-plugin', + + visitor: {}, + }; + }; + + htmlbarsOptions.isProduction = true; + htmlbarsOptions.plugins = { + ast: [plugin], + }; + + let tree = new TemplateCompiler(input.path(), htmlbarsOptions); + + output = createBuilder(tree); + await output.build(); + + assert.ok(wasProduction); + }); + it( 'ignores utf-8 byte order marks', co.wrap(function*() { diff --git a/package.json b/package.json index ba45828e..b8a3784a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ember-cli-htmlbars", - "version": "4.3.1", + "version": "4.5.0", "description": "A library for adding htmlbars to ember CLI", "keywords": [ "ember-addon", @@ -33,7 +33,7 @@ }, "dependencies": { "@ember/edition-utils": "^1.2.0", - "babel-plugin-htmlbars-inline-precompile": "^3.0.1", + "babel-plugin-htmlbars-inline-precompile": "^3.2.0", "broccoli-debug": "^0.6.5", "broccoli-persistent-filter": "^2.3.1", "broccoli-plugin": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index 31e54dac..2ff2963f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1645,10 +1645,10 @@ babel-plugin-htmlbars-inline-precompile@^1.0.0: resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-1.0.0.tgz#a9d2f6eaad8a3f3d361602de593a8cbef8179c22" integrity sha512-4jvKEHR1bAX03hBDZ94IXsYCj3bwk9vYsn6ux6JZNL2U5pvzCWjqyrGahfsGNrhERyxw8IqcirOi9Q6WCo3dkQ== -babel-plugin-htmlbars-inline-precompile@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.0.1.tgz#e1e38a4087f446578e419a21c112530c8df02345" - integrity sha512-ZiFY0nQjtdMPGIDwp/5LYOs6rCr54QfcSV5nPbrA7C++Fv4Vb2Q/qrKYx78t+dwmARJztnOBlObFk4z8veHxNA== +babel-plugin-htmlbars-inline-precompile@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.2.0.tgz#c4882ea875d0f5683f0d91c1f72e29a4f14b5606" + integrity sha512-IUeZmgs9tMUGXYu1vfke5I18yYJFldFGdNFQOWslXTnDWXzpwPih7QFduUqvT+awDpDuNtXpdt5JAf43Q1Hhzg== babel-plugin-module-resolver@^3.1.1, babel-plugin-module-resolver@^3.2.0: version "3.2.0" @@ -7098,9 +7098,8 @@ mocha@^7.1.1: yargs-unparser "1.6.0" "module-name-inliner@link:./tests/dummy/lib/module-name-inliner": - version "0.1.0" - dependencies: - ember-cli-version-checker "*" + version "0.0.0" + uid "" morgan@^1.9.1: version "1.9.1"