Mustache templates

A Dart library to parse and render mustache templates.

See the mustache manual for detailed usage information.

This library passes all mustache specification tests.

Example usage

  const source = '''
{{# names }}
  <div>{{ lastname }}, {{ firstname }}</div>
{{/ names }}
{{^ names }}
  <div>No names.</div>
{{/ names }}
{{! I am a comment. }}
''';

  final template = Template(source, name: 'template-filename.html');

  final String output = template.renderString(<String, Object>{
    'names': <Map<String, String>>[
      <String, String>{'firstname': 'Greg', 'lastname': 'Lowe'},
      <String, String>{'firstname': 'Bob', 'lastname': 'Johnson'},
    ],
  });

A template is parsed when it is created, after parsing it can be rendered any number of times with different values. A TemplateException is thrown if there is a problem parsing or rendering the template.

The Template contstructor allows passing a name, this name will be used in error messages. When working with a number of templates, it is important to pass a name so that the error messages specify which template caused the error.

By default all output from {{variable}} tags is html escaped, this behaviour can be changed by passing htmlEscapeValues : false to the Template constructor. You can also use a {{{triple mustache}}} tag, or a unescaped variable tag {{&unescaped}}, the output from these tags is not escaped.

Differences between strict mode and lenient mode.

Strict mode (default)

  • Tag names may only contain the characters a-z, A-Z, 0-9, underscore, period and minus. Other characters in tags will cause a TemplateException to be thrown during parsing.

  • During rendering, if no map key or object member which matches the tag name is found, then a TemplateException will be thrown.

Lenient mode

  • Tag names may use any characters.
  • During rendering, if no map key or object member which matches the tag name is found, then silently ignore and output nothing.

Nested paths

final template = Template('{{ author.name }}');
final String output = template.renderString(<String, Object>{
  'author': <String, String>{'name': 'Greg Lowe'},
});

Partials - example usage

final partial = Template('{{ foo }}', name: 'partial');

Template? resolver(String name) {
  if (name == 'partial-name') {
    // Name of partial tag.
    return partial;
  }
  return null;
}

final template = Template('{{> partial-name }}', partialResolver: resolver);

final String output = template.renderString(<String, String>{'foo': 'bar'}); // bar

Lambdas - example usage

final template = Template('{{# foo }}inner{{/ foo }}');
Object lambda(Object? _) => 'bar';
final String output = template.renderString(<String, Object>{'foo': lambda}); // bar
final template = Template('{{# foo }}hidden{{/ foo }}');
Object lambda(Object? _) => 'shown';
final String output = template.renderString(<String, Object>{'foo': lambda}); // shown
final template = Template('{{# foo }}oi{{/ foo }}');
Object lambda(LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
final String output = template.renderString(<String, Object>{'foo': lambda}); // <b>OI</b>
final template = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda(LambdaContext ctx) => '<b>${ctx.renderString().toUpperCase()}</b>';
final String output = template.renderString(<String, Object>{
  'foo': lambda,
  'bar': 'pub',
}); // <b>PUB</b>

In the following example LambdaContext.renderSource(source) re-parses the source string in the current context, this is the default behaviour in many mustache implementations. Since re-parsing the content is slow, and often not required, this library makes this step optional.

final template = Template('{{# foo }}{{bar}}{{/ foo }}');
Object lambda(LambdaContext ctx) => ctx.renderSource('${ctx.source} {{cmd}}');
final String output = template.renderString(<String, Object>{
  'foo': lambda,
  'bar': 'pub',
  'cmd': 'build',
}); // pub build