Template system based roughly on Jinja2, designed with a focus on security, simplicity, rendering speed, and minimal clientside footprint.
Features:
- Small feature set — focused on simple value replacement and filters.
- Small runtime — the JavaScript needed to render compiled templates in the client is about 1.1KB, uncompressed.
- Small precompiled size — compiled templates are approximately the same size as the templates themselves.
- Contextual escaping — anti-XSS escaping policies are applied automatically based on the HTML context.
Non-Features:
- Compiling templates in the client — templates must be precompiled in nodejs.
- Complex template programming — no support for loops, conditionals, tags, inheritance, etc.
- Protection against malicious templates — it is assumed that templates are created by trusted users only.
Compile and render a template in nodejs:
// Compile
var compile = require('micro-html-template').compile;
var precompiled = compile("<img src='https://example.com?myname={{user.name}}'>");
// Render
var render = require('micro-html-template-runtime').render;
var env = {user: {name: 'Jar Jar B.'}};
var htmlContent = render(precompiled, env);Render the template in the client:
<div id='template1'></div>
<script src='dist/micro-html-template-runtime.min.js'></script>
<script>
var precompiled = '...'; // precompiled template string from nodejs
var env = {user: {name: 'Jar Jar B.'}};
var htmlContent = microHtmlTemplate.render(precompiled, env);
document.getElementById('template1').innerHTML = htmlContent;
</script>The result:
<div id='template1'>
<img src='https://example.com?myname=Jar%20Jar%20B.'>
</div>See the tests for more examples.
This libarary is designed for applications where templates are created only by trusted users, but data used to render the templates is untrusted. Template data will be automatically protected against XSS by a combination of HTML and URI component escaping, depending on the context.
Templates must be valid HTML — macros may only appear in:
- text nodes
- quoted attribute values
Macros in unsafe contexts are ignored — macros may not appear in:
<script>tags<style>tagsstyleattribute valueson*event attribute values
Ok:
<!-- Text nodes are safe contexts (except in 'style' and 'script' tags). -->
<div>Hello, {{user.name}}!</div><!-- Quoted attribute values are safe contexts (except for 'style' and 'on*'). -->
<img height='{{height}}px'>Unsafe:
<!-- Template is not valid HTML. (Behavior is undefined.) -->
<{{tag.name}} src='http://example.com'><!-- Macro in script tag (passed through verbatim). -->
<script>var x = {{foo.x}};</script><!-- Macro in style tag (passed through verbatim). -->
<style>html {background:{{colors.foo}};}</style><!-- Macro in style attribute (passed through verbatim). -->
<div style='background:{{colors.foo}};'>hello world</div><!-- Macro in on* attribute (passed through verbatim). -->
<div onclick='alert("hello {{user.name}}")'>hello world</div>The results of all macro replacements are automatically HTML escaped. However,
certain attributes are interpreted as URIs by the browser (the src attribute
of an <iframe>, for example). Macro replacements in these attributes are URI
encoded (eg. encodeURIComponent()) and then HTML escaped.
<!-- Replacements in text nodes are just HTML escaped. -->
<div>hello {{user.name}}</div><!-- Replacements in regular attributes are just HTML escaped. -->
<img data-foo='https://example.com?myname={{user.name}}'><!-- Replacements in URI type attributes are both URI encoded and HTML escaped. -->
<img src='https://example.com?myname={{user.name}}'>Automatic contextual escaping can be disabled for individual macros: see Filters below.
The values used in macro expansion are provided as literals or via the env
object (passed as an argument to render).
<!-- JSON string and number literals are values that can be used in macros. -->
<ul>
<li>i = {{"√-͞1"}}</li>
<li>? = {{42}}</li>
<li>π = {{3.14}}</li>
<li>ħ = {{6.58e-16}}</li>
</ul><!-- Variable names refer to properties of the env object. -->
<div>hello {{name}}</div><!-- Use the dot operator to access nested values. -->
<div>hello {{user.name}}</div><!-- Square brackets work, too. -->
<div>hello {{user["name"]}}</div><!-- Variables can be used inside the square brackets. -->
<div>hello {{user[prop]}}</div><!-- Square brackets are also used for array access. -->
<div>hello {{users[0].name}}</div>Filters are functions that are applied to the replacement text. Filters are expressed as a pipeline:
<img src='https://example.com?q={{query | filter1 | filter2}}'>Filters beginning with a . character denote method invocation:
<img src='https://example.com?q={{query | .toUpperCase}}'>Filters and methods may take arguments:
<img src='https://example.com?q={{query | doit("foo", bar.baz) | .substr(42)}}'>The following built-in filters are included:
id— The identity filter, does nothing.uri— Escapes input for URI component context.html— Escapes input for HTML context.raw— Applied at the end of the pipeline this filter disables auto-escaping.
Note that the uri and html filters are automatically applied as needed to
prevent XSS. However, it may sometimes make sense to use them in macros, for
instance to escape URI components in an attribute that is not automatically
interpreted by the browser as a URI type:
<!-- The 'data-myurl' attribute normally would not be considered a URI context
so URI component escaping must be specified by adding the uri filter. -->
<div data-myurl='https://example.com?q={{query | uri}}'></div>Or if you need to double-escape a URI component for some reason:
<!-- The 'href' attribute is already a URI type, so this will be double-escaped. -->
<a href='https://example.com?q={{query | uri}}'>Query</a>Or when using the raw filter on trusted data:
<!-- Without "raw" the macro would be URI component escaped, which we don't want.
Using the "html" filter preserves the HTML escaping, though, which we do want. -->
<img src='{{impressionUrl | html | raw}}'>Filters can be added to the runtime in nodejs:
runtime = require('micro-html-template-runtime');
runtime.filters.uppercase = function(val) {
return val.toUpperCase();
};or in the client:
<script>
microHtmlTemplate.filters.uppercase = function(val) {
return val.toUpperCase();
};
</script>Filters may accept additional arguments:
<script>
microHtmlTemplate.filters.wrap = function(val, start, end) {
return start + val + end;
};
</script>Pass the additional arguments to the filter in the template:
<img src='https://example.com?q={{query | wrap("[", "]")}}'>To include the macro start delimiter {{ itself in a template it must be
escaped by preceeding it with another start delimiter, like this: {{{{.
<!-- Start delimiter escaped with '{{{{'. -->
<div>{{{{user.name}} = '{{user.name}}'</div><!-- The rendered template. -->
<div>{{user.name}} = 'Jar Jar B.'</div># Install dependencies.
npm install# Compile parser, minify runtime, etc.
make# Run tests.
make testCopyright © 2017 Adzerk, Inc. Distributed under the Apache License, Version 2.0.