Skip to content

Commit 41afd22

Browse files
rwjblueRobert Jackson
authored andcommitted
Remove usage of registerPlugin / unregisterPlugin
These APIs force Ember to use global mutable state (the list of plugins) and require some pretty gnarly cache busting techniques to avoid having addons break each other (due to the global mutable state leaking from one addon to another). In order to discourage this mutable state issue, Ember has deprecated usage of `Ember.HTMLBars.registerPlugin` and `Ember.HTMLBars.unregisterPlugin` (as of Ember 3.27). This PR changes all invocations to pass the required AST transforms directly in to the compiler invocation (instead of calling `registerPlugin` before hand), and allows us to continue working properly while avoiding the deprecation (and that evil mutable state). (cherry picked from commit 1c813dc)
1 parent 64448c0 commit 41afd22

File tree

4 files changed

+81
-114
lines changed

4 files changed

+81
-114
lines changed

lib/template-compiler-plugin.js

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,15 @@ class TemplateCompiler extends Filter {
3939
// TODO: do we need this?
4040
this.precompile = this.options.templateCompiler.precompile;
4141

42-
let { templateCompiler, plugins, EmberENV } = options;
42+
let { templateCompiler, EmberENV } = options;
4343

44-
utils.registerPlugins(templateCompiler, plugins);
4544
utils.initializeEmberENV(templateCompiler, EmberENV);
4645
}
4746

4847
baseDir() {
4948
return __dirname;
5049
}
5150

52-
unregisterPlugins() {
53-
let { templateCompiler, plugins } = this.options;
54-
55-
utils.unregisterPlugins(templateCompiler, plugins);
56-
}
57-
58-
registeredASTPlugins() {
59-
// This is a super obtuse way to get access to the plugins we've registered
60-
// it also returns other plugins that are registered by ember itself.
61-
let options = this.options.templateCompiler.compileOptions();
62-
return (options.plugins && options.plugins.ast) || [];
63-
}
64-
6551
processString(string, relativePath) {
6652
let srcDir = this.inputPaths[0];
6753
let srcName = path.join(srcDir, relativePath);
@@ -75,10 +61,18 @@ class TemplateCompiler extends Filter {
7561
parseOptions: {
7662
srcName: srcName,
7763
},
64+
65+
// intentionally not using `plugins: this.options.plugins` here
66+
// because if we do, Ember will mutate the shared plugins object (adding
67+
// all of the built in AST transforms into plugins.ast, which breaks
68+
// persistent caching)
69+
plugins: {
70+
ast: this.options.plugins ? this.options.plugins.ast : [],
71+
},
7872
}) +
7973
';';
8074
if (this.options.dependencyInvalidation) {
81-
let plugins = pluginsWithDependencies(this.registeredASTPlugins());
75+
let plugins = pluginsWithDependencies(this.options.plugins.ast);
8276
let dependencies = [];
8377
for (let i = 0; i < plugins.length; i++) {
8478
let pluginDeps = plugins[i].getDependencies(relativePath);

lib/utils.js

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const crypto = require('crypto');
34
const fs = require('fs');
45
const path = require('path');
56
const hashForDep = require('hash-for-dep');
@@ -140,26 +141,6 @@ function getTemplateCompiler(templateCompilerPath, EmberENV = {}) {
140141
return context.module.exports;
141142
}
142143

143-
function registerPlugins(templateCompiler, plugins) {
144-
if (plugins) {
145-
for (let type in plugins) {
146-
for (let i = 0, l = plugins[type].length; i < l; i++) {
147-
templateCompiler.registerPlugin(type, plugins[type][i]);
148-
}
149-
}
150-
}
151-
}
152-
153-
function unregisterPlugins(templateCompiler, plugins) {
154-
if (plugins) {
155-
for (let type in plugins) {
156-
for (let i = 0, l = plugins[type].length; i < l; i++) {
157-
templateCompiler.unregisterPlugin(type, plugins[type][i]);
158-
}
159-
}
160-
}
161-
}
162-
163144
function initializeEmberENV(templateCompiler, EmberENV) {
164145
if (!templateCompiler || !EmberENV) {
165146
return;
@@ -201,11 +182,17 @@ function setup(pluginInfo, options) {
201182
let htmlbarsOptions = buildOptions(projectConfig, templateCompilerPath, pluginInfo);
202183
let { templateCompiler } = htmlbarsOptions;
203184

204-
registerPlugins(templateCompiler, {
205-
ast: pluginInfo.plugins,
206-
});
185+
let templatePrecompile = templateCompiler.precompile;
186+
187+
let precompile = (template, options) => {
188+
options = options || {};
189+
options.plugins = {
190+
ast: pluginInfo.plugins,
191+
};
192+
193+
return templatePrecompile(template, options);
194+
};
207195

208-
let { precompile } = templateCompiler;
209196
precompile.baseDir = () => path.resolve(__dirname, '..');
210197

211198
let cacheKey;
@@ -305,8 +292,6 @@ function setupPlugins(wrappers) {
305292

306293
module.exports = {
307294
buildOptions,
308-
registerPlugins,
309-
unregisterPlugins,
310295
initializeEmberENV,
311296
template,
312297
setup,

node-tests/ast_plugins_test.js

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ const { createTempDir, createBuilder } = require('broccoli-test-helper');
1010
const fixturify = require('fixturify');
1111
const addDependencyTracker = require('../lib/addDependencyTracker');
1212
const templateCompiler = require('ember-source/dist/ember-template-compiler.js');
13-
const CANNOT_UNREGISTER_PLUGINS = !templateCompiler.unregisterPlugin;
1413

1514
describe('AST plugins', function() {
1615
const they = it;
1716
this.timeout(10000);
1817

1918
let input, output, builder, tree, htmlbarsOptions;
2019

20+
let clearTreeCache = co.wrap(function* clearTreeCache(tree) {
21+
if (tree && tree.processor.processor._cache) {
22+
yield tree.processor.processor._cache.clear();
23+
}
24+
});
25+
2126
beforeEach(
2227
co.wrap(function*() {
2328
rewriterCallCount = 0;
@@ -33,12 +38,7 @@ describe('AST plugins', function() {
3338

3439
afterEach(
3540
co.wrap(function*() {
36-
if (tree) {
37-
tree.unregisterPlugins();
38-
if (tree.processor.processor._cache) {
39-
yield tree.processor.processor._cache.clear();
40-
}
41-
}
41+
yield clearTreeCache(tree);
4242

4343
if (builder) {
4444
builder.cleanup();
@@ -104,9 +104,6 @@ describe('AST plugins', function() {
104104
they(
105105
'are accepted and used.',
106106
co.wrap(function*() {
107-
if (CANNOT_UNREGISTER_PLUGINS) {
108-
this.skip();
109-
}
110107
htmlbarsOptions.plugins = {
111108
ast: [DivRewriter],
112109
};
@@ -126,9 +123,6 @@ describe('AST plugins', function() {
126123
they(
127124
'will bust the hot cache if the dependency changes.',
128125
co.wrap(function*() {
129-
if (CANNOT_UNREGISTER_PLUGINS) {
130-
this.skip();
131-
}
132126
Object.assign(htmlbarsOptions, {
133127
plugins: {
134128
ast: [DivRewriter],
@@ -185,65 +179,67 @@ describe('AST plugins', function() {
185179
they(
186180
'will bust the persistent cache if the template cache key changes.',
187181
co.wrap(function*() {
188-
if (CANNOT_UNREGISTER_PLUGINS) {
189-
this.skip();
190-
}
191182
Object.assign(htmlbarsOptions, {
192183
plugins: {
193184
ast: [DivRewriter],
194185
},
195186
dependencyInvalidation: true,
196187
});
197188

198-
let firstTree = new TemplateCompiler(input.path(), htmlbarsOptions);
189+
let firstTree, secondTree, thirdTree;
199190

200191
try {
201-
output = createBuilder(firstTree);
202-
yield output.build();
203-
204-
let templateOutput = output.readText('template.js');
205-
assert.ok(!templateOutput.match(/div/));
206-
assert.ok(templateOutput.match(/my-custom-element/));
207-
assert.strictEqual(rewriterCallCount, 1);
208-
} finally {
209-
yield output.dispose();
210-
firstTree.unregisterPlugins();
211-
}
212-
213-
// The state didn't change. the output should be cached
214-
// and the rewriter shouldn't be invoked.
215-
let secondTree = new TemplateCompiler(input.path(), htmlbarsOptions);
216-
try {
217-
let output = createBuilder(secondTree);
218-
yield output.build();
219-
assert.deepStrictEqual(output.changes()['template.js'], 'create');
220-
// the "new" file is read from cache.
221-
let templateOutput = output.readText('template.js');
222-
assert.ok(!templateOutput.match(/div/));
223-
assert.ok(templateOutput.match(/my-custom-element/));
224-
assert.strictEqual(rewriterCallCount, 1);
225-
} finally {
226-
yield output.dispose();
227-
secondTree.unregisterPlugins();
228-
}
192+
firstTree = new TemplateCompiler(input.path(), htmlbarsOptions);
193+
194+
try {
195+
output = createBuilder(firstTree);
196+
yield output.build();
197+
198+
let templateOutput = output.readText('template.js');
199+
assert.ok(!templateOutput.match(/div/));
200+
assert.ok(templateOutput.match(/my-custom-element/));
201+
assert.strictEqual(rewriterCallCount, 1);
202+
} finally {
203+
yield output.dispose();
204+
}
229205

230-
// The state changes. the cache key updates and the template
231-
// should be recompiled.
232-
input.write({
233-
'template.tagname': 'MyChangedElement',
234-
});
206+
// The state didn't change. the output should be cached
207+
// and the rewriter shouldn't be invoked.
208+
secondTree = new TemplateCompiler(input.path(), htmlbarsOptions);
209+
try {
210+
let output = createBuilder(secondTree);
211+
yield output.build();
212+
assert.deepStrictEqual(output.changes()['template.js'], 'create');
213+
// the "new" file is read from cache.
214+
let templateOutput = output.readText('template.js');
215+
assert.ok(!templateOutput.match(/div/));
216+
assert.ok(templateOutput.match(/my-custom-element/));
217+
assert.strictEqual(rewriterCallCount, 1);
218+
} finally {
219+
yield output.dispose();
220+
}
235221

236-
let thirdTree = new TemplateCompiler(input.path(), htmlbarsOptions);
237-
try {
238-
let output = createBuilder(thirdTree);
239-
yield output.build();
240-
let templateOutput = output.readText('template.js');
241-
assert.strictEqual(rewriterCallCount, 2);
242-
assert.ok(templateOutput.match(/my-changed-element/));
243-
assert.strictEqual(rewriterCallCount, 2);
222+
// The state changes. the cache key updates and the template
223+
// should be recompiled.
224+
input.write({
225+
'template.tagname': 'MyChangedElement',
226+
});
227+
228+
thirdTree = new TemplateCompiler(input.path(), htmlbarsOptions);
229+
try {
230+
let output = createBuilder(thirdTree);
231+
yield output.build();
232+
let templateOutput = output.readText('template.js');
233+
assert.strictEqual(rewriterCallCount, 2);
234+
assert.ok(templateOutput.match(/my-changed-element/));
235+
assert.strictEqual(rewriterCallCount, 2);
236+
} finally {
237+
yield output.dispose();
238+
}
244239
} finally {
245-
yield output.dispose();
246-
thirdTree.unregisterPlugins();
240+
clearTreeCache(firstTree);
241+
clearTreeCache(secondTree);
242+
clearTreeCache(thirdTree);
247243
}
248244
})
249245
);

node-tests/template_compiler_test.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,8 @@ describe('TemplateCompiler', function() {
8383

8484
let tree = new TemplateCompiler(input.path(), htmlbarsOptions);
8585

86-
try {
87-
output = createBuilder(tree);
88-
await output.build();
89-
} finally {
90-
tree.unregisterPlugins();
91-
}
86+
output = createBuilder(tree);
87+
await output.build();
9288

9389
let expected = `export default Ember.HTMLBars.template(${htmlbarsPrecompile(source, {
9490
moduleName: 'template.hbs',
@@ -126,12 +122,8 @@ describe('TemplateCompiler', function() {
126122

127123
let tree = new TemplateCompiler(input.path(), htmlbarsOptions);
128124

129-
try {
130-
output = createBuilder(tree);
131-
await output.build();
132-
} finally {
133-
tree.unregisterPlugins();
134-
}
125+
output = createBuilder(tree);
126+
await output.build();
135127

136128
assert.ok(wasProduction);
137129
});

0 commit comments

Comments
 (0)