But since I am often interested in JavaScript, I decided to find a similar template engine for this language, but server-side (node.js, yeah). The first links in the search engine are given by template engines such as: ECT, JUST, . All of them, naturally, were not suitable for me because they were too far from the Smarty syntax.

Well, if you want to, but there are no solutions, then do it. Finding time at work and sometimes getting hit, the NodeSmarty project was born, which, in the future, will partially copy the Smarty syntax.

So, the project took root about 4 months ago, in September 2012, and gradually took on a working form. At first, the entire template compilation algorithm was taken from Smarty (Smarty_Compiler.class.php file), some functions were not transferred. In the process of writing code, I made all sorts of code shortcuts in terms of simplicity, as this is “my style”. Therefore, all the huge ones were replaced regular expressions(and by the way, they were still full of unnecessary garbage), part of the logic is in the functions.

Since the project is untested, it is completely clear that a sufficient number of bugs will be found in the process. I’m also sure that there will be complaints about error handling in templates (at this stage this “function” is poorly implemented). I will only be happy to receive messages about code improvements, and, to the best of my ability, introduce both corrections and new features.

Examples of the template engine:

compiler.js:
var NodeSmarty = require("./NodeSmarty/"); var Template = new NodeSmarty(); Template .setTemplateDir("./templates/") .setCompileDir("./compile/") .setCacheDir("./cache/"); var Array = ["val1", "two"]; Template.assign(( "Value":"first", "number":10, "array":Array )); var Final = Template.fetch("template.tpl", function(data) ( console.log(data); ));

template.tpl:
(include file="header.tpl.html") ($Value) ($number*10/20) (if !$Value) //... (elseif $number = $Value) //... (else ) //... (/if) (* comment *) (literal) for(var di=0; di< 10; di++) { console.log(di); } {/literal} {foreach from=$array item=Foo key=Key} Item: {$Foo} key: {$Key} {/foreach}

Code execution speed is higher than EJS and JUST. This happens because when the file is requested again, it is not the template that is called, but the already compiled code. But there is one more plus (although it doesn’t exist, it will be in the future) - the increase in execution speed also depends on file system(yes, first you need to check if the templates have changed and recompile them). If you compile files on the first request, and simply unload them from memory on subsequent requests, the speed will increase accordingly, and the code execution time will become even shorter!

Comparison of code execution speed with 1000 iterations:

A little about the principle of operation.

If the template is changed before the file is compiled, then the library simply loads the compiled file and substitutes the values ​​into it. If the template is changed later, the following instructions are executed:

First of all, the document is parsed for the presence of an opening tag: ( and a closing tag: ) (although you can change it if you wish, see the documentation).
Then each tag is parsed into the main parameter and attributes. The main parameter is also processed and is divided into 2 types: command or variable. A variable is distinguished from a command by a leading dollar sign ($). Both the variable and the command are processed to transform themselves into working JavaScript code. The compiled code is written to a file and the text is evaled (although there is not eval, but new Function() ).

The number of JS libraries is not decreasing in any way; on the contrary, it is growing every day. When we come to JS applications, best choice templates turn out to be better than full-fledged libraries because it results in cleaner code base and a better experience when working with them.

Not long ago I wrote that you could try to write your own library when the time comes. Template engines, on the other hand, require a bit more skill and understanding of the language you are working with, so it is better to rely on any template engine from the list below.



A list of them can be found on Wikipedia, which does a great job of comparing engines for different web programming languages, but it doesn't really focus on one language, so I'd like to see how many engines can be listed for Javascript.

If you develop in Javascript, you will recognize a number of engines, but you can also learn about some new ones. I would love to continue this list together and hope it makes you happy.

chibi.js

Chibi gives you everything to save traffic and time for displaying a template, a small and lightweight library that will help you better template your application. Focuses more on CSS instead of using animation. (The author’s literal “water” - translation)

templated.js

This is the smallest template engine you will come across, guaranteed (Nano - translation). It is built on top of Mustache and is easy to use and understand. The site has a large demo example in which you can run and test the code.

ECT

Like templayed, ECT also has demo installation pages on the website that you can play around with and see live results. It's built for speed, and claims to be the fastest JS templating engine (built on Coffeescript). Compatible with Node.js and has a clear syntax. There are benchmarks and unit tests on Github that show the effectiveness of this library.

Pithy.js

There is an internal DSL for generating HTML in JavaScript. This is great for small front-end projects, but is not recommended for heavy HTML pages.

T.js

T.js uses a simple Javascript data structure for presentation HTML data/xml.

Nunjucks

Created in Mozilla, Nunjucks is made for those who need performance and flexibility due to the ability to expand the user's library of plugins and features.

Jade

Jade is designed primarily for server-side templates in node.js, but can run in many other environments. It is made only for XML-like documents (HTML, RSS, ...), so do not use it for design plain text, markdown, CSS and similar documents.

Dust.js

Dust expands Mustache and offers high quality performance compared to other solutions on this list. Contains a very simple and understandable API.

Javascript templating engines I did not try to give examples, because there are many links to official pages contain demonstrations.

I hope you were able to discover new options for your next project. I'm sure there are many alternatives not mentioned, but the ones listed make the most sense.

UPD: commentators on the original article added:

  • "Pure - Simple and ultra-fast templating tool to generate HTML from JSON data
  • Dust.js is used by PayPal, and default in their Kraken.js framework.
  • Swig - A simple, powerful, and extendable JavaScript Template Engine.
UPD2: mentioned in the comments to this article:
  • Twig - JS implementation of the Twig Templating Language
  • EJS Embedded JavaScript templates for node

This article is review of template engines for front-end development. If you work with JS, templating engines are an excellent and suitable choice, as they allow you to write cleaner base code and make your applications easier to work with. Let's look at the most popular JS template engines that Wikipedia offers us. In order to work efficiently with these template engines, you need to have a good understanding of the language and a sufficient number of skills in this area.

ICanHaz.js is a fairly simple and lightweight library to work with Mustache templates. With it, you can define template fragments in script tags with type=”text/html” and call them via id, with validation.

Mustache.js is a very popular template engine in this application area. It provides server-side and client-side solutions so you don't have to worry about using multiple template engines. It has good and clear documentation, as well as a large community. Templates use JSON data format. Runs great in popular programming languages. Latest version libraries can be downloaded from the official page.

is an extension for Mustache and is fully compatible with it. Has higher speed execution, since it is not overloaded with unnecessary logic. HTML is generated using the same principle from data in JSON format. A significant drawback is the heavy weight of the library.

Underscore.js is a convenient and practical tool for working with jQuery. Has more than 60 functional utilities. This template engine enhances the functionality of jQuery when working with arrays, functions, objects, collections, etc. 5 jQuery UI Alternatives.

Hogan.js is a template engine developed by Twitter. It is used as a builder of intermediate templates for their fast dynamic processing by a web browser. The development of this template engine was based on Mustache, but Hogan.js is much faster. An API is offered to access the parser functions. Template scanning and parsing are performed by separate methods, as a result of which templates can be processed in advance on the server, and on client side used in Javascript form.

– used for working with server templates, but in general it is applicable for other purposes. It has high code readability and good security. Operates from the client part, it is possible to use the utility to compile HTML templates from command line. The template engine has been renamed Pug.js, but by the old name you can find a large amount of documentation and useful information for the developer.

The ECT template engine is designed to improve speed. It is based on the Coffeescript template engine. It has good compatibility with Node.js and a fairly simple syntax. You can also check out preliminary tests that show the performance of this library.

Hyper Host™ wishes you a pleasant work in the world of JS and is ready to post your wonderful projects on our website!

4366 times 3 Viewed times today

3.2 out of 5

Mustache is a template engine that contains a minimum of control logic and is available for different programming languages. It can be used both on the server (PHP, Ruby, etc.) and on the client (Javascript).

If you dynamically loaded blocks of HTML code from the server, and not structured data, only because you did not want to duplicate rendering on the server and on the client, then Mustache will help avoid this.

The template is written in a simple language consisting of several types of tags. Tags are framed by two or three curly braces on each side. You can use nested templates.

Let's look at a simple example template:

((header))

((content))



    ((#authors))
  • ((#accent)) ((.)) ((/accent))

  • ((/authors))
    ((^authors))
  • anonymous

  • ((/authors))

The data that the template operates on is called context. The tag name specifies which context field to access. An example of data that could serve as context for our template:

Var data = (
header: "New post",
content: "First line
Second",
authors: ["alex", "daemon", "john"],
accent: function () (
return function (text, render) (
text = render(text);
return "" + text + "";
}
}
};

To “launch” the template engine and render data using the template, you need to connect the library:

And call rendering using the to_html method:

Mustache.to_html(template, data);

Here the first parameter is the template, and the second is the data. You can also use the third parameter - a list of additional templates, and the fourth - the callback function, which is called after processing the template.

Learn more about tags

Total in Mustache four main types of tags: variable, section, comment and additional template connection.

The variable outputs data with HTML entities escaped ((header)) and without escaped (((content))). They differ in the number of brackets. In our case, instead of ((header)), the line “New post” will be substituted.

A section is a paired tag. The principle of its operation depends on the type of data with which it works. If the section name matches a combo box in the context, then the template engine goes through its elements and the text inside the paired tag is processed once for each list element. The list element is substituted for the dot tag. So, for example, section ((#authors))

  • {{.}}
  • ((/authors)) will become
  • alex
  • daemon
  • john
  • . If the list is empty, then the contents of the “cap tag” are processed, in our case this is ((^authors)) ... ((/authors)) .

    If the section name matches a function, then the result of its execution will be used for substitution.

    If the field corresponding to the section name is neither a list nor a function, then it will be used as a context for processing the contents of the tag.

    The comment is formatted as a tag with an exclamation mark, for example, ((! comment content)) .

    Including an additional template is called using a tag with an angle bracket. For example, ((>copyright)) . If there is a field with this name in the current context, it will be passed as the context for the connected template.

    Performance

    Performance issues of Javascript templating engines are discussed in this article. The author tested eight template engines and found that simple templates Mustache shows better performance.

    A great many articles have been written on the topic of template engines, including here on Habré.
    Previously, it seemed to me that it would be very difficult to do something of my own - “on my knees”.
    But it so happened that they sent me a test task.
    Write, they say, a JavaScript template engine, according to this scenario, then you will come for an interview.
    The demand, of course, was excessive, and at first I decided to simply ignore it.
    But out of sporting interest I decided to try it.
    It turned out that not everything is so complicated.

    Actually, if you are interested, then under the cut are some notes and conclusions on the creation process.

    For those who just want to look: the result , the cat .

    The source template is JS String() and the data is JS Object().
    Blocks of the form (% name %) body (% / %), unlimited nesting possible.
    If the value of name is a list, then all elements are printed, otherwise if not undefined, one element is printed.
    Substitutions of the form: (( name )) .
    In blocks and substitutions, it is possible to use dots as a name, such as ((.)) or (%.%) , where dot would be the current element of the top-level object.
    There are also comments - this is (# any comment w\wo multiline #) .
    Filters are possible for the values ​​themselves; they are specified separated by a colon: (( .:trim:capitalize… )) .

    It should work like:

    Var str = render(tpl, obj);

    Prove:
    +1 to self-esteem.

    UPD 2: I’ll say right away that in order to “clearly understand” what’s there and why, you need to start doing it, preferably together with a debugger.
    UPD 3: It is disassembled “on the fingers”. There is still room for “optimization”. But it will be much less clear.

    Let's get started.

    Because Since the original template is a string, you can take advantage of regular expressions.

    First, you can remove the comments so that they don’t show up:

    // to cut the comments tpl = tpl.replace (/\(#[^]*?#\)/g, "");

    Hint: [^] means any character, * - any number of times.

    Now we can think about how we will parse the “clean” result.
    Since blocks can be nested, I suggest storing everything as a tree.
    At each level of the tree there will be a JS Array() whose elements can contain a similar structure.

    To create this array you need to separate the flies from the cutlets.
    For this I used String.split() and String.match() .

    We also need a deep search for the string val name inside the obj object.

    The getObjDeep variant applied:

    var deeps = function (obj, val) ( var hs = val.split("."); var len = hs.length; var deep; var num = 0; for (var i = 0; i< len; i++) { var el = hs[i]; if (deep) { if (deep) { deep = deep; num++; } } else { if (obj) { deep = obj; num++; } } } if (num == len) { return deep; } else { return undefined; } };

    So, let's divide the line into parts and matches elements:

    // regular parsing code: // digital letters, dot, underscore, // colon, slash and minus, as many times as you like var ptn = /\(\%\s*+?\s*\%\)/g; // string pieces var parts = tpl.split (ptn); // matches themselves var matches = tpl.match (ptn);

    For debriefing, we will need two arrays.
    In one we will store blocks, in the other there will be the current element from the match cycle.

    // all blocks var blocks = ; // nesting var curnt = ; if(matches)( // because it might be null var len = matches.length; for (var i = 0; i< len; i++) { // выкидываем {% и %}, и попутно делаем trim var str = matches[i].replace (/^\{\%\s*|\s*\%\}$/g, ""); if (str === "/") { // finalise block // ... } else { // make block // ... } // ...

    Here blocks is the final array with selected blocks, and curnt is an array with the current nesting.

    At each step of the loop, we determine what is now in str, the beginning of the block or the end.
    If the beginning of the block, i.e. str !== "/" , then we create a new element and push it into the array.
    And also push it to curnt, because we need to understand at what level we are.
    At the same time, we enter the lines themselves into the block.
    Accordingly, if we have an empty curnt, then we are at level zero of the tree.
    If curnt is not empty, then you need to nest the element of the last curnt.

    // length of the current nesting var cln = curnt.length; if (cln == 0) ( // since this is the top level, we simply put the current element in it blocks.push (struct); // write the current nesting, which is also zero curnt.push (struct); ) else ( // need to nested the current nested block curnt.nest.push (struct); // now take this “last” element and add it to curnt var last = curnt.nest.length - 1; curnt.push (curnt .nest [ last ]);

    Accordingly, each element of the array is the minimum:

    Var struct = ( // current obj for the block cnt: deeps(obj, str), // nested blocks nest: , // line before all nested blocks be4e: parts[ i + 1 ], // str -- line going after completion of this // cnt is the parent block, we will parse the line within its framework af3e: ( cnt: null, str: "" ) );

    Because we may have a situation where there is something else after the block, then here af3e.str should be the line coming immediately after (% / %) of the current block. We will provide all the necessary links at the time the block is completed, so it’s clearer.
    At the same moment we remove the last element, the curnt element.

    If (str === "/") ( // the previous element curnt // is the parent // of the now completed block curnt .af3e = ( cnt: (curnt [ cln - 2 ] ? curnt [ cln - 2 ].cnt: obj ), str: parts[ i + 1 ] ); curnt.pop();

    Now we can collect a one-dimensional array that will contain all the necessary substrings with their current obj.
    To do this, you need to “parse” the resulting blocks, taking into account that there may be lists.
    It will take a little recursion, but overall it won't be that difficult.

    // array of strings for parsing elementary parts of blocks var stars = [ [ parts, obj ] ]; parseBlocks(blocks, stars);

    Example view of parseBlocks()

    var parseBlocks = function (blocks, stars) ( var len = blocks.length; for (var i = 0; i< len; i++) { var block = blocks [i]; // если определён текущий obj для блока if (block.cnt) { var current = block.cnt; // найдём списки switch (Object.prototype.toString.call(current)) { // если у нас массив case "": var len1 = current.length; for (var k = 0; k < len1; k++) { // кладём в stars текущий элемент массива и его строку stars.push ([ block.be4e, current[k] ]); // парсим вложенные блоки parseBlocks(block.nest, stars); } break; // если у нас объект case "": for (var k in current) { if (current.hasOwnProperty(k)) { // кладём в stars текущий элемент объекта и его строку stars.push ([ block.be4e, current[k] ]); // парсим вложенные блоки parseBlocks(block.nest, stars); } } break; // у нас не массив и не объект, просто выведем его default: stars.push ([ block.be4e, current ]); parseBlocks(block.nest, stars); } // кладём в stars то, что было после текущего блока stars.push ([ block.af3e.str, block.af3e.cnt ]); } } };

    Var pstr = ; var len = stars.length; for (var i = 0; i< len; i++) { pstr.push(parseStar (stars[i], stars[i])); } // Результат: return pstr.join ("");

    Example parseStar()

    var parseStar = function (part, current) ( var str = ""; // remove unnecessary var ptn = /\(\(\s*.+?\s*\)\)/g; var parts = part.split (ptn); var matches = part.match (ptn); // start assembling the string str += parts; if (matches) ( var len = matches.length; for (var i = 0; i< len; i++) { // текущий элемент со значением var match = matches [i]; // убираем лишнее и делаем trim var el = match.replace(/^\{\{\s*|\s*\}\}$/g, ""); var strel = ""; // находим элемент в текущем объекте var deep = deeps(current, el); // если нашли, то добавляем его к строке deep && (strel += deep); str += strel; } if (len >0) ( str += parts[ len ]; ) ) return str; )

    The above code is slightly smaller than the final result.
    So, for example, I did not show what to do with the current element if it is set to a dot.
    I also did not provide filter processing.
    In addition, in the final version, I “on my own” added to the processing of situations when the “current element” or “value for” are functions.

    But my goal was to show the concept itself...

    And the result, as already mentioned at the beginning of the article, can be found.
    Final example.

    I hope it is useful to someone.
    Thank you for your attention!


    Close