ember-cli-htmlbars
Advanced tools
Comparing version 4.4.1 to 4.5.0
@@ -0,1 +1,11 @@ | ||
## 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) | ||
@@ -2,0 +12,0 @@ |
'use strict'; | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
@@ -43,5 +42,4 @@ const utils = require('./utils'); | ||
let { templateCompiler, plugins, EmberENV } = options; | ||
let { templateCompiler, EmberENV } = options; | ||
utils.registerPlugins(templateCompiler, plugins); | ||
utils.initializeEmberENV(templateCompiler, EmberENV); | ||
@@ -54,15 +52,2 @@ } | ||
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) { | ||
@@ -72,2 +57,13 @@ let srcDir = this.inputPaths[0]; | ||
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 = | ||
@@ -82,6 +78,14 @@ 'export default ' + | ||
}, | ||
// 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 = []; | ||
@@ -112,16 +116,12 @@ for (let i = 0; i < plugins.length; i++) { | ||
_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'); | ||
@@ -128,0 +128,0 @@ } |
145
lib/utils.js
'use strict'; | ||
const crypto = require('crypto'); | ||
const fs = require('fs'); | ||
@@ -9,3 +10,6 @@ const path = require('path'); | ||
const addDependencyTracker = require('./addDependencyTracker'); | ||
const vm = require('vm'); | ||
const TemplateCompilerCache = new Map(); | ||
const INLINE_PRECOMPILE_MODULES = Object.freeze({ | ||
@@ -83,14 +87,6 @@ 'ember-cli-htmlbars': 'hbs', | ||
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, | ||
@@ -107,53 +103,60 @@ | ||
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 | ||
function getTemplateCompiler(templateCompilerPath, EmberENV = {}) { | ||
let templateCompilerFullPath = require.resolve(templateCompilerPath); | ||
let cacheData = TemplateCompilerCache.get(templateCompilerFullPath); | ||
// 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.` | ||
); | ||
} | ||
if (cacheData === undefined) { | ||
let templateCompilerContents = fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' }); | ||
let templateCompilerCacheKey = crypto | ||
.createHash('md5') | ||
.update(templateCompilerContents) | ||
.digest('hex'); | ||
cacheData = { | ||
script: new vm.Script(templateCompilerContents, { | ||
filename: templateCompilerPath, | ||
}), | ||
templateCompilerCacheKey, | ||
}; | ||
TemplateCompilerCache.set(templateCompilerFullPath, cacheData); | ||
} | ||
delete require.cache[templateCompilerPath]; | ||
} | ||
let { script } = cacheData; | ||
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]); | ||
} | ||
} | ||
// 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; | ||
} | ||
} | ||
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 context = vm.createContext(sandbox); | ||
script.runInContext(context); | ||
return context.module.exports; | ||
} | ||
@@ -202,7 +205,18 @@ | ||
registerPlugins(templateCompiler, { | ||
ast: pluginInfo.plugins, | ||
}); | ||
let templatePrecompile = templateCompiler.precompile; | ||
let { precompile } = templateCompiler; | ||
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); | ||
}; | ||
precompile.baseDir = () => path.resolve(__dirname, '..'); | ||
@@ -227,6 +241,18 @@ | ||
function getTemplateCompilerCacheKey(templateCompilerPath) { | ||
let templateCompilerFullPath = require.resolve(templateCompilerPath); | ||
let cacheData = TemplateCompilerCache.get(templateCompilerFullPath); | ||
if (cacheData === undefined) { | ||
getTemplateCompiler(templateCompilerFullPath); | ||
cacheData = TemplateCompilerCache.get(templateCompilerFullPath); | ||
} | ||
return cacheData.templateCompilerCacheKey; | ||
} | ||
function makeCacheKey(templateCompilerPath, pluginInfo, extra) { | ||
let templateCompilerFullPath = require.resolve(templateCompilerPath); | ||
let templateCompilerCacheKey = fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' }); | ||
let templateCompilerCacheKey = getTemplateCompilerCacheKey(templateCompilerPath); | ||
let cacheItems = [templateCompilerCacheKey, extra].concat(pluginInfo.cacheKeys.sort()); | ||
// extra may be undefined | ||
@@ -296,5 +322,2 @@ return cacheItems.filter(Boolean).join('|'); | ||
buildOptions, | ||
purgeModule, | ||
registerPlugins, | ||
unregisterPlugins, | ||
initializeEmberENV, | ||
@@ -308,2 +331,4 @@ template, | ||
buildParalleizedBabelPlugin, | ||
getTemplateCompiler, | ||
getTemplateCompilerCacheKey, | ||
}; |
{ | ||
"name": "ember-cli-htmlbars", | ||
"version": "4.4.1", | ||
"version": "4.5.0", | ||
"description": "A library for adding htmlbars to ember CLI", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
57083
977
12