2

Premise

We've got handlebars running in a backend nodejs application for templating various messages that get sent.

Handlebars.compile throws this exception (when compiling templates from partials)

Error: You must pass a string or Handlebars AST to Handlebars.compile. You passed <html>
<head>
... extremely long markup
at Object.compile (/Users/guscrawford/rollick-management-console/deployd/node_modules/handlebars/dist/cjs/handlebars/compiler/compiler.js:501:11)
at HandlebarsEnvironment.hb.compile (/Users/guscrawford/rollick-management-console/deployd/node_modules/handlebars/dist/cjs/handlebars.js:39:40)
at Object.invokePartialWrapper [as invokePartial] (/Users/guscrawford/rollick-management-console/deployd/node_modules/handlebars/dist/cjs/handlebars/runtime.js:71:44)
... additional stack trace through to dpd, bluebird etc.

Cannot replicate through isolation

Go ahead and try setting up a scrap project: yarn add handlebars handlebars-helper-ternary handlebars-helpers handlebars.numeral

Then run this script in nodejs:

const   handlebars = require('handlebars'),
        numeralHelper = require('handlebars.numeral'),    
        ternaryHelper = require('handlebars-helper-ternary'),
        helpers = require('handlebars-helpers')({
        handlebars: handlebars
    });
console.log(`Testing...`);
const base = `
<html>
    <body style="font-family:'Segoe UI', Tahoma, Geneva, Verdana,     'sans-serif'; font-size: larger;">
    {{>@partial-block }}
    <td style="text-align: center; padding: 24px;">
    Copyright 2018 My Company, Inc. All rights reserved.

    </body>
</html>

`;
const inner = `
{{#>base}}
    {{subscriber.name}},

    {{member.name}} has received a notifier from {{subscriber.name}}.    

    Click the link below to review!. 
    <a href='{{link}}'>Go!</a>

    Thank you,
    My Company
{{/base}}
`;
numeralHelper.registerHelpers(handlebars);
handlebars.registerHelper('ternary', ternaryHelper);
handlebars.registerHelper("moduloIf", function (index_count, mod, block)     {

    if (index_count > 0 && parseInt(index_count) % (mod) === 0) {
        return block.fn(this);
    } else {
        return block.inverse(this);
    }
});

handlebars.registerHelper("substitute", function(a, options) {
  try {
    function index(obj,i) { return obj ? obj[i] : {} }
    let data = a.split('.').reduce(index, this);
    if (data && typeof data === 'string') return data;
    else return options.fn(this);
  } catch (e) {
    console.log('substitute helper:' + e);
  }
});
handlebars.registerPartial('base',base)
var output = handlebars.compile(inner)({name:'Gus'});
console.log('Output:');
console.log(output)

Further consideration

In actuality we have the handlebars require wrapped in another module with code run against the handlebars instance as illustrated in the sample script. We're exporting the handlebars instance.

Gus Crawford
  • 1,017
  • 8
  • 18

2 Answers2

11

The string was a Buffer

Despite logging typeof the template string I was passing as a string, the output of readFileAsync without passing an encoding is a raw node Buffer.

Duh

Gus Crawford
  • 1,017
  • 8
  • 18
  • As I told you, it wasn't a string, a Buffer is not a string. Hence my answer was correct, the error occured on an part of code you didn't show, and I told you what your error was. – Marcos Casagrande Jun 04 '18 at 20:19
  • And it's exactly an object that implements a `toString` method containing the raw string, as I described. – Marcos Casagrande Jun 04 '18 at 20:31
2

The error is clear, you're passing something that isn't a string or AST.

This is the only way handlebars throws that error.

if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
    throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
}

You're probably passing an object, with a toString method, that's why you see:

You passed <html>
<head>
... extremely long markup

const input = {
  toString() {
    return `<html>
    <head>`;
  }
}
console.log('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
Marcos Casagrande
  • 37,983
  • 8
  • 84
  • 98
  • I've logged a `typeof handlebars.compile(inner)` and it is certainly a string – Gus Crawford Jun 04 '18 at 12:54
  • Unless it was overridden, most `toString()` invocations will output `[Object]` no? – Gus Crawford Jun 04 '18 at 12:55
  • Are you sure is there where it triggers the error? I'm not seeing the full stacktrace, so I would say you're wrong. Yes, unless overriden, it would be `[Object object]` – Marcos Casagrande Jun 04 '18 at 12:55
  • One way to debug, is modify that piece of code that I linked, in `node_modules/handlebars...` put a `console.log` and see exactly what you're sending. But the error is clear, you're sending something that is 100% not a string. – Marcos Casagrande Jun 04 '18 at 12:57
  • 1. 100% the `console.log` output of the template passed to `compile` is a string. 2. I realize I removed the markup for brevity but the stack trace clearly begins in `Object.compile > HandlebarsEnvironment.hb.compile > Object.invokePartialWrapper > Object.eval (functions in the stack outside of this I'm pretty confident are not in the scope of handlebars)` – Gus Crawford Jun 04 '18 at 13:01
  • Can you replicate the error? And give me a snippet that it will trigger it? – Marcos Casagrande Jun 04 '18 at 13:03
  • I couldn't replicate it in an isolated package by mirroring my work code-base as closely as possible. I'm going to further add in dpd to my scrap project and run the script within that context as in the source of the problem but I've not done so yet. – Gus Crawford Jun 04 '18 at 13:06
  • You're probably passing something that isn't a string to a partial. – Marcos Casagrande Jun 04 '18 at 13:07
  • It's something to do with the use of the partials, the other templates without the partial references don't have this problem compiling, and the markup in the stack message is different than the template I logged (the template has the partial references, the one from the stack has the inner content place holder reference in it, I would expect the output to be as the example from the working isolation – Gus Crawford Jun 04 '18 at 13:08
  • Passing something not a string to a partial... elaborate on that? How come the exact same templates from my source worked in my scrap project? – Gus Crawford Jun 04 '18 at 13:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172414/discussion-between-gus-crawford-and-marcos-casagrande). – Gus Crawford Jun 04 '18 at 13:10
  • When you register a partial, you're sending something that isn't a string. But the error remains the same, look in your code because you're passing wrong input, and that doesn't happen in the code you gave us. – Marcos Casagrande Jun 04 '18 at 13:11