├── .gitignore
├── LICENSE
├── README.md
├── file-loader.js
├── index.js
├── lib
├── attributeParser.js
├── macroParser.js
└── macros.js
├── package.json
├── runtime
└── index.js
└── test
├── attributeTest.js
├── lib
├── WebpackLoaderMock.js
├── loadOutput.js
├── loadTemplate.js
└── removeFirstline.js
├── loaderTest.js
├── macroTest.js
└── templates
├── absolute-image.html
├── compilation-options.html
├── custom-attributes.html
├── custom-macro.html
├── dynamic-attribute-with-parseDynamicRoutes.html
├── dynamic-attribute-with-root.html
├── dynamic-attribute.html
├── image.html
├── include.html
├── macro.html
├── macro_argument_list.html
├── macro_boolean_args.html
├── macro_escaped.html
├── macro_numeric_args.html
├── macro_repeat.html
├── macro_string_args.html
├── output
├── absolute-image-with-root.txt
├── absolute-image.txt
├── compilation-options.txt
├── custom-attributes.txt
├── custom-macro.txt
├── disabled-macro.txt
├── dynamic-attribute-with-parseDynamicRoutes.txt
├── dynamic-attribute-with-root.txt
├── dynamic-attribute.txt
├── image.txt
├── include.txt
├── macro_argument_list.txt
├── macro_boolean_args.txt
├── macro_escaped.txt
├── macro_numeric_args.txt
├── macro_repeat.txt
├── macro_string_args.txt
├── require.txt
├── simple-with-comment.txt
└── simple.txt
├── require.html
└── simple.html
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 - 2017 Emmanuel Antico
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | handlebars-template-loader
2 | ==========================
3 |
4 | A Handlebars template loader for Webpack
5 |
6 | ### Changelog
7 |
8 |
9 | * 1.0: Loader now works with Webpack 4. Still a beta release.
10 |
11 |
12 |
13 |
14 | **Table of Contents**
15 |
16 | - [Installation](#installation)
17 | - [Usage](#usage)
18 | - [Loading templates](#loading-templates)
19 | - [Using helpers](#using-helpers)
20 | - [Using partials](#using-partials)
21 | - [Options](#options)
22 | - [Prepending filename comment](#prepending-filename-comment)
23 | - [Images](#images)
24 | - [Runtime path](#runtime-path)
25 | - [Compilation options](#compilation-options)
26 | - [Macros](#macros)
27 | - [require](#require)
28 | - [include](#include)
29 | - [repeat](#repeat)
30 | - [Custom macros](#custom-macros)
31 | - [Disabling macros](#disabling-macros)
32 | - [Arguments](#arguments)
33 | - [Escaping](#escaping)
34 | - [License](#license)
35 |
36 |
37 |
38 |
39 |
40 | # Installation #
41 |
42 |
43 |
44 | ```bash
45 | npm install handlebars-template-loader
46 | ```
47 |
48 |
49 |
50 | >Since version 0.5.4, this loaders does not include Handlebars in its dependency list. Make sure to install Handlebars before running webpack. Read https://github.com/npm/npm/issues/6565 for details.
51 |
52 |
53 |
54 | # Usage #
55 |
56 |
57 |
58 | ```javascript
59 | module.exports = {
60 | //...
61 |
62 | module: {
63 | loaders: [
64 | { test: /\.hbs/, loader: "handlebars-template-loader" }
65 | ]
66 | },
67 |
68 | node: {
69 | fs: "empty" // avoids error messages
70 | }
71 | };
72 | ```
73 |
74 |
75 |
76 | # Loading templates #
77 |
78 |
79 |
80 | ```html
81 |
82 |
Hello {{name}}
83 | ```
84 |
85 |
86 |
87 | ```javascript
88 | // File: app.js
89 | var compiled = require('./hello.hbs');
90 | return compiled({name: "world"});
91 | ```
92 |
93 |
94 |
95 | # Using helpers #
96 |
97 |
98 |
99 | ```javascript
100 | // File: helpers.js
101 |
102 | // Get Handlebars instance
103 | var Handlebars = require('handlebars-template-loader/runtime');
104 |
105 | Handlebars.registerHelper('list', function(items, options) {
106 | var out = "";
107 |
108 | for(var i=0, l=items.length; i" + options.fn(items[i]) + "";
110 | }
111 |
112 | return out + " ";
113 | });
114 |
115 | Handlebars.registerHelper('link', function(text, url) {
116 | text = Handlebars.Utils.escapeExpression(text);
117 | url = Handlebars.Utils.escapeExpression(url);
118 |
119 | var result = '' + text + ' ';
120 |
121 | return new Handlebars.SafeString(result);
122 | });
123 | ```
124 |
125 |
126 |
127 | ```javascript
128 | // File: main.js
129 |
130 | // Include template helpers
131 | require("./helpers.js");
132 | ```
133 |
134 |
135 |
136 | # Using partials #
137 |
138 |
139 |
140 | ```javascript
141 | // Get Handlebars instance
142 | var Handlebars = require('handlebars-template-loader/runtime');
143 |
144 | // Require partial
145 | var partial = require('path/to/my/_partial.hbs');
146 |
147 | // Register partial
148 | Handlebars.registerPartial('my_partial_name', partial);
149 |
150 | ```
151 |
152 |
153 |
154 | # Options #
155 |
156 |
157 |
158 | ## Prepending filename comment ##
159 |
160 |
161 |
162 | When debugging a large single page app with the DevTools, it's often hard to find the template that contains a bug. With the following config a HTML comment is prepended to the template with the relative path in it (e.g. ``).
163 |
164 |
165 |
166 | ```javascript
167 | module.exports = {
168 | //...
169 |
170 | module: {
171 | loaders: [
172 | {
173 | test: /\.hbs$/,
174 | loader: "handlebars-template-loader",
175 | query: {
176 | prependFilenameComment: __dirname,
177 | }
178 | }
179 | ]
180 | }
181 | };
182 | ```
183 |
184 |
185 |
186 | ## Images ##
187 |
188 |
189 |
190 | In order to load images you must install either the `file-loader` or the `url-loader` package.
191 |
192 | ```javascript
193 | module.exports = {
194 | //...
195 |
196 | module: {
197 | loaders: [
198 | //...
199 | { test: /\.hbs/, loader: "handlebars-template-loader" },
200 | { test: /\.jpg/, loader: "file-loader" },
201 | { test: /\.png/, loader: "url-loader?mimetype=image/png" },
202 | ]
203 | }
204 | };
205 | ```
206 |
207 |
208 |
209 | ```html
210 |
211 |
212 |
213 |
214 |
215 | ```
216 |
217 |
218 |
219 | Images with an absolute path are not translated unless a `root` option is defined
220 |
221 | ```html
222 |
223 |
224 |
225 |
226 |
227 | ```
228 |
229 |
230 |
231 | In order to deactivate image processing define the `attributes` option as an empty array.
232 |
233 | ```javascript
234 | module.exports = {
235 | //...
236 |
237 | module: {
238 | loaders: [
239 | {
240 | test: /\.hbs$/,
241 | loader: "handlebars-template-loader",
242 | query: {
243 | attributes: []
244 | }
245 | }
246 | ]
247 | }
248 | };
249 | ```
250 |
251 |
252 |
253 | You could also add which attributes need to be processed in the form of pairs *tag:attribute*.
254 |
255 |
256 |
257 | ```javascript
258 | module.exports = {
259 | //...
260 |
261 | module: {
262 | loaders: [
263 | {
264 | test: /\.hbs$/,
265 | loader: "handlebars-template-loader",
266 | query: {
267 | attributes: ['img:src', 'x-img:src']
268 | }
269 | }
270 | ]
271 | }
272 | };
273 | ```
274 |
275 | Dynamic attributes won't be afected by this behaviour by default.
276 |
277 | ```html
278 |
279 |
280 | ```
281 |
282 | In order to append the root directory you'll need to specify the `parseDynamicRoutes` argument.
283 |
284 | ```javascript
285 | module.exports = {
286 | //...
287 |
288 | module: {
289 | loaders: [
290 | {
291 | test: /\.html$/,
292 | loader: "handlebars-template-loader",
293 | query: {
294 | root: "myapp",
295 | parseDynamicRoutes: true
296 | }
297 | }
298 | ]
299 | }
300 | };
301 | ```
302 |
303 | ```html
304 |
305 |
306 | ```
307 |
308 |
309 |
310 | ## Runtime path ##
311 |
312 | If you have a custom location for your Handlebars runtime module then you can set that in your `query` object via the `runtimePath` property. This is the path to the Handlebars runtime that every `.hbs` file will require and use. By default this loader looks up the absolute path to the `handlebars/runtime` in your `node_modules` folder. Changing this property is useful if you are doing somethign non-standard with your Handlebar templates, for example setting an alias for the `handlebars/runtime` path.
313 |
314 |
315 |
316 | ```javascript
317 | module.exports = {
318 | //...
319 |
320 | module: {
321 | loaders: [
322 | {
323 | test: /\.html$/,
324 | loader: "handlebars-template-loader",
325 | query: {
326 | runtimePath: 'handlebars/runtime'
327 | }
328 | }
329 | ]
330 | }
331 | };
332 | ```
333 |
334 |
335 |
336 | ## Compilation options ##
337 |
338 |
339 |
340 | Handlebars does support [additional compilation options](http://handlebarsjs.com/reference.html) that you can specify in your `query` object literal.
341 |
342 |
343 |
344 | ```javascript
345 | module.exports = {
346 | //...
347 |
348 | module: {
349 | loaders: [
350 | {
351 | test: /\.html$/,
352 | loader: "handlebars-template-loader",
353 | query: {
354 | root: "myapp",
355 | strict: true,
356 | noEscape: true
357 | }
358 | }
359 | ]
360 | }
361 | };
362 | ```
363 |
364 |
365 |
366 | # Macros #
367 |
368 |
369 | Macros allow additional features like including templates or inserting custom text in a compiled templates.
370 |
371 |
372 |
373 | ## require ##
374 |
375 |
376 |
377 | The `require` macro expects a path to a handlebars template. The macro is then translated to a webpack require expression that evaluates the template using the same arguments.
378 |
379 |
380 |
381 | ```html
382 | Profile
383 |
384 | Name: {{name}}
385 |
386 | Surname: {{surname}}
387 |
388 | @require('profile-details.hbs')
389 |
390 | ```
391 |
392 |
393 |
394 | ## include ##
395 |
396 |
397 |
398 | While the `require` macro expects a resource that returns a function, the `include` macro can be used for resources that return plain text. For example, we can include text loaded through the `html-loader` directly in our template.
399 |
400 | ```html
401 |
402 |
Introduction
403 | @include('intro.htm')
404 | Authors
405 | @include('authors.htm')
406 |
407 | ```
408 |
409 |
410 |
411 | ## repeat ##
412 |
413 |
414 |
415 | The `repeat` macro will repeat the given string the amount of times as specified by the second argument (default to 1). It will only accept string literals.
416 |
417 | ```html
418 | Lorem ipsum
419 | @repeat(' ', 3)
420 | Sit amet
421 | @repeat('\n')
422 | ```
423 |
424 |
425 |
426 | ## Custom macros ##
427 |
428 |
429 |
430 | We can include additional macros by defining them in the webpack configuration file. Remember that the value returned by a macro is inserted as plain javascript, so in order to insert a custom text we need to use nested quotes. For example, let's say that we want a macro that includes a copyright string in our template.
431 |
432 |
433 |
434 | ```javascript
435 | // File: webpack.config.js
436 | module.exports = {
437 | // ...
438 |
439 | module: {
440 | loaders: {
441 | // ...
442 | { test: /\.hbs/, loader: "handlebars-template-loader" },
443 | }
444 | },
445 |
446 | macros: {
447 | copyright: function () {
448 | return "'Copyright FakeCorp 2014 - 2015
'";
449 | }
450 | }
451 | }
452 | ```
453 |
454 |
455 |
456 | We then invoke our macro from within the template as usual.
457 |
458 |
459 |
460 | ```html
461 |
464 | ```
465 |
466 |
467 |
468 | ## Disabling macros ##
469 |
470 |
471 |
472 | You can disable macros if you are a bit unsure about their usage or just simply want faster processing. This is achieved by setting the `parseMacros` options to false.
473 |
474 |
475 |
476 | ```javascript
477 | module.exports = {
478 | // ...
479 |
480 | module: {
481 | loaders: {
482 | // ...
483 | {
484 | test: /\.hbs/,
485 | loader: "handlebars-template-loader",
486 | query: {
487 | parseMacros: false
488 | }
489 | },
490 | }
491 | }
492 | }
493 | ```
494 |
495 |
496 |
497 | ## Arguments ##
498 |
499 |
500 |
501 | Macros can accept an arbitrary number of arguments. Only boolean, strings and numeric types are supported.
502 |
503 |
504 |
505 | ```javascript
506 | // File: webpack.config.js
507 | module.exports = {
508 | // ...
509 |
510 | module: {
511 | loaders: {
512 | // ...
513 | { test: /\.html$/, loader: "handlebars-template-loader" },
514 | }
515 | },
516 |
517 | macros: {
518 | header: function (size, content) {
519 | return "'" + content + " '";
520 | }
521 | }
522 | }
523 | ```
524 |
525 |
526 |
527 | ```html
528 | @header(1, 'Welcome')
529 | Lorem ipsum
530 | @header(3, 'Contents')
531 | Sit amet
532 | ```
533 |
534 |
535 |
536 | ## Escaping ##
537 |
538 |
539 |
540 | Macro expressions can be escaped with the `\` character.
541 |
542 | ```html
543 | @repeat(' ', 3)
544 | \@escaped()
545 | @custom_macro()
546 | ```
547 |
548 | Translates to
549 |
550 | ```html
551 |
552 | @escaped()
553 | custom string
554 | ```
555 |
556 |
557 |
558 | # License #
559 |
560 | Released under the MIT license.
561 |
--------------------------------------------------------------------------------
/file-loader.js:
--------------------------------------------------------------------------------
1 | var loaderUtils = require('loader-utils');
2 |
3 | module.exports = function (source) {
4 | if (this.cacheable) {
5 | this.cacheable();
6 | }
7 | var query = this.query instanceof Object ? this.query : loaderUtils.parseQuery(this.query);
8 |
9 | var allLoadersButThisOne = this.loaders.filter(function(loader) {
10 | return loader.module !== module.exports;
11 | });
12 |
13 | // This loader shouldn't kick in if there is any other loader
14 | if (allLoadersButThisOne.length > 0) {
15 | return source;
16 | }
17 |
18 | return 'module.exports = ' + JSON.stringify(query.url);
19 | };
20 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var Handlebars = require('handlebars');
2 | var HbsRuntime = require('./runtime');
3 | var loaderUtils = require('loader-utils');
4 | var path = require('path');
5 |
6 | // Parsers
7 | var attributeParser = require('./lib/attributeParser');
8 | var macroParser = require('./lib/macroParser');
9 |
10 | // Helpers
11 | var _extend = function(obj, from) {
12 | for (var key in from) {
13 | if (!from.hasOwnProperty(key)) continue;
14 | obj[key] = from[key];
15 | }
16 | return obj;
17 | };
18 |
19 | // Extendable arguments
20 | var macros = _extend({}, require('./lib/macros'));
21 |
22 | module.exports = function(content) {
23 | if (this.cacheable) this.cacheable();
24 | var callback = this.async();
25 |
26 | // Default arguments
27 | var root,
28 | parseMacros = true,
29 | attributes = ['img:src'],
30 | parseDynamicRoutes = false,
31 | runtimePath = require.resolve('handlebars/runtime').replace(/\\/g, '/');
32 |
33 | // Parse arguments
34 | var query = this.query instanceof Object ? this.query : loaderUtils.parseQuery(this.query);
35 |
36 | if (typeof(query) === 'object') {
37 | if (query.attributes !== undefined) {
38 | attributes = Array.isArray(query.attributes) ? query.attributes : [];
39 | }
40 |
41 | root = query.root;
42 |
43 | if (query.parseMacros !== undefined) {
44 | parseMacros = !!query.parseMacros;
45 | }
46 |
47 | // Prepend a html comment with the filename in it
48 | if (query.prependFilenameComment) {
49 | var filenameRelative = path.relative(query.prependFilenameComment, this.resource);
50 | content = '\n\n' + content;
51 | }
52 |
53 | // Check if dynamic routes must be parsed
54 | if (query.parseDynamicRoutes !== undefined) {
55 | parseDynamicRoutes = !!query.parseDynamicRoutes;
56 | }
57 |
58 | if (query.runtimePath) {
59 | runtimePath = query.runtimePath;
60 | }
61 | }
62 |
63 | // Include additional macros
64 | if (this.options && this.options.macros instanceof Object) {
65 | _extend(macros, this.options.macros);
66 | }
67 |
68 | // Parser contexts
69 | var macrosContext, attributesContext;
70 |
71 | // Parse macros
72 | if (parseMacros) {
73 | macrosContext = macroParser(content, function (macro) {
74 | return macros[macro] !== undefined && typeof(macros[macro]) === 'function';
75 | }, 'MACRO');
76 | content = macrosContext.replaceMatches(content);
77 | }
78 |
79 | // Parse attributes
80 | attributesContext = attributeParser(content, function (tag, attr) {
81 | return attributes.indexOf(tag + ':' + attr) !== -1;
82 | }, 'ATTRIBUTE', root, parseDynamicRoutes);
83 | content = attributesContext.replaceMatches(content);
84 |
85 | // Compile template
86 | var source = Handlebars.precompile(content, query);
87 |
88 | // Resolve macros
89 | if (parseMacros) {
90 | source = macrosContext.resolveMacros(source, macros);
91 | }
92 |
93 | // Resolve attributes
94 | source = attributesContext.resolveAttributes(source);
95 |
96 | callback(null, 'var Handlebars = require(\'' + runtimePath + '\');\n' +
97 | 'module.exports = (Handlebars[\'default\'] || Handlebars).template(' + source + ');');
98 | };
99 |
100 | module.exports.Handlebars = HbsRuntime;
101 |
--------------------------------------------------------------------------------
/lib/attributeParser.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var url = require('url');
3 | var Parser = require("fastparse");
4 | var loaderUtils = require('loader-utils');
5 |
6 | /**
7 | * HELPERS
8 | */
9 |
10 | // Reminder: path.isAbsolute is not available in Node 0.10.x
11 | var pathIsAbsolute = function (attrValue) {
12 | return path.resolve(attrValue) == path.normalize(attrValue);
13 | };
14 |
15 | // Detects is a given resource string includes a custom loader
16 | var hasCustomLoader = function (resourcePath) {
17 | var resource = resourcePath.split('!\.');
18 | if (resource.length > 1)
19 | return resource;
20 | return false;
21 | };
22 |
23 | // Checks whether a string contains a template expression
24 | var isTemplate = function (expression) {
25 | return /\{\{.*\}\}/.test(expression);
26 | };
27 |
28 | /**
29 | * ATTRIBUTECONTEXT CLASS
30 | */
31 | var AttributeContext = function(isRelevantTagAttr, usid, root, parseDynamicRoutes) {
32 | this.matches = [];
33 | this.isRelevantTagAttr = isRelevantTagAttr;
34 | this.usid = usid;
35 | this.data = {};
36 | this.root = root;
37 | this.parseDynamicRoutes = parseDynamicRoutes;
38 |
39 | // The ident method builds a unique string expression
40 | // that replaces an attribute value.
41 | // This value is replaced again for the expected expression
42 | // once the template has been transformed to a function
43 | this.ident = function() {
44 | return "____" + usid + Math.random() + "____";
45 | };
46 | };
47 |
48 | // The replaceMatches method does an initial parse
49 | // that replaces attribute values with a unique expression
50 | AttributeContext.prototype.replaceMatches = function(content) {
51 | var self = this;
52 | content = [content];
53 | this.matches.reverse();
54 |
55 | this.matches.forEach(function(match) {
56 | // Determine if the attribute contains a template expresssion
57 | if (match.containsTemplate) {
58 | // Check if path should be modified to include the "root" option
59 | if (pathIsAbsolute(match.value) && self.root !== undefined) {
60 | var x = content.pop();
61 | content.push(x.substr(match.start + match.length));
62 | content.push(self.parseDynamicRoutes ? loaderUtils.urlToRequest(match.value, self.root) : match.value);
63 | content.push(x.substr(0, match.start));
64 | }
65 | } else {
66 | // Ignore if path is absolute and no root path has been defined
67 | if (pathIsAbsolute(match.value) && self.root === undefined) {
68 | return;
69 | }
70 |
71 | // Ignore if is a URL
72 | if (!loaderUtils.isUrlRequest(match.value, self.root)) {
73 | return;
74 | }
75 |
76 | var uri = url.parse(match.value);
77 | if (uri.hash !== null && uri.hash !== undefined) {
78 | uri.hash = null;
79 | match.value = uri.format();
80 | match.length = match.value.length;
81 | }
82 |
83 | do {
84 | var ident = self.ident();
85 | } while (self.data[ident]);
86 |
87 | self.data[ident] = match;
88 |
89 | var x = content.pop();
90 | content.push(x.substr(match.start + match.length));
91 | content.push(ident);
92 | content.push(x.substr(0, match.start));
93 | }
94 | });
95 |
96 | content.reverse();
97 | return content.join('');
98 | };
99 |
100 | // Replaces the expressions inserted by replaceMatches with the corresponding requires
101 | AttributeContext.prototype.resolveAttributes = function(content) {
102 | var regex = new RegExp('____' + this.usid + '[0-9\\.]+____', 'g');
103 | var self = this;
104 | return content.replace(regex, function(match) {
105 | if (!self.data[match]) {
106 | return match;
107 | }
108 |
109 | var url = self.data[match].value;
110 | // Make resource available through file-loader
111 | var fallbackLoader = require.resolve('../file-loader.js') + '?url=' + encodeURIComponent(url);
112 | return "\" + require(" + JSON.stringify(fallbackLoader + '!' + loaderUtils.urlToRequest(url, self.root)) + ") + \"";
113 | });
114 | };
115 |
116 | /**
117 | * PARSER
118 | */
119 |
120 | // Process a tag attribute
121 | var processMatch = function(match, strUntilValue, name, value, index) {
122 | var self = this;
123 | var containsTemplate = false;
124 |
125 | // Check if attribute is included in the "attributes" option
126 | if (!this.isRelevantTagAttr(this.currentTag, name)) {
127 | return;
128 | }
129 |
130 | this.matches.push({
131 | start: index + strUntilValue.length,
132 | length: value.length,
133 | value: value,
134 | containsTemplate: isTemplate(value)
135 | });
136 | };
137 |
138 | // Parser configuration
139 | var specs = {
140 | outside: {
141 | "": true,
142 | "": true,
143 | "<[!\\?].*?>": true,
144 | "<\/[^>]+>": true,
145 | "<([a-zA-Z\\-:]+)\\s*": function(match, tagName) {
146 | this.currentTag = tagName;
147 | return 'inside';
148 | }
149 | },
150 |
151 | inside: {
152 | "\\s+": true, // Eat up whitespace
153 | ">": 'outside', // End of attributes
154 | "(([a-zA-Z\\-]+)\\s*=\\s*\")([^\"]*)\"": processMatch,
155 | "(([a-zA-Z\\-]+)\\s*=\\s*\')([^\']*)\'": processMatch,
156 | "(([a-zA-Z\\-]+)\\s*=\\s*)([^\\s>]+)": processMatch
157 | }
158 | };
159 |
160 | var parser = new Parser(specs);
161 |
162 | module.exports = function parse(html, isRelevantTagAttr, usid, root) {
163 | var context = new AttributeContext(isRelevantTagAttr, usid, root);
164 | return parser.parse('outside', html, context);
165 | };
166 |
--------------------------------------------------------------------------------
/lib/macroParser.js:
--------------------------------------------------------------------------------
1 | var Parser = require("fastparse");
2 |
3 | /**
4 | * MACRO CLASS
5 | */
6 | var Macro = function(name, index, length) {
7 | this.name = name;
8 | this.start = index;
9 | this.length = length;
10 | this.args = [];
11 | };
12 |
13 | Macro.prototype.getArguments = function() {
14 | var args = [];
15 |
16 | this.args.forEach(function(arg) {
17 | args.push(arg.value);
18 | });
19 |
20 | return args;
21 | };
22 |
23 | /**
24 | * MACROCONTEXT CLASS
25 | */
26 | var MacroContext = function(isMacroAvailable, usid) {
27 | this.currentDirective = null;
28 | this.matches = [];
29 | this.isMacroAvailable = isMacroAvailable;
30 | this.usid = usid;
31 | this.data = {};
32 |
33 | // The ident method builds a unique string expression that replaces an macro.
34 | // This value is replaced again for the expected expression once the template
35 | // has been transformed to a function
36 | this.ident = function() {
37 | return "____" + usid + Math.random() + "____";
38 | };
39 | };
40 |
41 | MacroContext.prototype.replaceMatches = function(content) {
42 | var self = this;
43 | content = [content];
44 | this.matches.reverse();
45 |
46 | this.matches.forEach(function(match) {
47 | do {
48 | var ident = self.ident();
49 | } while (self.data[ident]);
50 |
51 | self.data[ident] = match;
52 |
53 | var x = content.pop();
54 | content.push(x.substr(match.start + match.length));
55 | content.push(ident);
56 | content.push(x.substr(0, match.start));
57 | });
58 |
59 | content.reverse();
60 | return content.join('');
61 | };
62 |
63 | MacroContext.prototype.resolveMacros = function(content, macros) {
64 | var regex = new RegExp('____' + this.usid + '[0-9\\.]+____', 'g');
65 | var self = this;
66 |
67 | // Replace macro expressions
68 | content = content.code || content;
69 | content = content.replace(regex, function(match) {
70 | if (!self.data[match]) {
71 | return match;
72 | }
73 |
74 | // TODO: invoke macros with configuration context
75 | var macro = self.data[match];
76 | return '" + ' + macros[macro.name].apply(null, macro.getArguments()) + ' + "';
77 | });
78 |
79 | // Replace escaped macros
80 | content = content.replace(/\\+(@[\w]+)/, function(match, expr) {
81 | return expr;
82 | });
83 |
84 | return content;
85 | };
86 |
87 | // Parses a macro string argument
88 | var processStringArg = function(match, value, index, length) {
89 | if (!this.currentMacro) return;
90 | this.currentMacro.args.push({
91 | start: index + value.length,
92 | index: index,
93 | length: length,
94 | value: value
95 | });
96 | };
97 |
98 | // Parses a macro numeric argument
99 | var processNumArg = function(match, value, index, length) {
100 | if (!this.currentMacro) return;
101 | this.currentMacro.args.push({
102 | start: index + value.length,
103 | index: index,
104 | length: length,
105 | value: parseFloat(value)
106 | });
107 | };
108 |
109 | // Parses a macro boolean argument
110 | var processBooleanArg = function(match, value, index, length) {
111 | if (!this.currentMacro) return;
112 | this.currentMacro.args.push({
113 | start: index + value.length,
114 | index: index,
115 | length: length,
116 | value: value === 'true'
117 | });
118 | };
119 |
120 | // Parser configuration
121 | var specs = {
122 | outside: {
123 | "^@(\\w+)\\(|([^\\\\])@(\\w+)\\(": function(match, name, prefix, _name, index, length) {
124 | name = name || _name;
125 |
126 | if (!this.isMacroAvailable(name)) {
127 | this.currentMacro = null;
128 | return 'inside';
129 | }
130 |
131 | var macro = new Macro(name, prefix ? index + 1 : index, length);
132 | this.matches.push(macro);
133 | this.currentMacro = macro;
134 | return 'inside';
135 | }
136 | },
137 |
138 | inside: {
139 | "\\)": function(match, index) {
140 | if (this.currentMacro !== null) {
141 | this.currentMacro.length = 1 + index - this.currentMacro.start;
142 | }
143 | return 'outside';
144 | },
145 | "\'([^\']*)\'": processStringArg,
146 | "\"([^\"]*)\"": processStringArg,
147 | "\\s*([\\d|\\.]+)\\s*": processNumArg,
148 | "\\s*(true|false)\\s*": processBooleanArg,
149 | "\\s+": true
150 | }
151 | };
152 |
153 | var parser = new Parser(specs);
154 |
155 | module.exports = function parse(html, isMacroAvailable, usid) {
156 | var context = new MacroContext(isMacroAvailable, usid);
157 | return parser.parse('outside', html, context);
158 | };
159 |
--------------------------------------------------------------------------------
/lib/macros.js:
--------------------------------------------------------------------------------
1 | var loaderUtils = require('loader-utils');
2 |
3 | var strRepeat = function(str, times) {
4 | var result = '';
5 |
6 | for (var i = 0; i < times; i++) {
7 | result += str;
8 | }
9 |
10 | return result;
11 | };
12 |
13 | // Default macros
14 | module.exports = {
15 | // Includes a child template using the same context
16 | require: function(resourcePath) {
17 | // Since Handlebars v4, an extra argument called "container" is passed to the helper wrapper.
18 | // In order to keep compatibility, we remove the first argument from the list if we detect that more than 6 arguments are available.
19 | // See issue #9 for details.
20 | return "require(" + JSON.stringify(loaderUtils.urlToRequest(resourcePath)) + ").apply(null, Array.prototype.slice.call(arguments, arguments.length > 6))";
21 | },
22 |
23 | // Includes the contents of a given resource
24 | include: function(resourcePath) {
25 | return "require(" + JSON.stringify(loaderUtils.urlToRequest(resourcePath)) + ")";
26 | },
27 |
28 | repeat: function(str, times) {
29 | var text = strRepeat(str || '', typeof(times) == 'undefined' ? 1 : parseInt(times));
30 | return "'" + text + "'";
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "handlebars-template-loader",
3 | "version": "1.0.0",
4 | "description": "A Handlebars template loader for Webpack",
5 | "main": "index.js",
6 | "homepage": "https://github.com/emaphp/handlebars-template-loader",
7 | "license": "MIT",
8 | "bugs": {
9 | "url": "https://github.com/emaphp/handlebars-template-loader/issues"
10 | },
11 | "author": {
12 | "name": "Emmanuel Antico",
13 | "email": "emmanuel.antico@gmail.com"
14 | },
15 | "contributors": [
16 | {
17 | "name": "Jérôme Steunou (JSteunou)"
18 | },
19 | {
20 | "name": "Patrick Browne (ptbrowne)"
21 | },
22 | {
23 | "name": "Ivan (Ivanca)"
24 | },
25 | {
26 | "name": "Matt Thompson (whatknight)"
27 | },
28 | {
29 | "name": "Harry Wolff (hswolff)"
30 | }
31 | ],
32 | "repository": {
33 | "type": "git",
34 | "url": "https://github.com/emaphp/handlebars-template-loader.git"
35 | },
36 | "dependencies": {
37 | "fastparse": "^1.1.1",
38 | "loader-utils": "^0.2.15"
39 | },
40 | "keywords": [
41 | "handlebars",
42 | "template",
43 | "webpack",
44 | "loader"
45 | ],
46 | "readmeFilename": "README.md",
47 | "devDependencies": {
48 | "chai": "^3.5.0",
49 | "chai-string": "^1.2.0",
50 | "istanbul": "^0.4.4",
51 | "mocha": "^2.5.3"
52 | },
53 | "scripts": {
54 | "test": "mocha",
55 | "cover": "istanbul cover node_modules/mocha/bin/_mocha"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/runtime/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('handlebars/runtime');
--------------------------------------------------------------------------------
/test/attributeTest.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var chai = require('chai');
3 | var assert = chai.assert;
4 | chai.use(require('chai-string'));
5 |
6 | var attributeParser = require('../lib/attributeParser');
7 | var attributes = ['img:src', 'link:href'];
8 |
9 | // Helper to test the attributeParser.
10 | function testMatch(name, html, result) {
11 | it('should parse ' + name, function() {
12 | var parsed = attributeParser(html, function(tag, attr) {
13 | return attributes.indexOf(tag + ':' + attr) != -1;
14 | }, 'ATTRIBUTE', '/asdf/');
15 |
16 | // We are only interested in the `value` property of `matches`.
17 | var values = parsed.matches.map(function(match) { return match.value; });
18 | assert.deepEqual(values, result);
19 | });
20 | }
21 |
22 | // Helper to test the replaceMatches function.
23 | function replaceMatch(html) {
24 | return attributeParser(html, function(tag, attr) {
25 | return attributes.indexOf(tag + ':' + attr) != -1;
26 | }, 'ATTRIBUTE').replaceMatches(html);
27 | }
28 |
29 | // Testcases based on https://github.com/webpack/html-loader/blob/master/test/parserTest.js
30 | describe('attribute parser', function() {
31 | testMatch('normal', 'Text ', ['image.png', 'image2.png']);
32 | testMatch('single-quotes', "Text ", ['image.png', 'image2.png']);
33 | testMatch('whitespace', 'T ex t ', ['image.png', 'image2.png']);
34 | testMatch('whitespace2', 'Text < img src="image.png" >', []);
35 | testMatch('wrong <', 'Text < ', ['image.png']);
36 | testMatch("wrong >", 'Text > ', ['image.png']);
37 | testMatch('no quot', ' ', ['image.png']);
38 | testMatch('first tag', ' ', ['image.png']);
39 | testMatch('comment', '', []);
40 | testMatch('comment2', '', []);
41 | testMatch('comment3', ' -->', []);
42 | testMatch('comment4', ' -->', ['image.png']);
43 | testMatch('tags', ' ', ['image.png', 'style.css']);
44 | testMatch('cdata', ']]> ', ['image2.png']);
45 | testMatch('doctype', ' ', ['image.png']);
46 |
47 | it('should replace image paths', function() {
48 | var html = ' ';
49 | var result = replaceMatch(html);
50 | assert.startsWith(result, ' ';
55 | var result = replaceMatch(html);
56 | assert.endsWith(result, '____#asdf">');
57 | });
58 |
59 | it('should not replace urls', function() {
60 | var html = ' ';
61 | var result = replaceMatch(html);
62 | assert.equal(result, html);
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/test/lib/WebpackLoaderMock.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 |
4 | function WebpackLoaderMock (options) {
5 | this.context = options.context || '';
6 | this.query = options.query;
7 | this.options = options.options || {};
8 | this.resource = options.resource;
9 | this._asyncCallback = options.async;
10 | this._resolveStubs = options.resolveStubs || {};
11 | }
12 |
13 | WebpackLoaderMock.prototype.async = function () {
14 | return this._asyncCallback;
15 | };
16 |
17 | module.exports = WebpackLoaderMock;
18 |
--------------------------------------------------------------------------------
/test/lib/loadOutput.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 |
4 | function loadOutput(outputPath) {
5 | return fs.readFileSync(path.join(path.dirname(__dirname), 'templates/output', outputPath))
6 | .toString()
7 | .replace(/%%LOADER%%/g, require.resolve('../../file-loader.js'));
8 | }
9 |
10 | module.exports = loadOutput;
11 |
--------------------------------------------------------------------------------
/test/lib/loadTemplate.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 |
4 | function loadTemplate(templatePath) {
5 | return fs.readFileSync(path.join(path.dirname(__dirname), 'templates', templatePath)).toString();
6 | }
7 |
8 | module.exports = loadTemplate;
9 |
--------------------------------------------------------------------------------
/test/lib/removeFirstline.js:
--------------------------------------------------------------------------------
1 | function removeFirstline(str) {
2 | return str.substr(str.indexOf("\n") + 1);
3 | }
4 |
5 | module.exports = removeFirstline;
6 |
--------------------------------------------------------------------------------
/test/loaderTest.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var chai = require('chai');
4 | var assert = chai.assert;
5 | chai.use(require('chai-string'));
6 |
7 | var loader = require('../');
8 | var WebpackLoaderMock = require('./lib/WebpackLoaderMock');
9 | var loadTemplate = require('./lib/loadTemplate');
10 | var loadOutput = require('./lib/loadOutput');
11 | var removeFirstline = require('./lib/removeFirstline');
12 |
13 | function testTemplate(loader, template, options, testFn) {
14 | loader.call(new WebpackLoaderMock({
15 | query: options.query,
16 | resource: path.join(__dirname, 'templates', template),
17 | options: options.options,
18 | async: function(err, source) {
19 | testFn(source);
20 | }
21 | }), loadTemplate(template));
22 | }
23 |
24 | describe('loader', function() {
25 | it('should load simple handlebars template', function(done) {
26 | testTemplate(loader, 'simple.html', {}, function(output) {
27 | // Copy and paste the result of `console.log(output)` to templates/output/simple.txt
28 | assert.equal(removeFirstline(output), loadOutput('simple.txt').trimRight());
29 | done();
30 | });
31 | });
32 |
33 | it('should prepend html comment', function(done) {
34 | testTemplate(loader, 'simple.html', {
35 | query: {
36 | prependFilenameComment: __dirname
37 | }
38 | }, function(output) {
39 | assert.equal(removeFirstline(output), loadOutput('simple-with-comment.txt').trimRight());
40 | done();
41 | });
42 | });
43 |
44 | it('should be possible to require a template', function(done) {
45 | testTemplate(loader, 'require.html', {}, function(output) {
46 | assert.equal(removeFirstline(output), loadOutput('require.txt').trimRight());
47 | done();
48 | });
49 | });
50 |
51 | it('should be possible to include a template', function(done) {
52 | testTemplate(loader, 'include.html', {}, function(output) {
53 | assert.equal(removeFirstline(output), loadOutput('include.txt').trimRight());
54 | done();
55 | });
56 | });
57 |
58 | it('should require an image', function(done) {
59 | testTemplate(loader, 'image.html', {}, function(output) {
60 | assert.equal(removeFirstline(output), loadOutput('image.txt').trimRight());
61 | done();
62 | });
63 | });
64 |
65 | it('should require given custom attributes', function(done) {
66 | testTemplate(loader, 'custom-attributes.html', {
67 | query: {
68 | attributes: ['img:src', 'link:href']
69 | }
70 | }, function(output) {
71 | assert.equal(removeFirstline(output), loadOutput('custom-attributes.txt').trimRight());
72 | done();
73 | });
74 | });
75 |
76 | it('should not parse an absolute image without root option given', function(done) {
77 | testTemplate(loader, 'absolute-image.html', {}, function(output) {
78 | assert.equal(removeFirstline(output), loadOutput('absolute-image.txt').trimRight());
79 | done();
80 | });
81 | });
82 |
83 | it('should parse an absolute image if root option is given', function(done) {
84 | testTemplate(loader, 'absolute-image.html', {
85 | query: {
86 | root: '/bar'
87 | }
88 | }, function(output) {
89 | assert.equal(removeFirstline(output), loadOutput('absolute-image-with-root.txt').trimRight());
90 | done();
91 | });
92 | });
93 |
94 | it('should leave dynamic attribute unaltered', function(done) {
95 | testTemplate(loader, 'dynamic-attribute.html', {
96 | query: {}
97 | }, function(output) {
98 | assert.equal(removeFirstline(output), loadOutput('dynamic-attribute.txt').trimRight());
99 | done();
100 | });
101 | });
102 |
103 | it('should ignore root option if parseDynamicRoutes is not specified', function(done) {
104 | testTemplate(loader, 'dynamic-attribute-with-root.html', {
105 | query: {
106 | root: '/bar'
107 | }
108 | }, function(output) {
109 | assert.equal(removeFirstline(output), loadOutput('dynamic-attribute-with-root.txt').trimRight());
110 | done();
111 | });
112 | });
113 |
114 | it('should modify dynamic routes', function(done) {
115 | testTemplate(loader, 'dynamic-attribute-with-parseDynamicRoutes.html', {
116 | query: {
117 | root: '/bar',
118 | parseDynamicRoutes: true
119 | }
120 | }, function(output) {
121 | assert.equal(removeFirstline(output), loadOutput('dynamic-attribute-with-parseDynamicRoutes.txt').trimRight());
122 | done();
123 | });
124 | });
125 |
126 | it('should support compilation options', function(done) {
127 | // srcName makes the loader return a {code, map} object literal
128 | testTemplate(loader, 'compilation-options.html', {
129 | query: {
130 | srcName: 'foo.js'
131 | }
132 | }, function(output) {
133 | assert.equal(removeFirstline(output), loadOutput('compilation-options.txt').trimRight());
134 | done();
135 | });
136 | });
137 | });
138 |
--------------------------------------------------------------------------------
/test/macroTest.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var chai = require('chai');
4 | var assert = chai.assert;
5 | chai.use(require('chai-string'));
6 |
7 | var loader = require('../');
8 | var WebpackLoaderMock = require('./lib/WebpackLoaderMock');
9 | var loadTemplate = require('./lib/loadTemplate');
10 | var loadOutput = require('./lib/loadOutput');
11 | var removeFirstline = require('./lib/removeFirstline');
12 |
13 | function testTemplate(loader, template, options, testFn) {
14 | loader.call(new WebpackLoaderMock({
15 | query: options.query,
16 | resource: path.join(__dirname, 'templates', template),
17 | options: options.options,
18 | async: function(err, source) {
19 | testFn(source);
20 | }
21 | }), loadTemplate(template));
22 | }
23 |
24 | describe('macro', function() {
25 | it('should be parsed', function(done) {
26 | testTemplate(loader, 'custom-macro.html', {
27 | options: {
28 | macros: {
29 | foo: function() {
30 | return '"bar
"';
31 | }
32 | }
33 | }
34 | }, function(output) {
35 | assert.equal(removeFirstline(output), loadOutput('custom-macro.txt').trimRight());
36 | done();
37 | });
38 | });
39 |
40 | it('should receive boolean arguments', function(done) {
41 | testTemplate(loader, 'macro_boolean_args.html', {
42 | options: {
43 | macros: {
44 | bool_test: function(arg) {
45 | assert.typeOf(arg, 'boolean');
46 | return arg ? '"TRUE
"' : '"FALSE
"';
47 | }
48 | }
49 | }
50 | }, function(output) {
51 | assert.equal(removeFirstline(output), loadOutput('macro_boolean_args.txt').trimRight());
52 | done();
53 | });
54 | });
55 |
56 | it('should receive numeric arguments', function(done) {
57 | testTemplate(loader, 'macro_numeric_args.html', {
58 | options: {
59 | macros: {
60 | num_test: function(arg) {
61 | assert.typeOf(arg, 'number');
62 | return '"' + arg + '
"';
63 | }
64 | }
65 | }
66 | }, function(output) {
67 | assert.equal(removeFirstline(output), loadOutput('macro_numeric_args.txt').trimRight());
68 | done();
69 | });
70 | });
71 |
72 | it('should receive string arguments', function(done) {
73 | testTemplate(loader, 'macro_string_args.html', {
74 | options: {
75 | macros: {
76 | str_test: function(arg) {
77 | assert.typeOf(arg, 'string');
78 | return '"' + arg.toUpperCase() + '
"';
79 | }
80 | }
81 | }
82 | }, function(output) {
83 | assert.equal(removeFirstline(output), loadOutput('macro_string_args.txt').trimRight());
84 | done();
85 | });
86 | });
87 |
88 | it('should receive argument list', function(done) {
89 | testTemplate(loader, 'macro_argument_list.html', {
90 | options: {
91 | macros: {
92 | numbers: function(first, second, third) {
93 | assert.typeOf(first, 'number');
94 | assert.typeOf(second, 'number');
95 | assert.typeOf(third, 'number');
96 | var output = '';
97 | for (var i = 0; i < 3; i++) {
98 | output += '' + arguments[i] + '
';
99 | }
100 | return '"' + output + '"';
101 | },
102 |
103 | booleans: function(first, second, third) {
104 | assert.typeOf(first, 'boolean');
105 | assert.typeOf(second, 'boolean');
106 | assert.typeOf(third, 'boolean');
107 | var output = '';
108 | for (var i = 0; i < 3; i++) {
109 | output += '' + (arguments[i] ? 'TRUE' : 'FALSE') + '
';
110 | }
111 | return '"' + output + '"';
112 | },
113 |
114 | strings: function(first, second, third) {
115 | assert.typeOf(first, 'string');
116 | assert.typeOf(second, 'string');
117 | assert.typeOf(third, 'string');
118 | var output = '';
119 | for (var i = 0; i < 4; i++) {
120 | output += '' + arguments[i].toLowerCase().replace(/"/g, "\\\"") + '
';
121 | }
122 | return '"' + output + '"';
123 | },
124 |
125 | mixed: function() {
126 | assert.equal(arguments.length, 6);
127 | assert.typeOf(arguments[0], 'boolean');
128 | assert.typeOf(arguments[1], 'number');
129 | assert.typeOf(arguments[2], 'string');
130 | assert.typeOf(arguments[3], 'boolean');
131 | assert.typeOf(arguments[4], 'string');
132 | assert.typeOf(arguments[5], 'number');
133 |
134 | var output = '';
135 |
136 | for (var i = 0; i < 6; i++) {
137 | var type = typeof(arguments[i]);
138 |
139 | if (type == 'string') {
140 | output += '' + arguments[i].toLowerCase().replace(/"/g, "\\\"") + '
';
141 | } else if (type == 'number') {
142 | output += '' + arguments[i] + '
';
143 | } else if (type == 'boolean') {
144 | output += '' + (arguments[i] ? 'TRUE' : 'FALSE') + '
';
145 | }
146 | }
147 |
148 | return '"' + output + '"';
149 | }
150 | }
151 | }
152 | }, function(output) {
153 | assert.equal(removeFirstline(output), loadOutput('macro_argument_list.txt').trimRight());
154 | done();
155 | });
156 | });
157 |
158 | it('should not be evaluated', function(done) {
159 | testTemplate(loader, 'macro.html', {
160 | query: {
161 | parseMacros: false
162 | }
163 | }, function(output) {
164 | assert.equal(removeFirstline(output), loadOutput('disabled-macro.txt').trimRight());
165 | done();
166 | });
167 | });
168 |
169 | it('should be replaced when escaped', function(done) {
170 | testTemplate(loader, 'macro_escaped.html', {
171 | options: {
172 | macros: {
173 | unescaped: function() {
174 | return '"Ok
"';
175 | }
176 | }
177 | }
178 | }, function(output) {
179 | assert.equal(removeFirstline(output), loadOutput('macro_escaped.txt').trimRight());
180 | done();
181 | });
182 | });
183 |
184 | it('should replace repeat string macros', function(done) {
185 | testTemplate(loader, 'macro_repeat.html', {
186 | options: {}
187 | }, function(output) {
188 | assert.equal(removeFirstline(output), loadOutput('macro_repeat.txt').trimRight());
189 | done();
190 | });
191 | });
192 | });
193 |
--------------------------------------------------------------------------------
/test/templates/absolute-image.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/templates/compilation-options.html:
--------------------------------------------------------------------------------
1 | {name}@repeat('test')
2 |
--------------------------------------------------------------------------------
/test/templates/custom-attributes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/test/templates/custom-macro.html:
--------------------------------------------------------------------------------
1 | What, @foo(3)
2 |
--------------------------------------------------------------------------------
/test/templates/dynamic-attribute-with-parseDynamicRoutes.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/templates/dynamic-attribute-with-root.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/templates/dynamic-attribute.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/templates/image.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/test/templates/include.html:
--------------------------------------------------------------------------------
1 | Hello, @include('./bar.html')
2 |
--------------------------------------------------------------------------------
/test/templates/macro.html:
--------------------------------------------------------------------------------
1 | Hi.
2 | @repeat(' ', 3)
3 | @repeat("\n", 2)
4 |
--------------------------------------------------------------------------------
/test/templates/macro_argument_list.html:
--------------------------------------------------------------------------------
1 | @numbers(1 3.534 000000000000000005)
2 | @booleans(true false true)
3 | @strings('Lorem' "Ipsum" '"Sit"' "'Amet'")
4 | @mixed(false 423.429 "Hello world" true 'Lorem' 00000000003)
--------------------------------------------------------------------------------
/test/templates/macro_boolean_args.html:
--------------------------------------------------------------------------------
1 | @bool_test(true)
2 |
3 | @bool_test(false)
--------------------------------------------------------------------------------
/test/templates/macro_escaped.html:
--------------------------------------------------------------------------------
1 | \@escaped(1 2 3)
2 | @unescaped()
3 | \@unescaped()
--------------------------------------------------------------------------------
/test/templates/macro_numeric_args.html:
--------------------------------------------------------------------------------
1 | @num_test(5)
2 |
3 | @num_test(6.71)
4 |
5 | @num_test(00000000000000002)
--------------------------------------------------------------------------------
/test/templates/macro_repeat.html:
--------------------------------------------------------------------------------
1 | Hello
2 | @repeat(' ', 3)
3 | @repeat('test')
4 | Hola
5 | @repeat('\n', 2)
6 |
--------------------------------------------------------------------------------
/test/templates/macro_string_args.html:
--------------------------------------------------------------------------------
1 | @str_test('Lorem ipsum')
2 |
3 | @str_test("Sit amet")
--------------------------------------------------------------------------------
/test/templates/output/absolute-image-with-root.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return " \n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/absolute-image.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return " \n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/compilation-options.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "{name}" + 'test' + " \n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/custom-attributes.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return " \n \n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/custom-macro.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "What, " + "bar
" + "\n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/disabled-macro.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "Hi.\n@repeat(' ', 3)\n@repeat(\"\\n\", 2)\n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/dynamic-attribute-with-parseDynamicRoutes.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
3 |
4 | return " \n ";
11 | },"useData":true});
12 |
--------------------------------------------------------------------------------
/test/templates/output/dynamic-attribute-with-root.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
3 |
4 | return " \n ";
11 | },"useData":true});
12 |
--------------------------------------------------------------------------------
/test/templates/output/dynamic-attribute.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
3 |
4 | return " \n ";
11 | },"useData":true});
12 |
--------------------------------------------------------------------------------
/test/templates/output/image.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return " \n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/include.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "Hello, " + require("./bar.html") + "\n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/macro_argument_list.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "" + "1
3.534
5
" + "\n" + "TRUE
FALSE
TRUE
" + "\n" + "lorem
ipsum
\"sit\"
'amet'
" + "\n" + "FALSE
423.429
hello world
TRUE
lorem
3
" + "";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/macro_boolean_args.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "" + "TRUE
" + "\n \n" + "FALSE
" + "";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/macro_escaped.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "@escaped(1 2 3)\n" + "Ok
" + "\n\\@unescaped()";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/macro_numeric_args.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "" + "5
" + "\n \n" + "6.71
" + "\n \n" + "2
" + "";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/macro_repeat.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "Hello
\n" + ' ' + "\n" + 'test' + "\nHola \n" + '\n\n' + "\n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/macro_string_args.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "" + "LOREM IPSUM
" + "\n \n" + "SIT AMET
" + "";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/require.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
2 | return "Hello, " + require("./bar.html").apply(null, Array.prototype.slice.call(arguments, arguments.length > 6)) + "\n";
3 | },"useData":true});
4 |
--------------------------------------------------------------------------------
/test/templates/output/simple-with-comment.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"1":function(container,depth0,helpers,partials,data) {
2 | var helper;
3 |
4 | return ""
5 | + container.escapeExpression(((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"description","hash":{},"data":data}) : helper)))
6 | + "
\n";
7 | },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
8 | var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
9 |
10 | return "\n\n"
11 | + container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
12 | + " \nA simple template
\n\n"
13 | + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.description : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
14 | },"useData":true});
15 |
--------------------------------------------------------------------------------
/test/templates/output/simple.txt:
--------------------------------------------------------------------------------
1 | module.exports = (Handlebars['default'] || Handlebars).template({"1":function(container,depth0,helpers,partials,data) {
2 | var helper;
3 |
4 | return ""
5 | + container.escapeExpression(((helper = (helper = helpers.description || (depth0 != null ? depth0.description : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"description","hash":{},"data":data}) : helper)))
6 | + "
\n";
7 | },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
8 | var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
9 |
10 | return ""
11 | + container.escapeExpression(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
12 | + " \nA simple template
\n\n"
13 | + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.description : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
14 | },"useData":true});
15 |
--------------------------------------------------------------------------------
/test/templates/require.html:
--------------------------------------------------------------------------------
1 | Hello, @require('./bar.html')
2 |
--------------------------------------------------------------------------------
/test/templates/simple.html:
--------------------------------------------------------------------------------
1 | {{title}}
2 | A simple template
3 |
4 | {{#if description}}
5 | {{description}}
6 | {{/if}}
7 |
--------------------------------------------------------------------------------