shawnbot / meta-template

:sparkles: Automagically convert Nunjucks templates into a variety of other formats!
52 stars 9 forks source link
javascript nunjucks template-metaprogramming

meta-template

Maintaining templates can be a pain in the butt, especially if you need to maintain templates for multiple engines or host languages. Meta-template aims to solve the problem of multi-engine template maintenance by making it possible to treat Nunjucks templates (which are theoretically compatible with Jinja out of the box, and almost compatible with Django, Liquid, and Twig) as the source of truth and programmatically transform them into other formats (such as ERB, Handlebars, Mustache) and even other languages, such as JSX or PHP.

How it works

At a high level, there are three steps in the template conversion process:

  1. Use Nunjucks to parse a template into an abstract syntax tree (AST)

    const mt = require('meta-template');
    const ast = mt.parse.string('{% if foo %}{{ foo }}{% else %}no foo!{% endif %}');
  2. Make any necessary transformations to the AST to match the output format

    mt.ast.walk(ast, node => {
    if (node.type === 'TemplateData') {
      // do something with node.value here to modify the output, e.g.
      node.value = '(' + node.value + ')';
    }
    });
  3. Format the AST into a string with a function that declaratively handles different types of AST "node" (If, Output, etc.), and automatically throws errors for unsupported node types

    const out = mt.format.php(ast);
    console.log(out);
    // produces:
    // '<?php if ($foo): ?><?= $foo ?><?php else: ?>(no foo!)<?php endif; ?>'

You can try it yourself by combining the above snippets into a standalone script and run it through the php command with:

node njk2php.js | php
# (no foo!)

The abstract syntax tree

The abstract syntax tree, or AST, is a tree structure of JavaScript objects that describes the parsed template. Some common nodes in the tree are:

TODO: explain the parse and AST bits.

The format API

TODO: explain the abstract and concrete format APIs.

Play with it!

Currently I'm experimenting with different output formats, starting with Liquid (most useful for us Jekyll users at 18F) and PHP (which seemed to me the most potentially difficult). You can test these out by cloning the repo, running npm install to get the dependencies, then running the bin/parse.js script:

# output the Nunjucks AST in JSON format
./bin/parse.js path/to/template.html

# do the same without line and col info (--clean), trim input (--trim)
./bin/parse.js --clean --trim path/to/template.html

# or use stdin
echo 'foo {{ bar }} baz {% if x %}hi{% endif %}' | ./bin/parse.js

# reformat the AST as Nunjucks (this _should_ produce the same output)
echo 'foo {{ bar }} baz...' | ./bin/parse.js --format

# reformat as Liquid
echo 'foo {{ bar }} baz...' | ./bin/parse.js --format liquid

# reformat as PHP!
echo 'foo {{ bar }} baz...' | ./bin/parse.js --format php

Roadmap

This project is in its infancy, but here is a very rough roadmap: