Skip to content

Commit 0972d65

Browse files
authored
Merge pull request #103 from mixmaxhq/pure-init
feat: support pure helper definitions
2 parents a643d42 + f38c9ea commit 0972d65

File tree

2 files changed

+77
-43
lines changed

2 files changed

+77
-43
lines changed

README.md

+34-29
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
Features:
66

7-
* Import Handlebars templates as ES6 modules
8-
* Support for Handlebars [helpers](#helpers) and partials
9-
* [Precompiles](http://handlebarsjs.com/precompilation.html) templates so your application only needs the Handlebars runtime
10-
* Handlebars runtime [included](#handlebars)
11-
* Optional rendering to [jQuery collections](#jquery) vs. raw strings
7+
- Import Handlebars templates as ES6 modules
8+
- Support for Handlebars [helpers](#helpers) and partials
9+
- [Precompiles](http://handlebarsjs.com/precompilation.html) templates so your application only needs the Handlebars runtime
10+
- Handlebars runtime [included](#handlebars)
11+
- Optional rendering to [jQuery collections](#jquery) vs. raw strings
1212

1313
## Installation
1414

@@ -31,15 +31,14 @@ var rollup = require('rollup');
3131
var handlebars = require('rollup-plugin-handlebars-plus');
3232
var rootImport = require('rollup-plugin-root-import');
3333

34-
3534
var partialRoots = [`${__dirname}/src/client/js/views/`, `${__dirname}/src/common/views/`];
3635

3736
rollup({
3837
entry: 'main.js',
3938
plugins: [
4039
// Required by use of `partialRoot` below.
4140
rootImport({
42-
root: partialRoots
41+
root: partialRoots,
4342
}),
4443
handlebars({
4544
handlebars: {
@@ -55,14 +54,20 @@ rollup({
5554
// Options to pass to Handlebars' `parse` and `precompile` methods.
5655
options: {
5756
// Whether to generate sourcemaps for the templates
58-
sourceMap: true // Default: true
59-
}
57+
sourceMap: true, // Default: true
58+
},
6059
},
6160

6261
// The ID(s) of modules to import before every template, see the "Helpers" section below.
6362
// Can be a string too.
6463
helpers: ['/utils/HandlebarsHelpers.js'], // Default: none
6564

65+
// Whether to register the defined helpers at template declaration in a way that would allow
66+
// the initialization call to be elided if the template is never used. Useful in a library
67+
// context where the templates might all get tree-shaken away, leaving no need for the
68+
// helpers. Does nothing if helpers is empty.
69+
helpersPureInitialize: true, // Default: false
70+
6671
// In case you want to compile files with other extensions.
6772
templateExtension: '.html', // Default: '.hbs'
6873

@@ -74,10 +79,10 @@ rollup({
7479
partialRoot: partialRoots, // Default: none
7580

7681
// The module ID of jQuery, see the "jQuery" section below.
77-
jquery: 'jquery' // Default: none
78-
})
79-
]
80-
})
82+
jquery: 'jquery', // Default: none
83+
}),
84+
],
85+
});
8186
```
8287

8388
lets you do this:
@@ -114,16 +119,16 @@ rollup({
114119
entry: 'main.js',
115120
plugins: [
116121
handlebars({
117-
helpers: ['/utils/HandlebarsHelpers.js']
118-
})
119-
]
120-
})
122+
helpers: ['/utils/HandlebarsHelpers.js'],
123+
}),
124+
],
125+
});
121126
```
122127

123128
```js
124129
// /utils/HandlebarsHelpers.js
125-
export default function(Handlebars) {
126-
Handlebars.registerHelper('encodeURIComponent', function(text) {
130+
export default function (Handlebars) {
131+
Handlebars.registerHelper('encodeURIComponent', function (text) {
127132
return new Handlebars.SafeString(encodeURIComponent(text));
128133
});
129134
}
@@ -168,9 +173,9 @@ rollup({
168173
commonjs({
169174
include: 'node_modules/**',
170175
}),
171-
handlebars()
172-
]
173-
})
176+
handlebars(),
177+
],
178+
});
174179
```
175180

176181
In case you need the default runtime ID, it's available as `handlebars.runtimeId`. This might be
@@ -192,8 +197,8 @@ import Template from './index.html';
192197
var MyView = Backbone.View.extend({
193198
render() {
194199
this.setElement(Template());
195-
}
196-
})
200+
},
201+
});
197202
```
198203

199204
or by customizing the template using jQuery's APIs:
@@ -206,7 +211,7 @@ var tooltip = TooltipTemplate();
206211

207212
tooltip.css({
208213
left: 50,
209-
top: 100
214+
top: 100,
210215
});
211216

212217
$('body').append(tooltip);
@@ -223,10 +228,10 @@ rollup({
223228
entry: 'main.js',
224229
plugins: [
225230
handlebars({
226-
jquery: 'jquery'
227-
})
228-
]
229-
})
231+
jquery: 'jquery',
232+
}),
233+
],
234+
});
230235
```
231236

232237
Curious about how to ID jQuery when it's a global i.e. you're _not_ bundling it?

src/index.js

+43-14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ const DEFAULT_HANDLEBARS_ID = path.relative(
1010
require.resolve('handlebars/runtime')
1111
);
1212

13+
const INTERNAL_INIT_ID = '\0handlebarsPlusHelpersInit';
14+
15+
const escapePath = (path) => path.replace(/\\/g, '\\\\');
16+
17+
const nonEmptyOr = (array, fallback) => (array.length ? array : fallback);
18+
const asArrayOr = (value, fallback) => nonEmptyOr([].concat(value || []), fallback);
19+
1320
/**
1421
* Constructs a Rollup plugin to compile Handlebars templates.
1522
*
@@ -74,7 +81,36 @@ function handlebars(options) {
7481
const Handlebars = options.handlebars.module || require('handlebars');
7582
const ImportScanner = require('./ImportScanner')(Handlebars);
7683

84+
const hbsImport = `import Handlebars from '${escapePath(options.handlebars.id)}';\n`;
85+
86+
const wrapTemplateDefinition = options.helpersPureInitialize
87+
? (defineTemplate, initExpr) =>
88+
defineTemplate((expr) => `(function() {${initExpr};return ${expr};})()`)
89+
: (defineTemplate, initExpr) => `${initExpr};\n${defineTemplate()}`;
90+
91+
// Support `helpers` being singular or plural.
92+
const helpers = asArrayOr(options.helpers, null);
93+
7794
return {
95+
resolveId: (id) => (helpers && id === INTERNAL_INIT_ID ? id : undefined),
96+
97+
load(id) {
98+
if (!helpers || id !== INTERNAL_INIT_ID) return;
99+
100+
let body = hbsImport;
101+
body += '';
102+
103+
const initExpr = helpers.map((helperPath, i) => {
104+
const ref = `Helpers${i}`;
105+
body += `import ${ref} from '${escapePath(helperPath)}';\n`;
106+
return ` ${ref}.__initialized || (${ref}(Handlebars), ${ref}.__initialized = true);\n`;
107+
});
108+
109+
body += `export default function() {\n${initExpr.join('')}}\n`;
110+
111+
return { code: body, map: { mappings: '' } };
112+
},
113+
78114
transform(code, id) {
79115
if (!id.endsWith(options.templateExtension)) return;
80116

@@ -95,28 +131,21 @@ function handlebars(options) {
95131
template = template.code;
96132
}
97133

98-
const escapePath = (path) => path.replace(/\\/g, '\\\\');
99-
100-
let body = `import Handlebars from '${escapePath(options.handlebars.id)}';\n`;
134+
let body = hbsImport;
101135
if (options.jquery) body += `import $ from '${escapePath(options.jquery)}';\n`;
102136

103-
if (options.helpers) {
104-
// Support `helpers` being singular or plural.
105-
[].concat(options.helpers).forEach((helpers, i) => {
106-
body += `import Helpers${i} from '${escapePath(helpers)}';\n`;
107-
body += `if (!Helpers${i}.__initialized) {\n`;
108-
body += ` Helpers${i}(Handlebars);\n`;
109-
body += ` Helpers${i}.__initialized = true;\n`;
110-
body += `}\n`;
111-
});
112-
}
137+
body += `import init from '${INTERNAL_INIT_ID}';\n`;
113138

114139
for (const partial of scanner.partials) {
115140
// Register the partial dependencies as partials.
116141
body += `import '${escapePath(partial)}${options.templateExtension}';\n`;
117142
}
118143

119-
body += `var Template = /*#__PURE__*/Handlebars.template(${template});\n`;
144+
body += wrapTemplateDefinition(
145+
(wrapExpression = (expr) => expr) =>
146+
`var Template = /*#__PURE__*/${wrapExpression(`Handlebars.template(${template})`)};\n`,
147+
'init()'
148+
);
120149

121150
if (options.isPartial(name)) {
122151
let partialName = id;

0 commit comments

Comments
 (0)