fmpl
v3.0.0
Published
fucking template engine
Downloads
5
Readme
fmpl
This is a template engine.
Naming
It's like "tmpl", only with an "F" instead of a "T". Use your funning imagination to figure out what the "F" stands for.
Installation
npm install fmpl
Reasons
This solves a singular goal. That goal is to precompile templates into an independent callable function. Only pug/ejs seem to do that but pug is for HTML and ejs doesn't support template inheritance.
This library is NOT:
- asynchronous
- super serious
- recommended for use in production
I mean, it works, and the tests pass (100% coverage), but the parser is kinda naive and you can jam it up if you try really hard. I use it for precompiling HTML email templates. I wouldn't recommend it as a replacement for a more "real" template engine like pug, ejs, nunjucks or dust.
If you want minimal dependencies and standalone precompilation, this is the library for you.
Usage
const {Fmpl} = require('fmpl');
import {Fmpl} from 'fmpl'; // typescript
//fancy usage
const fmpl = new Fmpl();
const fn = fmpl.compile(someTemplateString);
console.log(fn({ dem: 'vars' }));
//less fancy usage
console.log(Fmpl.render(someTemplateString, { dem: 'vars' }));
From the command line
fmpl
comes with a cli utility called fmpl
. Run it from node_modules/.bin/fmpl
.
fmpl [--verbose|-v] [--help|-h] [file] [file...]
fmpl is a fucking template engine that accepts a file and spits out a
stringified JavaScript function to stdout. If no file is specified
then it reads from stdin. If "-" is given for the filename it will read
from stdin.
Options
--verbose|-v show debugging messages
--help|-g show this message
--render json compile and render the template
Brief syntax overview:
{{ expression }} echo the result of a JavaScript expression
{$ expression $} execute a JavaScript expression
{% if expression %}{% endif %} basic if statement
{% for expression %}{% endfor %} basic for loop
{% while expression %}{% endwhile %} basic while loop
{% block name %}{% endblock %} create a block
{% include name %} include another template
Append "-" to any of the above opening tags will trim previous whitespace.
Returns 0 if it worked, 1 if it didn't.
Template Syntax
Quick and dirty: variables are in {{ }}
, code is in {$ $}
, everything else is in
{% %}
. Parentheses around if/else/for/while expressions are optional.
Trim previous whitespace with -
, e.g. {{- }}
or {%- %}
.
Variables/expressions
Any JavaScript expression can can be interpolated
Interpolate a variable:
const tmpl = 'Hello {{ name }}';
Fmpl.render(tmpl, { name: 'yarp' }); //Hello yarp
Interpolate an expression:
const tmpl = 'Hello {{ (() => { return \'yarp\'; }()) }}';
Fmpl.render(tmpl); //Hello yarp
Arbitrary code
Sometimes you just want to set a variable or something.
{$
is the same as {{
except that it won't echo the result.
const tmpl = '{$ var name = \'yarp\'; $}Hello {{ name }}';
Fmpl.render(tmpl); //Hello yarp
Control Flow
if
/else
Parentheses are optional. else
is optional.
const tmpl = 'Hello {% if foo %}{{ foo }}{% else %}world{% endif %}';
Fmpl.render(tmpl, { foo: 'bar' }); //Hello bar
Fmpl.render(tmpl, { foo: '' }); //Hello world
for
Parentheses are optional.
Regular for loop:
const tmpl = '{% for var i = 0; i < 3; i++ %}{{ String.fromCharCode(i + 65) }} {% endfor %}';
Fmpl.render(tmpl); //A B C
for..in
loop:
const tmpl = 'Hello {% for var fruit in fruits %}{{ fruit }} are {{ fruits[fruit] }} {% endfor %}';
Fmpl.render(tmpl, { fruits: { apples: 'red', bananas: 'yellow' }}); //apples are red bananas are yellow
while
Parentheses are optional
const tmpl = '{$ var i = 0; $}{% while i < 3 %}{{ String.fromCharCode(i + 65) }}{$ i++ $} {% endwhile %}';
Fmpl.render(tmpl); //A B C
Blocks
Blocks are blocks of content that you can declare and then override or append to. Useful for template inheritance.
They are declared with {% block <name of block> %}
, and referenced later the same way.
They can optionally have content. If they have a content, the default is to replace the content
if a block is referenced later. If you want to append content, add a "+" in front of the name,
like so {% block +<name of block> %}
.
Blocks are inserted into the content where they are initially declared.
const tmpl = '{% block yarp %}{% endblock %} Hello {% block yarp %}block content{% endblock %}world';
Fmpl.render(tmpl); //block content Hello world
Appending content:
const tmpl = '{% block yarp %}original content{% endblock %} Hello {% block +yarp %} new content{% endblock %}world';
Fmpl.render(tmpl); //original content new content Hello world
Blocks can be nested, and names from a parent block can be reused:
const tmpl = `
{% block content %}{% endblock %}
This should be last in the rendered result.
{% block content %}
blox!
{% block content %}{% endblock %}
more blox!
{% block content %}i like blox{% endblock %}
{% endblock %}
`;
Fmpl.render(tmpl);
/*
blox!
i like blox
more blox!
This should be last in the rendered result.
*/
Includes and template inheritance
Template inheritance can be accomplished by combining an include
and a block
.
Includes simply insert another template into the current template. You can use them as many times as you want wherever you want. Try not to create a circular template or else I'll kill your family. Not really though.
By default, included templates are resolved assuming they are file names. If this isn't ideal,
you can create your own resolver and load it into the Fmpl
instance.
The default filename resolver is very simple and kinda stupid. If the include path is not absolute, it searches relatively from the original file path (if available). So if you have doubly nested templates that include relative templates from different directories, you may run into issues. Easiest solution is to simply use your own resolver.
If an included template cannot be resolved, an error will be thrown.
const parent = `
Some stuff at the top.
{% block content %}{% endblock %}
Some stuff at the bottom.`;
const child = `
{% include myParentTemplate %}
{% block content %}
This is in the middle!
{% endblock %}
`;
const fmpl = new Fmpl();
fmpl.addIncludeResolver((name) => {
if (name === 'myParentTemplate') {
return parent;
}
return null;
});
fmpl.compile(child)();
/*
Some stuff at the top.
This is in the middle!
Some stuff at the bottom.
*/
Internals
Internally it appends a bunch of stuff to a string and then dynamically
creates a callable function using the Function
constructor. Code you write
in your template (e.g. in an if
statement) is inserted verbatim and will
throw syntax errors if it sucks.
Variables for internal use are prefixed with ____
(four underscores) so if you
do something like {$ ____render = null; $}
nothing will work. Try not do that.