Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid require cache busting whenever possible. #130

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 96 additions & 6 deletions ember-addon-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,78 @@
const path = require('path');
const utils = require('./utils');
const hashForDep = require('hash-for-dep');
const VersionChecker = require('ember-cli-version-checker');

// used as a way to memoize canAvoidCacheBusting so that we can avoid many many
// top down searches of the addon heirarchy
const NEEDS_CACHE_BUSTING = new WeakMap();

function canAvoidCacheBusting(project) {
if (NEEDS_CACHE_BUSTING.has(project)) {
return NEEDS_CACHE_BUSTING.get(project);
}

let checker = new VersionChecker(project);
let emberVersion = checker.forEmber();

let hasLegacyHTMLBarsAddons = checkAddonsForLegacyVersion(project);

let needsCacheBusting = hasLegacyHTMLBarsAddons || emberVersion.lt('1.13.0');
NEEDS_CACHE_BUSTING.set(project, !!needsCacheBusting);

return needsCacheBusting;
}

function checkAddonsForLegacyVersion(addon) {
let registry = addon.registry;

let plugins = registry && registry.load('htmlbars-ast-plugin');
let hasPlugins = plugins && plugins.length > 0;

if (hasPlugins) {
let htmlbarsInstance = addon.addons.find(addon => addon.name === 'ember-cli-htmlbars');
if (!htmlbarsInstance || htmlbarsInstance.legacyPluginRegistrationCacheBustingRequired !== false) {
return true;
}

let inlineHTMLBarsCompileInstance = addon.addons.find(addon => addon.name === 'ember-cli-htmlbars-inline-precompile');
if (!inlineHTMLBarsCompileInstance || inlineHTMLBarsCompileInstance.legacyPluginRegistrationCacheBustingRequired !== false) {
return true;
}
}

return addon.addons.some(checkAddonsForLegacyVersion);
}

module.exports = {
name: require('./package').name,

init() {
this._super.init && this._super.init.apply(this, arguments);

// default to `true`
this.legacyPluginRegistrationCacheBustingRequired = true;
},

included() {
this._super.included.apply(this, arguments);

// populated lazily to ensure that the registries throughout the entire
// project are populated
//
// population happens in setupPreprocessorRegistry hook, which is called in
// `lib/broccoli/ember-app.js` constructor and `lib/models/addon.js`
// constructor in ember-cli itself
this.legacyPluginRegistrationCacheBustingRequired = canAvoidCacheBusting(this.project);
},

parentRegistry: null,

purgeModule(templateCompilerPath) {
// do nothing if we are operating in the "new world" and avoiding
// global state mutations...
if (this.legacyPluginRegistrationCacheBustingRequired === false) { return; }

// 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
Expand Down Expand Up @@ -43,13 +108,20 @@ module.exports = {
toTree(tree) {
let htmlbarsOptions = this._addon.htmlbarsOptions();
let TemplateCompiler = require('./index');

return new TemplateCompiler(tree, htmlbarsOptions);
},

precompile(string) {
precompile(string, _options) {
let htmlbarsOptions = this._addon.htmlbarsOptions();
let templateCompiler = htmlbarsOptions.templateCompiler;
return utils.template(templateCompiler, string);

let options = Object.assign({}, _options, {
contents: string,
plugins: htmlbarsOptions.plugins,
});

return utils.template(templateCompiler, string, options);
}
});

Expand Down Expand Up @@ -83,18 +155,24 @@ module.exports = {
},

htmlbarsOptions() {
if (this._htmlbarsOptions && this.legacyPluginRegistrationCacheBustingRequired === false) {
return this._htmlbarsOptions;
}

let projectConfig = this.projectConfig() || {};
let EmberENV = projectConfig.EmberENV || {};
let templateCompilerPath = this.templateCompilerPath();
let pluginInfo = this.astPlugins();

this.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
// cloneable) to prevent ember-template-compiler.js from mutating the
// shared global config (the cloning can be removed after we drop support
// for Ember < 3.2).
let clonedEmberENV = JSON.parse(JSON.stringify(EmberENV));
global.EmberENV = clonedEmberENV; // Needed for eval time feature flag checks
let pluginInfo = this.astPlugins();


let htmlbarsOptions = {
isHTMLBars: true,
Expand All @@ -109,12 +187,24 @@ module.exports = {
pluginCacheKey: pluginInfo.cacheKeys
};

if (this.legacyPluginRegistrationCacheBustingRequired !== false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the !== false here makes it hard for my brain to read this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kk, ya I can fix that up

let plugins = pluginInfo.plugins;

if (plugins) {
for (let type in plugins) {
for (let i = 0, l = plugins[type].length; i < l; i++) {
htmlbarsOptions.templateCompiler.registerPlugin(type, plugins[type][i]);
}
}
}
}

this.purgeModule(templateCompilerPath);

delete global.Ember;
delete global.EmberENV;

return htmlbarsOptions;
return this._htmlbarsOptions = htmlbarsOptions;
},

astPlugins() {
Expand Down
17 changes: 2 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,14 @@ class TemplateCompiler extends Filter {
this.inputTree = inputTree;

this.precompile = this.options.templateCompiler.precompile;
this.registerPlugin = this.options.templateCompiler.registerPlugin;

this.registerPlugins();
this.initializeFeatures();
}

baseDir() {
return __dirname;
}

registerPlugins() {
let plugins = this.options.plugins;

if (plugins) {
for (let type in plugins) {
for (let i = 0, l = plugins[type].length; i < l; i++) {
this.registerPlugin(type, plugins[type][i]);
}
}
}
}

initializeFeatures() {
let EmberENV = this.options.EmberENV;
let FEATURES = this.options.FEATURES;
Expand All @@ -76,7 +62,8 @@ class TemplateCompiler extends Filter {
try {
return 'export default ' + utils.template(this.options.templateCompiler, stripBom(string), {
contents: string,
moduleName: relativePath
moduleName: relativePath,
plugins: this.options.plugins,
}) + ';';
} catch(error) {
rethrowBuildError(error);
Expand Down
6 changes: 5 additions & 1 deletion node-tests/purge-module-test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
'use strict';

const purgeModule = require('../ember-addon-main').purgeModule;
const _purgeModule = require('../ember-addon-main').purgeModule;
const expect = require('chai').expect;

describe('purgeModule', function() {
const FIXTURE_COMPILER_PATH = require.resolve('./fixtures/compiler');

function purgeModule(modulePath) {
return _purgeModule.call({}, modulePath);
}

it('it works correctly', function() {
expect(purgeModule('asdfasdfasdfaf-unknown-file')).to.eql(undefined);

Expand Down
65 changes: 60 additions & 5 deletions node-tests/template_compiler_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ const TemplateCompiler = require('../index');
const co = require('co');
const { createTempDir, createBuilder } = require('broccoli-test-helper');
const fixturify = require('fixturify');
const MergeTrees = require('broccoli-merge-trees');

describe('TemplateCompiler', function(){
this.timeout(10000);

let input, output, builder;

function buildHTMLBarsOptions(plugins) {
return {
plugins,
isHTMLBars: true,
templateCompiler: require('ember-source/dist/ember-template-compiler.js'),
};
}

beforeEach(co.wrap(function*() {
input = yield createTempDir();
input.write(fixturify.readSync(`${__dirname}/fixtures`));
Expand All @@ -31,11 +40,7 @@ describe('TemplateCompiler', function(){
let htmlbarsOptions, htmlbarsPrecompile;

beforeEach(function() {
htmlbarsOptions = {
isHTMLBars: true,
templateCompiler: require('ember-source/dist/ember-template-compiler.js')
};

htmlbarsOptions = buildHTMLBarsOptions();
htmlbarsPrecompile = htmlbarsOptions.templateCompiler.precompile;
});

Expand Down Expand Up @@ -95,4 +100,54 @@ describe('TemplateCompiler', function(){

assert.strictEqual(output.readText('web-component-template.js'), expected);
}));

describe('multiple instances', function() {
let first, second;

beforeEach(co.wrap(function*() {
first = yield createTempDir();
second = yield createTempDir();
}));

it('allows each instance to have separate AST plugins', co.wrap(function*() {
first.write({
'first': {
'foo.hbs': `LOLOL`,
}
});

second.write({
'second': {
'bar.hbs': `LOLOL`,
}
});

class SillyPlugin {
constructor(syntax) {
this.syntax = syntax;
}

transform(ast) {
this.syntax.traverse(ast, {
TextNode(node) {
node.chars = 'NOT FUNNY!';
}
});

return ast;
}
}

let firstTree = new TemplateCompiler(first.path(), buildHTMLBarsOptions({
ast: [SillyPlugin],
}));
let secondTree = new TemplateCompiler(second.path(), buildHTMLBarsOptions());

output = createBuilder(new MergeTrees([firstTree, secondTree]));
yield output.build();

assert.ok(output.readText('first/foo.js').includes('NOT FUNNY'), 'first was transformed');
assert.ok(output.readText('second/bar.js').includes('LOLOL'), 'second was not transformed');
}));
});
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@
},
"dependencies": {
"broccoli-persistent-filter": "^2.1.0",
"ember-cli-version-checker": "^2.1.2",
"hash-for-dep": "^1.2.3",
"json-stable-stringify": "^1.0.1",
"strip-bom": "^3.0.0"
},
"devDependencies": {
"@ember/optional-features": "^0.7.0",
"broccoli-merge-trees": "^3.0.1",
"broccoli-test-helper": "^1.5.0",
"chai": "^4.2.0",
"co": "^4.6.0",
Expand Down