Automagically convert Nunjucks templates into a variety of other formats!
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.
At a high level, there are three steps in the template conversion process:
const mt = require('meta-template');
const ast = mt.parse.string('{% if foo %}{{ foo }}{% else %}no foo!{% endif %}');
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 + ')';
}
});
If
, Output
, etc.), and automaticallyconst 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, or AST, is a tree structure of JavaScript objects that describes the parsed template. Some common nodes in the tree are:
TemplateData
represents a raw string of template outputOutput
represents template data output, such as a variableIf
represents a conditional control structure with cond
(condition),body
(the output when cond
succeeds), and optional else_
child nodesSymbol
represents a "simple" variable expression, e.g. foo
LookupVal
represents a nested variable expression, e.g. foo.bar[0]
Literal
represents literals like true
, false
, and null
(which mustInclude
the Nunjucks/Jinja/Liquid implementation of template partialsTODO: explain the parse and AST bits.
TODO: explain the abstract and concrete format APIs.
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
This project is in its infancy, but here is a very rough roadmap: