Demo page (Try in your browser!)
https://github.com/cho45/micro-template.js
micro-template is a template engine for JavaScript which is similar to embed.js.
This is inspired by John Resig's template but has more efficient features:
- Better error messages: shows line numbers in runtime errors
- Supports source maps: debugging is easier in Chrome, including for syntax errors
- Well tested: works on Node.js
- Escapes by default: all output is escaped for security
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>micro-template.js test</title>
</head>
<body>
<script type="application/x-template" id="tmpl1">
<div class='foobar' id="aaa">
<% if (isFoo) { %>
<%= foobar %>
<% } else { %>
<%= foobaz %>
<% } %>
<%=raw html %>
</div>
</script>
<script type="module">
import { extended as template } from 'https://cdn.jsdelivr.net/npm/micro-template@2.1.2/lib/micro-template.js';
console.log('micro-template.js loaded', template);
console.log('Template example:', template('tmpl1', { isFoo: true, foobar: "a", foobaz: "b" }));
let html = template('tmpl1', {
isFoo : true,
foobar : 'foobar!!',
foobaz : 'foobaz!!',
html : '<marquee>Helloooo</marquee>'
});
console.log(html);
</script>
</body>
</html>
npm install micro-template
import fs from 'node:fs';
import { template } from 'micro-template';
template.get = (id) => fs.readFileSync(`tmpl/${id}.tmpl`, 'utf-8');
const result = template('part1', {
foo : 'bar',
baz : 'piyo'
});
console.log(result)
<% … %>
: normal script part<%= … %>
: escaped html output part<%=raw …%>
: unescaped (almost dangerous) html output part
If the first argument of template matches /^[\w\-]+$/
, it is treated as id
of template. In this case, use document.getElementById(id).innerHTML
to get source.
Otherwise, the first argument is treated as source directly.
If the second argument is an Array, it is treated as a list of property names for the data object. In this case, the template function will be compiled with these property names as its local variables, and the compiled function itself will be returned (not executed). This allows you to precompile a template for repeated use with the same set of variable names.
If the second argument is an Object, the template will be rendered immediately using the object's properties as local variables.
By default, micro-template uses document.getElementById(id).innerHTML
to get the template source from the id.
To override this behavior, you can set a function to template.get
. For example, if your template files are in the tmpl/
directory:
import { template } from 'micro-template';
template.get = function (id) { return require('fs').readFileSync('tmpl/' + id + '.tmpl', 'utf-8') };
micro-template always expands data variables as local variables in the template function. The template API only supports two arguments: the template source/id and the data object (or an array of property names for precompilation). All keys of the data object are available as local variables in the template code.
If the second argument is an Array, it is treated as a list of property names for the data object. In this case, the template function will be compiled with these property names as its local variables, and the function itself will be returned (not executed). This allows you to precompile a template for repeated use with the same set of variable names.
For example:
const render = template('aaa <% foo %> bbb', ['foo']);
const result = render({ foo: 'bar' });
// result: 'aaa bar bbb'
You can access all properties of the data object directly as variables inside the template.
Note: The previous API that allowed calling template(tmpl)
to return a function is removed. Always use the two-argument form: template(tmpl, data)
or template(tmpl, [prop1, prop2, ...])
.
This package also provides extended
function which includes include
and wrapper
function. Of course, this feature can be used on browsers by just copying and pasting lib/micro-template.js
.
import { extended as template } from 'micro-template';
template('view1', { foo : 'bar' });
<!-- view1 -->
aaa
<% include('view2') %>
bbb
<!-- view2 -->
Hello, World!
import { extended as template } from 'micro-template';
template('view1', { foo : 'bar' });
<!-- view1 -->
<% wrapper('wrapper', function () { %>
<h1>Hello, World!</h1>
<% }) %>
<!-- wrapper -->
<!DOCTYPE html>
<title><%= foo %></title>
<body><%=raw content %></body>
online: benchmark on perf.link
node:
- node misc/benchmark.js
> node --expose-gc ./misc/benchmark.js
1 = 1 / 2 = 2 / 3 = Fizz / 4 = 4 / 5 = Buzz / 6 = Fizz / 7 = 7 / 8 = 8 / 9 = Fizz / 10 = Buzz / 11 = 11 / 12 = Fizz / 13 = 13 / 14 = 14 / 15 = FizzBuzz / 16 = 16 / 17 = 17 / 18 = Fizz / 19 = 19 / 20 = Buzz / 21 = Fizz / 22 = 22 / 23 = 23 / 24 = Fizz / 25 = Buzz / 26 = 26 / 27 = Fizz / 28 = 28 / 29 = 29 / 30 = FizzBuzz /
clk: ~3.05 GHz
cpu: Apple M1
runtime: node 20.10.0 (arm64-darwin)
benchmark avg (min … max) p75 / p99 (min … top 1%)
------------------------------------------------- -------------------------------
micro-template 20.72 µs/iter 19.75 µs █
(18.83 µs … 232.63 µs) 35.50 µs █
( 11.37 kb … 429.21 kb) 108.69 kb ▄█▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
micro-template pre compiled 19.64 µs/iter 19.65 µs █
(19.48 µs … 19.94 µs) 19.83 µs ▅ ▅▅ █▅▅▅▅▅ ▅
( 1.92 kb … 1.96 kb) 1.93 kb █▁▁██▁██████▁▁▁▁▁▁▁▁█
micro-template (not cached) 174.13 µs/iter 172.08 µs ▆█
(164.50 µs … 627.62 µs) 325.83 µs ██
( 4.38 kb … 639.55 kb) 140.32 kb ██▄▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
micro-template (template.variable) 21.01 µs/iter 20.13 µs █
(19.25 µs … 245.96 µs) 32.79 µs █
( 12.90 kb … 356.45 kb) 108.75 kb ██▆▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
ejs.compile (pre compiled) 438.36 µs/iter 443.67 µs ▄█▄
(418.00 µs … 665.54 µs) 489.46 µs ███▂
( 10.37 kb … 798.84 kb) 99.42 kb ▁▁██████▇▅▅▃▃▂▂▂▁▂▁▁▁
ejs.render 457.18 µs/iter 458.37 µs █
(439.75 µs … 659.54 µs) 520.92 µs
( 5.91 kb … 842.41 kb) 141.02 kb ▃▄██▇▆▄▄▃▃▂▂▂▂▂▁▁▁▁▁▁
John Resig's tmpl 240.02 µs/iter 240.67 µs
(228.29 µs … 436.46 µs) 277.63 µs ▅
( 14.08 kb … 438.89 kb) 126.56 kb ▃▅███▆▄▃▃▂▃▂▂▂▂▁▁▁▁▁▁
┌ ┐
micro-template ┤ 20.72 s
micro-template pre compiled ┤ 19.64 s
micro-template (not cached) ┤■■■■■■■■■■s ■ 174.13 µ
micro-template (template.variable) ┤ 21.01 s
ejs.compile (pre compiled) ┤■■■■■■■■■■■■■■■■■■■■ s■■■■■■■■■■■■ 438.36 µ
ejs.render ┤■■■■■■■■■■■■■■■■■■■■■s ■■■■■■■■■■■■ 457.18 µ
John Resig's tmpl ┤■■■■■■■■■■■■ s■■■■ 240.02 µ
└ ┘
summary
micro-template pre compiled
1.06x faster than micro-template
1.07x faster than micro-template (template.variable)
8.87x faster than micro-template (not cached)
12.22x faster than John Resig's tmpl
22.32x faster than ejs.compile (pre compiled)
23.28x faster than ejs.render
micro-template-serialize
is a CLI tool for precompiling multiple template files into a single ESM (ECMAScript Module) JavaScript file. This is especially useful for environments where dynamic function generation (such as with new Function()
) is not allowed, or for delivering precompiled templates to browsers or serverless environments.
- Security: Avoids the use of
new Function()
at runtime, which is often restricted in secure or serverless environments. - Performance: Templates are precompiled, so rendering is fast and does not require parsing or compiling templates at runtime.
- Convenience: Bundles multiple templates into a single importable module.
micro-template-serialize <input1.tmpl> <input2.tmpl> ... --output <output.js> [--root <dir>]
<input1.tmpl> ...
: List of template files to serialize.--output <output.js>
: Output JavaScript file (required).--root <dir>
: Root directory for template IDs (default: current directory).
micro-template-serialize test/data-test1.tmpl test/data-fizzbuzz.tmpl --output templates.js --root test
This will generate a file named templates.js
in the current directory. You can then import the generated module in your JavaScript code:
import { extended as template } from './templates.js';
const result = template('main', { foo: 'world', baz: 'baz!' });
console.log('render result:', result);
- If a template does not contain a
<!--meta.keys=[...]-->
comment, a warning will be shown. - The output file is an ESM module (use
import
to load it).