Skip to content

Commit

Permalink
Merge pull request #660 from ember-cli/cache-template-compiler-evalua…
Browse files Browse the repository at this point in the history
…tion

Replace `purgeModule` cache busting with `vm` based sandboxing
  • Loading branch information
rwjblue authored Feb 26, 2021
2 parents a798b5f + 8d5dbcf commit b35c554
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 78 deletions.
84 changes: 44 additions & 40 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ 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',
Expand Down Expand Up @@ -92,18 +95,10 @@ function buildParalleizedBabelPlugin(
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,

pluginNames: pluginInfo.pluginNames,
Expand All @@ -116,37 +111,47 @@ 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);

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;

// 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 context = vm.createContext({
EmberENV: clonedEmberENV,
module: { require, exports: {} },
require,
});

script.runInContext(context);

return context.module.exports;
}

function registerPlugins(templateCompiler, plugins) {
Expand Down Expand Up @@ -259,11 +264,10 @@ function setup(pluginInfo, options) {

function makeCacheKey(templateCompilerPath, pluginInfo, extra) {
let templateCompilerFullPath = require.resolve(templateCompilerPath);
let templateCompilerCacheKey = crypto
.createHash('md5')
.update(fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' }))
.digest('hex');
let { templateCompilerCacheKey } = TemplateCompilerCache.get(templateCompilerFullPath);

let cacheItems = [templateCompilerCacheKey, extra].concat(pluginInfo.cacheKeys.sort());

// extra may be undefined
return cacheItems.filter(Boolean).join('|');
}
Expand Down Expand Up @@ -332,7 +336,6 @@ function setupPlugins(wrappers) {

module.exports = {
buildOptions,
purgeModule,
registerPlugins,
unregisterPlugins,
initializeEmberENV,
Expand All @@ -343,4 +346,5 @@ module.exports = {
isColocatedBabelPluginRegistered,
isInlinePrecompileBabelPluginRegistered,
buildParalleizedBabelPlugin,
getTemplateCompiler,
};
38 changes: 0 additions & 38 deletions node-tests/purge-module-test.js

This file was deleted.

0 comments on commit b35c554

Please sign in to comment.