fmpl
TypeScript icon, indicating that this package has built-in type declarations

3.0.0 • Public • Published

fmpl

This is a template engine.

Build Status NPM version

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.

Dependencies (0)

    Dev Dependencies (10)

    Package Sidebar

    Install

    npm i fmpl

    Weekly Downloads

    0

    Version

    3.0.0

    License

    MIT

    Unpacked Size

    27.1 kB

    Total Files

    5

    Last publish

    Collaborators

    • tmont