Skip to content

Commit b35c554

Browse files
author
Robert Jackson
authored
Merge pull request #660 from ember-cli/cache-template-compiler-evaluation
Replace `purgeModule` cache busting with `vm` based sandboxing
2 parents a798b5f + 8d5dbcf commit b35c554

File tree

2 files changed

+44
-78
lines changed

2 files changed

+44
-78
lines changed

lib/utils.js

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const hashForDep = require('hash-for-dep');
77
const debugGenerator = require('heimdalljs-logger');
88
const logger = debugGenerator('ember-cli-htmlbars:utils');
99
const addDependencyTracker = require('./addDependencyTracker');
10+
const vm = require('vm');
11+
12+
const TemplateCompilerCache = new Map();
1013

1114
const INLINE_PRECOMPILE_MODULES = Object.freeze({
1215
'ember-cli-htmlbars': 'hbs',
@@ -92,18 +95,10 @@ function buildParalleizedBabelPlugin(
9295
function buildOptions(projectConfig, templateCompilerPath, pluginInfo) {
9396
let EmberENV = projectConfig.EmberENV || {};
9497

95-
purgeModule(templateCompilerPath);
96-
97-
// do a full clone of the EmberENV (it is guaranteed to be structured
98-
// cloneable) to prevent ember-template-compiler.js from mutating
99-
// the shared global config
100-
let clonedEmberENV = JSON.parse(JSON.stringify(EmberENV));
101-
global.EmberENV = clonedEmberENV; // Needed for eval time feature flag checks
102-
10398
let htmlbarsOptions = {
10499
isHTMLBars: true,
105100
EmberENV: EmberENV,
106-
templateCompiler: require(templateCompilerPath),
101+
templateCompiler: getTemplateCompiler(templateCompilerPath, EmberENV),
107102
templateCompilerPath: templateCompilerPath,
108103

109104
pluginNames: pluginInfo.pluginNames,
@@ -116,37 +111,47 @@ function buildOptions(projectConfig, templateCompilerPath, pluginInfo) {
116111
pluginCacheKey: pluginInfo.cacheKeys,
117112
};
118113

119-
purgeModule(templateCompilerPath);
120-
121-
delete global.Ember;
122-
delete global.EmberENV;
123-
124114
return htmlbarsOptions;
125115
}
126116

127-
function purgeModule(templateCompilerPath) {
128-
// ensure we get a fresh templateCompilerModuleInstance per ember-addon
129-
// instance NOTE: this is a quick hack, and will only work as long as
130-
// templateCompilerPath is a single file bundle
131-
//
132-
// (╯°□°)╯︵ ɹǝqɯǝ
133-
//
134-
// we will also fix this in ember for future releases
135-
136-
// Module will be cached in .parent.children as well. So deleting from require.cache alone is not sufficient.
137-
let mod = require.cache[templateCompilerPath];
138-
if (mod && mod.parent) {
139-
let index = mod.parent.children.indexOf(mod);
140-
if (index >= 0) {
141-
mod.parent.children.splice(index, 1);
142-
} else {
143-
throw new TypeError(
144-
`ember-cli-htmlbars attempted to purge '${templateCompilerPath}' but something went wrong.`
145-
);
146-
}
117+
function getTemplateCompiler(templateCompilerPath, EmberENV = {}) {
118+
let templateCompilerFullPath = require.resolve(templateCompilerPath);
119+
let cacheData = TemplateCompilerCache.get(templateCompilerFullPath);
120+
121+
if (cacheData === undefined) {
122+
let templateCompilerContents = fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' });
123+
let templateCompilerCacheKey = crypto
124+
.createHash('md5')
125+
.update(templateCompilerContents)
126+
.digest('hex');
127+
128+
cacheData = {
129+
script: new vm.Script(templateCompilerContents, {
130+
filename: templateCompilerPath,
131+
}),
132+
133+
templateCompilerCacheKey,
134+
};
135+
136+
TemplateCompilerCache.set(templateCompilerFullPath, cacheData);
147137
}
148138

149-
delete require.cache[templateCompilerPath];
139+
let { script } = cacheData;
140+
141+
// do a full clone of the EmberENV (it is guaranteed to be structured
142+
// cloneable) to prevent ember-template-compiler.js from mutating
143+
// the shared global config
144+
let clonedEmberENV = JSON.parse(JSON.stringify(EmberENV));
145+
146+
let context = vm.createContext({
147+
EmberENV: clonedEmberENV,
148+
module: { require, exports: {} },
149+
require,
150+
});
151+
152+
script.runInContext(context);
153+
154+
return context.module.exports;
150155
}
151156

152157
function registerPlugins(templateCompiler, plugins) {
@@ -259,11 +264,10 @@ function setup(pluginInfo, options) {
259264

260265
function makeCacheKey(templateCompilerPath, pluginInfo, extra) {
261266
let templateCompilerFullPath = require.resolve(templateCompilerPath);
262-
let templateCompilerCacheKey = crypto
263-
.createHash('md5')
264-
.update(fs.readFileSync(templateCompilerFullPath, { encoding: 'utf-8' }))
265-
.digest('hex');
267+
let { templateCompilerCacheKey } = TemplateCompilerCache.get(templateCompilerFullPath);
268+
266269
let cacheItems = [templateCompilerCacheKey, extra].concat(pluginInfo.cacheKeys.sort());
270+
267271
// extra may be undefined
268272
return cacheItems.filter(Boolean).join('|');
269273
}
@@ -332,7 +336,6 @@ function setupPlugins(wrappers) {
332336

333337
module.exports = {
334338
buildOptions,
335-
purgeModule,
336339
registerPlugins,
337340
unregisterPlugins,
338341
initializeEmberENV,
@@ -343,4 +346,5 @@ module.exports = {
343346
isColocatedBabelPluginRegistered,
344347
isInlinePrecompileBabelPluginRegistered,
345348
buildParalleizedBabelPlugin,
349+
getTemplateCompiler,
346350
};

node-tests/purge-module-test.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)