npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fmpl

v3.0.0

Published

fucking template engine

Downloads

5

Readme

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.