Skip to content

Commit a95a4fe

Browse files
committed
Drive all template compilation from babel
Classically, standalone templates got a dedicated preprocessor that had nothing to do with Javascript transpilation. Later came inline templates that appear inside Javascript. Those were handled as a totally separate code path from the standalone templates. To this day, there are two different code paths for handling these two cases. But at this point, templates-inside-javascript are the foundational primitive that is aligned with [where Ember is heading](emberjs/rfcs#779), because they have access to Javascript scope and this solves a lot of problems. We can treat standalone HBS as just a degenerate kind of JS that is easily up-compiled via a pure function, allowing them to go down the Javascript code path and allowing us to drop all the old code path. This also makes it easier to support new features like [AST transforms that can manipulate Javascript scope](emberjs/babel-plugin-ember-template-compilation#5). Embroider already [implemented this same pattern](embroider-build/embroider#1010).
1 parent 8e2ba66 commit a95a4fe

File tree

8 files changed

+44
-781
lines changed

8 files changed

+44
-781
lines changed

lib/template-compiler-plugin.js

Lines changed: 14 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,38 @@
11
'use strict';
22

3-
const path = require('path');
4-
const utils = require('./utils');
53
const Filter = require('broccoli-persistent-filter');
6-
const crypto = require('crypto');
7-
const stringify = require('json-stable-stringify');
8-
const stripBom = require('strip-bom');
9-
10-
function rethrowBuildError(error) {
11-
if (!error) {
12-
throw new Error('Unknown Error');
13-
}
14-
15-
if (typeof error === 'string') {
16-
throw new Error('[string exception]: ' + error);
17-
} else {
18-
// augment with location and type information and re-throw.
19-
error.type = 'Template Compiler Error';
20-
error.location = error.location && error.location.start;
21-
22-
throw error;
23-
}
24-
}
4+
const jsStringEscape = require('js-string-escape');
255

266
class TemplateCompiler extends Filter {
27-
constructor(inputTree, _options, requiresModuleApiPolyfill = true) {
28-
let options = _options || {};
29-
7+
constructor(inputTree, options = {}) {
308
if (!('persist' in options)) {
319
options.persist = true;
3210
}
33-
3411
super(inputTree, options);
35-
36-
this.options = options;
37-
this.inputTree = inputTree;
38-
this.requiresModuleApiPolyfill = requiresModuleApiPolyfill;
39-
40-
// TODO: do we need this?
41-
this.precompile = this.options.templateCompiler.precompile;
42-
43-
let { templateCompiler, EmberENV } = options;
44-
45-
utils.initializeEmberENV(templateCompiler, EmberENV);
4612
}
4713

4814
baseDir() {
4915
return __dirname;
5016
}
5117

5218
processString(string, relativePath) {
53-
let srcDir = this.inputPaths[0];
54-
let srcName = path.join(srcDir, relativePath);
55-
56-
try {
57-
// we have to reverse these for reasons that are a bit bonkers. the initial
58-
// version of this system used `registeredPlugin` from
59-
// `ember-template-compiler.js` to set up these plugins (because Ember ~ 1.13
60-
// only had `registerPlugin`, and there was no way to pass plugins directly
61-
// to the call to `compile`/`precompile`). calling `registerPlugin`
62-
// unfortunately **inverted** the order of plugins (it essentially did
63-
// `PLUGINS = [plugin, ...PLUGINS]`).
64-
//
65-
// sooooooo...... we are forced to maintain that **absolutely bonkers** ordering
66-
let astPlugins = this.options.plugins ? [...this.options.plugins.ast].reverse() : [];
67-
68-
let precompiled = this.options.templateCompiler.precompile(stripBom(string), {
69-
contents: string,
70-
isProduction: this.options.isProduction,
71-
moduleName: relativePath,
72-
parseOptions: {
73-
srcName: srcName,
74-
},
75-
76-
// intentionally not using `plugins: this.options.plugins` here
77-
// because if we do, Ember will mutate the shared plugins object (adding
78-
// all of the built in AST transforms into plugins.ast, which breaks
79-
// persistent caching)
80-
plugins: {
81-
ast: astPlugins,
82-
},
83-
});
84-
85-
if (this.options.dependencyInvalidation) {
86-
let plugins = pluginsWithDependencies(this.options.plugins.ast);
87-
let dependencies = [];
88-
for (let i = 0; i < plugins.length; i++) {
89-
let pluginDeps = plugins[i].getDependencies(relativePath);
90-
dependencies = dependencies.concat(pluginDeps);
91-
}
92-
this.dependencies.setDependencies(relativePath, dependencies);
93-
}
94-
95-
if (this.requiresModuleApiPolyfill) {
96-
return `export default Ember.HTMLBars.template(${precompiled});`;
97-
} else {
98-
return `import { createTemplateFactory } from '@ember/template-factory';\n\nexport default createTemplateFactory(${precompiled});`;
99-
}
100-
} catch (error) {
101-
rethrowBuildError(error);
19+
return [
20+
`import { hbs } from 'ember-cli-htmlbars';`,
21+
`export default hbs('${jsStringEscape(string)}', { moduleName: '${jsStringEscape(
22+
relativePath
23+
)}' });`,
24+
'',
25+
].join('\n');
26+
}
27+
28+
getDestFilePath(relativePath) {
29+
if (relativePath.endsWith('.hbs')) {
30+
return relativePath.replace(/\.hbs$/, '.js');
10231
}
10332
}
104-
105-
_buildOptionsForHash() {
106-
let strippedOptions = {};
107-
108-
for (let key in this.options) {
109-
if (key !== 'templateCompiler') {
110-
strippedOptions[key] = this.options[key];
111-
}
112-
}
113-
114-
strippedOptions._requiresModuleApiPolyfill = this.requiresModuleApiPolyfill;
115-
116-
return strippedOptions;
117-
}
118-
119-
optionsHash() {
120-
if (!this._optionsHash) {
121-
let templateCompilerCacheKey = utils.getTemplateCompilerCacheKey(
122-
this.options.templateCompilerPath
123-
);
124-
125-
this._optionsHash = crypto
126-
.createHash('md5')
127-
.update(stringify(this._buildOptionsForHash()), 'utf8')
128-
.update(templateCompilerCacheKey, 'utf8')
129-
.digest('hex');
130-
}
131-
132-
return this._optionsHash;
133-
}
134-
135-
cacheKeyProcessString(string, relativePath) {
136-
return (
137-
this.optionsHash() + Filter.prototype.cacheKeyProcessString.call(this, string, relativePath)
138-
);
139-
}
14033
}
14134

14235
TemplateCompiler.prototype.extensions = ['hbs', 'handlebars'];
14336
TemplateCompiler.prototype.targetExtension = 'js';
14437

145-
function pluginsWithDependencies(registeredPlugins) {
146-
let found = [];
147-
for (let i = 0; i < registeredPlugins.length; i++) {
148-
if (registeredPlugins[i].getDependencies) {
149-
found.push(registeredPlugins[i]);
150-
}
151-
}
152-
return found;
153-
}
154-
15538
module.exports = TemplateCompiler;

lib/utils.js

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -190,34 +190,6 @@ function getTemplateCompiler(templateCompilerPath, EmberENV = {}) {
190190
return context.module.exports;
191191
}
192192

193-
function initializeEmberENV(templateCompiler, EmberENV) {
194-
if (!templateCompiler || !EmberENV) {
195-
return;
196-
}
197-
198-
let props;
199-
200-
if (EmberENV.FEATURES) {
201-
props = Object.keys(EmberENV.FEATURES);
202-
203-
props.forEach((prop) => {
204-
templateCompiler._Ember.FEATURES[prop] = EmberENV.FEATURES[prop];
205-
});
206-
}
207-
208-
if (EmberENV) {
209-
props = Object.keys(EmberENV);
210-
211-
props.forEach((prop) => {
212-
if (prop === 'FEATURES') {
213-
return;
214-
}
215-
216-
templateCompiler._Ember.ENV[prop] = EmberENV[prop];
217-
});
218-
}
219-
}
220-
221193
function setup(pluginInfo, options) {
222194
let projectConfig = options.projectConfig || {};
223195
let templateCompilerPath = options.templateCompilerPath;
@@ -374,13 +346,9 @@ function setupPlugins(wrappers) {
374346

375347
module.exports = {
376348
buildOptions,
377-
initializeEmberENV,
378349
setup,
379-
makeCacheKey,
380350
setupPlugins,
381351
isColocatedBabelPluginRegistered,
382352
isInlinePrecompileBabelPluginRegistered,
383353
buildParalleizedBabelPlugin,
384-
getTemplateCompiler,
385-
getTemplateCompilerCacheKey,
386354
};

node-tests/addon-test.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,11 @@ describe('ember-cli-htmlbars addon', function () {
8787
yield output.build();
8888

8989
expect(output.read()).to.deep.equal({
90-
'hello.js':
91-
'export default Ember.HTMLBars.template({"id":"pb4oG9l/","block":"[[[10,0],[12],[1,\\"Hello, World!\\"],[13]],[],false,[]]","moduleName":"hello.hbs","isStrictMode":false});',
90+
'hello.js': [
91+
`import { hbs } from 'ember-cli-htmlbars';`,
92+
`export default hbs('<div>Hello, World!</div>', { moduleName: 'hello.hbs' });`,
93+
'',
94+
].join('\n'),
9295
});
9396
})
9497
);

0 commit comments

Comments
 (0)