During the development of my
experimental WEB project on Node.JS, which I described in two
previous articles , I was faced with the problem of choosing a template engine. Despite the fact that there are quite a few ready-made solutions, I did not manage to find one that would satisfy me 100%. This is how
JUST was born.
Competitors
Jade
github.com/visionmedia/jadeThis template engine is quite popular among Node.JS developers. It has good functionality and speed of work, but contains controversial points:
- Refusing to use tags in the place for which they, in fact, were invented. With such an approach, to put it mildly, I disagree. Of course, this is very subjective, but the kind of page markup without the usual tags explodes the brain. A typesetter who is far from modern technologies of templating will not thank you if he has to make changes to such code. Also, additional work is required when transferring the layout to templates, which will slow down the development process.
- Overloading functionality. Any developer tries to make his product as versatile as possible, but sometimes you need to be able to stop on time. In my opinion, Jade has already crossed this line.
Ejs
github.com/visionmedia/ejs')
Small, fast and fairly convenient template engine. It is simple and clear, but, alas, for a large project its functionality is unlikely to be enough. Out of the box, he does not know how to assemble a page from parts, although this is partially solved by small additions, as for example, this is done in
Express . But crutches are not our method.
Twigjs
github.com/fadrizul/twigjsAs the name suggests, this is a port of a fairly popular JavaScript PHP templating engine. This template engine is well balanced, but it cannot be called ideal. To implement the logic, developers have added their own syntax, which should facilitate development. I do not think this idea is good, because this increases the threshold for entry into development. Whether it is PHP or JavaScript, the coder or programmer will have to spend time learning one more syntax, delve into its logic and follow its changes. The moment is very controversial. Many developers are happy with this syntactic sugar. Me not.
Simple, fast, convenient
These three qualities formed the basis of JUST. Template engine is one of the most important details of any WEB project. Except for completely cached pages, it works with every request. So that at any stage of the project development work with templates only brings joy, the template engine should initially be such that it would be desirable to lick it.
Just it
- HTML for markup
- JavaScript for logic
- Template caching
- Automatic reload of changed templates (only for Node.JS version)
- Very simple syntax, but serious functionality.
- Inheritance, injection, block definition
- Ability to work both in Node.JS and in the browser
- High speed
Closer to the point
A simple example of JUST templates:
page.html
<%! layout %> <p>Page content</p>
layout.html
<html> <head> <title><%= title %></title> </head> <body><%*%></body> </html>
The code that generates the resulting HTML from these templates and the transferred data looks like this:
var JUST = require('just'); var just = new JUST({ root : __dirname + '/view' }); just.render('page', { title: 'Hello, World!' }, function(error, html) { console.log(error); console.log(html); });
A more complete example can be found in the
repository on GutHub .
On the client, everything works the same way. First you need to connect JUST to the page:
<script src="https://raw.github.com/baryshev/just/master/just.min.js" type="text/javascript"></script>
Now JUST can be used in the same way as in Node.JS:
var just = new JUST({ root : '/view' }); just.render('page', { title: 'Hello, World!' }, function(error, html) { console.log(error); console.log(html); });
Because In the client version, templates are loaded using AJAX, they must be on the same domain as the page with the script.
As root, JUST can use a JavaScript object. Sometimes this opportunity can come in handy.
var just = new JUST({ root : { layout: '<html><head><title><%= title %></title></head><body><%*%></body></html>', page: '<%! layout %><p>Page content</p>' } });
How does it work
A template is broken down into parts by a simple algorithm and transformed into a function that performs string concatenation, and accepts data as parameters for generating a result. This function is cached, and further work goes only with it. Re-parsing occurs only when the application is restarted or when the template file is changed (if the watchForChanges option is set to true). This approach provides excellent performance. On "
What to do? »Pages with template parsing generated 20-60ms. Disassembled template (from cached function) for 1-2ms. Considering that parsing occurs only 1 time for each template, and the application lives for a long time - the template parsing time can be neglected.
The data passed to the template engine is visible “through” to all templates involved in the construction of the page. This eliminates the need to transfer them manually to parent or child templates, but there is the possibility of manual data transfer.
What JUST can do
Data output
Data output in the template is carried out using a simple construction:
<%= varName %>
Data is inserted into the template as is, i.e. they do not undergo any preprocessing in the template engine.
Javascript logic
In the templates, you can use the full JavaScript code. For example:
<% for (var i = 0; i < articles.length; i++) { %> <% this.partial('article', { article: articles[i] }); %> <% } %>
or
<% if (user.authenticated) { %> <%@ partials/user %> <% } else { %> <%@ partials/auth %> <% } %>
or
<%= new Date().toString() %>
Pattern Inheritance
As practice has shown, inheritance is a very convenient tool that allows you to build a page from the bottom up. The operation of inheritance in JUST is as follows:
<%! layout %>
or
<% this.extend('layout', { customVar: 'Hello, World!' }); %>
if you need to pass additional parameters to the parent template. The inheritance operation can be anywhere in the template, but it is more convenient to insert it either from above or below.
In the parent template in place of the child insertion, you need to insert the following structure:
<%*%>
or
<% this.child(); %>
Pattern injection
This operation allows you to connect an external template to the call site, delivering the necessary parameters to it, if necessary.
<%@ partial %>
or
<% this.partial('partial', { customVar: 'Hello, World!' }); %>
if you need to pass additional parameters to the template.
Block redefinition
This mechanism is somewhat similar to inheritance. In the parent templates, in addition to the place to insert the child, you can declare places to insert blocks. The content of these blocks is defined in the child templates. Unlike inheritance, block redefinition works to the very top, i.e. from any template can determine the contents of the block of any of their parents, and not just the immediate. To define a block, the following construction is used:
<%[ blockName %> <p>This is block content</p> <%]%>
or
<% this.blockStart('blockName'); %> <p>This is block content</p> <% this.blockEnd(); %>
In any of the parent templates, you need to define the insertion point for the block using the following construction:
<%* blockName %>
or
<% this.child('blockName'); %>
Blocks can be overridden. The result is a block defined at the lowest level.
What is not and will not be in JUST
Data filters
Almost all template engines have filter sets that allow you to manipulate data during the HTML generation phase. For example: tag escaping, case-inchuring, clipping strings. This approach is fundamentally wrong and this functionality is not the place.
In the comments on this subject, objections will surely appear like: “This approach does not correspond to MVC. Tag escaping refers only to the presentation of data in the form of HTML / XML, and we may want to display them in a format where screening is not needed, for example, PDF. Therefore, this functionality should be in the template engine ".
Preventing disputes over this, I will immediately answer this question.
The template engine is not a component of the “V” model of MVC in its pure form. As a special case, it can be, but it is not required. The “presentation” component can include both a tool for directly constructing a response (template engine) and tools for preparing data for a particular presentation. At this stage, the data can be prepared and cashed. This approach will allow not to perform a huge amount of unnecessary data operations. Considering that string operations are rather slow, this cannot be neglected. If you don’t care how many times a line is replaced, one or ten thousand, then I have bad news for you.
Syntactic sugar in logical terms
Another fashion trend is to reinvent your own pattern syntax. Most often it comes down to cutting out brackets, etc. reduce language constructs. The gain from this is very doubtful. The developer should know one more syntax and hope that the author of the template engine in the new version will not make it “even more convenient”, and its templates will continue to work after the update. Native language syntax is known to all those involved in the development. A new developer can immediately join the business, and not delve into the work of a super-convenient syntax. Layout designers, who most often don’t have much to do with how programmers cut their creations into pieces, can make small changes to the templates themselves and even correct the logic, because JavaScript, they still know of duty.
A spoon of tar
There is one unpleasant moment in JUST. If the template has a call to an undefined variable, then when it is processed, a ReferenceError will occur when the call to a nonexistent variable. I want to say a little about the reasons for this behavior and why it did not work out to fix it.
To access the variables inside the template, I see three ways:
- The variable is accessed through the% object%.% Var%. For example, this.varName or data.varName. In this case, when referring to the undefined error value will not be, and in the template at this point an empty line will appear, but in the end we will get greatly expanded templates due to variable prefixes.
- Autochange of direct access to a variable to a call through the% object%.% Var%. For example, if the template has the <% = varName%> construct, when parsing it, we can easily replace it with this.varName, thus eliminating the error with an undefined variable. But references to a variable can be not only in the output structure, but also in logic. For example, traversing an array in a loop or participating in conditional statements. Without additional syntax analysis, in such places there is no possibility to replace calls to variables automatically.
- Adding an object with template data to scope using with. By placing an object with data in scope, we can access them without the object prefix, but pay for it with an error when accessing an undefined variable.
At the moment I do not know how to get around this situation without additional parsing the template. If someone sees a beautiful solution for this problem - it will be very interesting for me to read about it. For now, this should be taken as a feature of this template engine. Although in a sense, this approach disciplines and does not allow undefined variables to scatter.