Nimja
By David Krause (enthus1ast)
Nimja is a compiled, type safe and fast templating engine written in Nim.
It looks like python’s jinja2 or PHP’s twig, but in contrast to them,
it is fully compiled and type safe, since it compiles down to Nim.
All the heavy lifting is done on compile time, and the resulting binary contains
idiomatic Nim that is very fast on runtime.
# nimble install nimja
import nimja
proc renderStuff(): string =
compileTemplateStr("""
{% extends partials/_master.nwt%}
{% block content %}
<h1>Random links</h1>
{% const links = [
(title: "google", target: "https://google.de"),
(title: "nim", target: "https://nim-lang.org")]
%}
{% for (ii, item) in links.pairs() %}
{{ii}} <a href="{{item.target}}">This is a link to: {{item.title}}</a><br>
{% endfor %}
<h1>Members</h1>
{% for (idx, user) in users.pairs %}
<a href="/users/{{idx}}">{% importnwt "./partials/_user.nwt" %}</a><br>
{% endfor %}
{% endblock %}
""")
Since Nimja transforms the templates to Nim on compile time, most Nim code is valid in the templates.
For example:
import times
iterator someProc(someVar: int): string =
for idx in 0 .. someVar:
yield "foo"
proc renderStuff(someVar: int): string =
compileTemplateStr("""
the current date is {{ now() }}
<ul>
{% for str in someProc(someVar): %}
<li>{{str}}</li>
{% endfor %}
</ul>
""")
echo renderStuff(10)
Nimja not only supports all the basics of a template engine:
if, for, while etc.
but also advanced features like:
extend or white space control.
Extend
Extend in particular is very useful for building websites. It basically allows a child template to choose its outer template.
This way you could just render a detail page, and have it automatically render its surrounding boilerplate.
For example:
master.nimja
<html>
<head><title>MyPage - {% block title %}{% endblock %}</title></head>
<body>
<h1>{{self.title}}</h1>
<div class="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
detail.nimja
{% extends master.nimja %}
{% block title %}Detail Page{% endblock %}
{% block content %}I am content{% endblock %}
When the detail page is rendered in your application, also the master.nimja is rendered implicitly:
import nimja
import os # for `/`
proc render(): string =
compileTemplateFile(getScriptDir() / "detail.nimja")
echo render()
multiple level of extend works as you would expect it.
Imports
Another useful feature of Nimja are the imports. This allows to import a template into another (also multiple times). So you can create small building blocks to use all over your website.
user.nimja
<div class="user">
User: {{user.name}} {{user.lastname}} age: {{user.age}}
</div>
detail.nimja
{% extends "master.nimja" %}
{# we define the users here for illustration, but they can come from db etc.. #}
{% let users: seq[tuple[name, lastname: string, age: int]] = @[
("David", "Krause", 33),
("Katja", "Kopylevic", 32)
] %}
<div class="users">
{% for user in users %}
{% importnwt getScriptDir() / "user.nimja" %}
{% endfor %}
</div>
Very Fast
Nimja is not explicitly optimized for performance, but is has some small optimization passes, for example it tries to minimize the number of string concatenations by combining them. Also the way Nimja works (mostly on compile time), makes it very fast.
Ajusa has made a simple template engine benchmark, and I’m very proud that Nimja is that fast:
# https://github.com/enthus1ast/dekao/blob/master/bench.nim
# nim c --gc:arc -d:release -d:danger -d:lto --opt:speed -r bench.nim
name ............................... min time avg time std dv runs
nimja .............................. 0.016 ms 0.017 ms ±0.001 x1000 <--
nimja iterator ..................... 0.008 ms 0.009 ms ±0.001 x1000 <--
dekao .............................. 0.105 ms 0.117 ms ±0.013 x1000
karax .............................. 0.126 ms 0.132 ms ±0.008 x1000
htmlgen ............................ 0.021 ms 0.023 ms ±0.004 x1000
scf ................................ 0.023 ms 0.024 ms ±0.003 x1000
nim-mustache ....................... 0.745 ms 0.790 ms ±0.056 x1000
The meaningfulness of such benchmarks are of course worth discussing, but it shows that you must not sacrifice runtime performance for the joy and ease of use (thanks to Nim’s macros).
How?
Nimja works by compiling its templates to Nim code:
for example this:
proc foo(ss: string, ii: int): string =
compileTemplateStr(
"""example{% if ii == 1%}{{ss}}{%endif%}{% var myvar = 1 %}{% myvar.inc %}"""
)
is transformed to this:
proc foo(ss: string; ii: int): string =
result &= "example"
if ii == 1:
result &= ss
var myvar = 1
inc(myvar, 1)
Since result &= "mystring is generated, you can choose any return type,
that implements a &= proc. This makes the process quite flexible.
So instead of a string, you could also return a Rope or similar.
Nimja can also generate iterator bodies. So instead of string concatenation, it can also yield.
iterator foo(ss: string, ii: int): string =
compileTemplateStr(
"""example{% if ii == 1%}{{ss}}{%endif%}{% var myvar = 1 %}{% myvar.inc %}""",
iter = true
)
If the web server is able to send data in chunks, you could save some memory, since only small chunks must be stored. In the benchmark, this approach is twice as fast.
Hot Code Reloading
Nimja is a compiled template engine, this means you must recompile your application for every change you do in the templates. This could be quite annoying and time consuming.
To streamline the experience a little bit, Nimja ships with Hot Code Reloading utilities.
This means it can compile templates to a dynamic library and recompile and reload this library on template change.
~For details have a look at the Hot Code Reloading section of the readme.~
Hot Code Reloading requires you to change your structure a little bit. You must group your render functions in one Nim file this file is then compiled to a shared object, and loaded on runtime.
!!!TODO add hcr example !!!
“Fileless”
The templates are compiled to binary and stored in the executable. This means that the templates must not be shipped in clear text.
In addition Nimjautils has utility functions that helps store small assets in the executable.
eg.: includeRawStatic and includeStaticAsDataurl
this could be used for single executable “electron like” GUI libraries.
Looks familiar
I’ve build Nimja to port jinja2 and twig websites to Nim. This means it tries to be as similar to both template languages as possible, without loosing it’s “Nim’ness”
Porting often means to just change a few lines of code, or add another “filter” you’ve used in one of the other engines.
A “filter” is just a Nim proc, so most of your current proc are “filters”
already. You would call them like you would call them in Nim (“foo.myFilter”)
or you could use the pipe alias from Nimjautils foo | myFilter
import nimja
import strutils
proc rot13(str: string): string =
for ch in str:
case toLowerAscii(ch)
of 'a'..'m': result.add chr(ord(ch) + 13)
of 'n'..'z': result.add chr(ord(ch) - 13)
else:
result.add ch
proc render(msg: string): string =
compileTemplateStr """
<h1>{{msg.rot13}}</h1>
"""
echo render("foo baa baz")
# <h1>sbb onn onm</h1>
Check it out on github: