The time of the template zoo has passed, now MVC dinosaurs are running around, and they use built-in template engines and component builders. But to replace the old less convenient template engines in Knockout and Backbone, they are sometimes needed, mostly stopped in development at about 2014.
It happened with
DoT.js. Initially abandoned by the authors for about a year in 2013, he received their attention briefly, rising from version 1.0.1 to 1.1.1, and was again abandoned (or stabilized, depending on how you argue). In this connection, it was needed in 2013 (
to make a clone of DoT.js ), and now we need
to upgrade it.
It is as fast as the built-in
_.template()
in Underscore / Lodash, but with an improved syntax, in which the need to write JS in templates is rare, and in Underscor is always needed. These brackets with scripts even came up with a special term: javascript encapulated sections (JES), and basically got rid of them.
')
What we get in addition?
1. The template structure
was redesigned (in 2013, a link from there) to better read and reduce the number of decoding functions;
2.
Tests have shown that the speed on the average has not changed (fluctuations -3% - + 10% depending on the parameters);
3. Added team work on the structure, similar to the work on the array;
4. The 4th parameter is a filter of elements of a structure or array;
5. In some places, slowdowns due to bypassing bugs are compensated for by optimizing the code and regexps;
6. The global name “doT” is capable of changing to another in the settings (the original is not);
7. The order was introduced in the version numbering and versioning of the global functions encodeHTML () - the instances adopted here for optimization.
For reference on version numbering
In npm, a copy of version 1.1.1 of one-in-one in package.json is named as 1.1.2, but in the file there is the number 1.1.1; in branch 2.0 in repo - the same with non-update of number 1.1.1 and there is only 1 difference in var rw = unescape(...)
. In general, everything is done for confusion. Therefore, we believe that the newest version is 1.1.1, in which we take into account the difference from branch 2.0. Vetka 2.0 does not deserve its title.On occasion, it is convenient to make the documentation (there is
from the authors ), with a tool for checking it. (What was readable on the web - links below.) In short:
• he retained the “original” syntax of Underscore
_.template()
, in which we can write any JS code inside parentheses "
{{ ... }}
", including unclosed curly and operator brackets, and outside the parentheses - HTML-text fragments.
• brackets can be redefined by overriding in the settings all regexps with them (usually not necessary);
• the name of the senior element of the
'it'
structure can also be redefined, as well as 4 logical settings of the behavior;
• AMD, commonJS and just its global name (
'doT'
) are
'doT'
;
• besides the basic universal syntax, it has a number of commands similar to the Mustache / Handelbars style;
• like them, and
_.template()
, has 2 stages of templating (parameter currying) into a function and then into HTML (or other) code;
• not sharpened strictly under HTML, but tied to JS, so its scope is browsers and NodeJS;
• not much larger in volume than the source code
_.template()
- 3.3 K in compressed non-zipped form.
(To experiment with the code of a new or old template engine, you can use the old example in http://jsfiddle.net/6KU9Y/2/ (but then there is a js-fiddle better.) A convenient testing page for 2 engines on a common template and data is also built in the repository - in test/index.html
. By default, it compares the same results in the deployed and minified versions of files. And the second engine can work only with a clone, since it may have a global name other than 'doT'
. clone, and in the first place, for example, the original DoT.js 1.1.1 and watch the differences in parsing.)In the file of the second engine you need to set the name
globalName:'doTmin'
before opening the page.
However, the same is easy online:
https://jsfiddle.net/spmbt/v3yvpbsu/26/embedded/#Result or with frames and code editing, who has big screens:
https://jsfiddle.net/spmbt/ v3yvpbsu / 26 / . You don’t need to connect files, just copy the contents of 2 versions of DoT.js, and in the second, it’s more convenient to do it in a clone - correct the name
doT
to
doTmin
(even if not minimized). The default is DoT.js 1.1.1 original and DoT12.js 1.2.1 is a clone.
The benchmark (the 3rd button) is arranged over a short period of time, therefore it depends on parallel processes, and the speed of synchronous algorithms can be judged by averaging over a large number of measurements (the average accumulates and allows a little to judge the speeds). There is a built-in benchmark in the project, but it is complicated, through the node, and it does the same (for a group of test files).
The auto-update checkbox and error trapping are built into the test page, which allows you to test for the version that remains intact. So, it is easy to compare the speeds of the
'~'
and
'@'
commands by array. The second one can also work, but much slower - by 10-15% (checked with the “Bench” button). This is due to the need to use a slower for-in loop in the second case. However, it is impossible to do without for-in for structures, in order not to have the need to prepare arrays from structures for the original version, which does not have the
'@'
command.
Immediately, traditionally, we note that the 2nd calculation (calculation
on a compiled pattern ) is a hundred times
faster (for short expressions) than the full compilation each time (the 1st number, 100 times fewer dimensions). In the screenshot, "
Comp1e3: 99.22ms
" means: "1000 full compilations were performed in 99.22ms time." "
Run5e5: 69.08 ms
" in the 5th line means: "50 thousand fast HTML generations with a pattern are carried out for an average time of 69.08ms for every 10 thousand generations".
A
drop-down list of examples has been added, from which typical examples of DoT.js commands can be extracted and tested in 2 versions of the script. Example changes are temporarily saved and can be returned to them by re-selecting the previous item of the example, if the browser page has not been reloaded.
About teams - read more
At the beginning of the paragraph in quotation marks will be given the name, which in the
DoT12.js
code is the regular expression that is responsible for the given template function (command).
" ValEncEval " command
One regexp under this name combines 3 previous commands that have similar syntax.
{{ JS }}
Underscore Universal Heritage. Nothing more is needed; it is self-sufficient to describe everything (spaces in parentheses are optional). But read ... Read more 40 lines is strictly not recommended. JS unclosed brackets are interspersed with the same curly braces of the template engine. They are followed by unclosed HTML tags. This is a normal practice, it works fine, the machine understands. At the same time, Mustache / Handlebars are already readable.
{{= JS }}
The value of the expression is laid out in the surrounding stream of HTML, with the preservation of HTML tags and despite the unclosed tag brackets. So, encountered
"<br>"
in the meaning of the expression, if it is posted on the browser page, will behave not as text, but as a tag - will lead to the transfer of the line according to HTML rules.
{{! JS }}
The same, but returning text to the output with “hitting” the HTML code: html-tags and html-encoded (
&...;
) characters turn into text;
Command " conditional "
{{? if- }} then- {{?}}
Conditional inclusion of the template (spaces in brackets are optional);
{{? if- }} then- {{?? [if-else-]}} [if-]else- {{?}}
branching and conditional text chain templates.
An expression is any containing a global variable or a structure with the name
'it'
, into which the parameter is passed during templating.
if-else is easy to get by inverting the if-expression. The syntax did not complicate, apparently, for the sake of speed.
" Use " command
{{# , }}
Analogue of the preprocessor (macros) - at the beginning you can insert any fragment of the text, including unmatched template brackets. The result will be compiled. For example, through
{{# ...}}
you can add to the template the text of another template through a variable. But the team was conceived for simpler and more concrete cases, paired with the define command.
" Define " command
Appeared from version 1.0. At first it was decided not in the text of the templates, but in the list of parameters after the settings (the 3rd parameter in doT.template (template, settings, parameters)).
The definition format of a variable for use-commands in the define command:
{{## def.defin1 :___#}} {{## def.defin1 =___#}} - ()
Variables can be with points and $, in them any line is defined. There are a number of tricks.
The points in the name (the style of someone copied).
If the first 4 characters are 'def.', They are deleted.
If after a colon - the pairs are written def [code] = {arg: param, text: v};
The equality defines the function 'def' (it is convenient to look at the test page in the list of examples).
You can define a macro in one place so that you can use it 2 or more times. Like all macros, it is doubtful in terms of code quality. If you need macros - it means - it took you to stand before someone, reducing the size of the texts in a dirty way, because it was not time to think out how to reduce it from the point of view of the project. And the storage of variables is spent script resources.
There is a certain plus in the fact that the environments and workplaces of the script are separated, as with a couple of use-useParams commands.
And there is a semantic difference in the fact that scripts are executed on the fly, at the time of associating a template with data, and define and use are one step earlier when associating a template function with a template. In a word - these are macros.
The " defineParams " command
{{##foo="bar"#}}
So the parameters are determined, accumulating in the internal structure, and then used many times after.
" UseParams " command
Using previously defined parameters.
{{#def.foo}}
Nothing prevents to take, and determine the parameters in JS:
{{ JS }}
besides the reluctance to mix the environment of the template and the rest of JS. Here syntax highlighting and code analysis in the IDE and editors will be lost. Therefore, there are more reasons for not writing JS in template texts.
" Iterate " command
Combining 2 teams with different speeds and capabilities. The while template
{{~...}}
is a while loop through the array. (They chose, obviously, from everything and chose the fastest at that moment in browsers.) Works faster by 10-15% than its alternative
{{@...}}
on the for-in-template, which can run through the array or on the structure. 4th parameter - filtering elements by expression. The original version supports only the array and without filtering. It does not suit - there is always "
{{ ... }}
" (it is convenient to write, to read - no, like Perl or machine code).
{{~ it : value : index : filter-expression }} while- {{~}}
where
it
is the word
'it'
(or another), meaning the first argument, or the global name of the array, or an expression that returns an array;
value
- any name, for example,
'v'
or
'value'
without quotes, which will be used in the for-template in place of the substituted value of the array element, for example, in the expression
{{= value+1}}
;
index
- likewise, any name that defines the index of an element in an array.
Yes, the parameters are indicated “on the contrary” (first value, then index), but this is how they turned out, we will not change here and in the next similar command. The logic is that the last (and in general the last)
index
can be omitted, if not needed in the template, along with a colon.
You can omit other parameters in the clone, leaving a colon. In the original - it is impossible: at least a letter, but it is necessary to write. The first default parameter is 'it' (or rather,
templateSettings.varname
), the rest also have defaults, but they are very technical, not memorable.
In sum, everything is done in such a way that both the compilation and the execution of the templates are as fast as possible, without unnecessary actions and beauty. Most likely, something else can be improved, and some performance improvements will worsen the compile time, so there is a reasonable balance in everything.
{{@ it : value : index : expression }} for-in- {{@}}
Running through the structure of its first level. It is performed in the browser more slowly (% by 10-15) than in the array. You must take into account the peculiarities of the order of issuing keys-numbers and other keys (numbers go first, then all other keys, except for old versions of IE of type 8 and lower, Opera and old Fxs of the same past time. Where numbers went in the same row as others.). Moreover, no standard guarantees order, but it exists. Using it, or testing it, or relying on arrays is a matter for the developer. The most reputable will say that you cannot be 100% trusted and will be right. Same story in both Python and JSON.
At the same time, using the order in which JSON arrived or the structure was defined reduces the size of the procedure code. The 4th parameter is an expression - filter condition. If false, the item is not displayed. (If there is no condition, is the parasitic
"if(1)"
condition preserved? This is the balance charge.)
Examples of abbreviations
{{@::i}} for-in- {{@}}
it's just a walk through all the elements of the structure.
Example:
{{@::i}} <i>{{=it[i]}}</i> {{@}}
don't care
{{@:v}} <i>{{=v}}</i> {{@}}
Instead of patterns, you can use arrays in
"@"
commands. But on the contrary, in
"~"
-commands (in arrays) - structures - it is impossible, for the sake of compatibility with the original.
... After 7 years, all this will disappear and die under the weight of new magnificent frameworks, where the issues will be resolved in their own way and in a different way. forEach, filter, reduce, JSX - template killers are already on the threshold and with one foot each on this side of the door. And even chimpanzees can manage screen layouts. In the meantime - hurry to dig in crutches, until they finally become stories like K155LA3.
What are the settings and why are we needed
varname: 'it',
This setting is quite understandable. In template expressions, the name is used instead of arguments [0] (the first parameter of the intermediate compiled function). arguments [0] are far from always being applied because of nested functions, and the name is almost always if there are no conflicts. If there are conflicts, the name can be changed, moreover, not only directly in the library template's code, which is moveton, but also in the 2nd parameter
doT.template()
, in the local settings of the current command. (There is also a way to "static" change settings, getting to them by
window.doT.templateSettings
.)
There are other specific names for this template engine that can cause conflicts. They are easy to see on the test page by clicking the "Show function" button on the
test/index.html
page. These are the names:
out, arr1, arr2
, ..., ll (two small L), v, i (when passing through arrays. They screen the same external names. But
it
is on a special account, without it - nowhere, therefore rendered in the settings.
strip: true,
Discarding extra spaces, tabs and line breaks. If the formatting of the output text is not important, use true. The function and template will be shorter, and the compilation speed, however strange it may seem, is slightly lower (1-2%); performance - speed indistinguishable. Those. to speed up the compilation you need to set
strip: false
.
append: true,
The style of adding pieces of HTML code and data to the out variable is summation in a chain or assignment operators. The latter lengthens the text of the function, therefore, if it is not particularly needed, we select true. (Apparently, once it was a question - what to choose and what works faster.)
log: true,
Not used. Either you forgot to delete it, or you need it somewhere in neighboring scripts like NodeJS - express.
selfcontained: false,
Here is a little optimization story. The
doT.template(...)
function can be prepared in one common environment (
_globals
) and immediately executed as
doT.template(...)(...)
, or it can be
doT.template(...)(...)
separately (come by ajax or from a file) . In the latter case, you need true (extends the function doT.template (...)), and usually, in the first case - false. Then it is not necessary to generate too much in it, and the calculated is saved in
_globals._encodeHTML
generated from
_globals.doT.encodeHTMLSource()
, but not always, but only with the presence of
{{! }}
{{! }}
in templates.
In other words,
selfcontained = true
means that the template function
doT.template()
will be used separately from doT.js, so it must contain everything to perform the template. All - this means only a special case of coding HTML symbols with
{{!}}
Commands. If they exist, the function should include the definition of the encoding function - the string
doT.encHtmlStr
when it is created (this is done in clone 1.2.1, and in the original, the
encodeHTMLSource
function
encodeHTMLSource
converted to a string).
In version 1.1.1 of the original there is a flaw - the algorithm always “puts” the function code in the template, without compression, even if
selfcontained = false
, this had to be fixed. This function also binds the
doNotSkipEncoded
parameter
doNotSkipEncoded
time, although this is only necessary when creating a template function.
Then, in the original engine, there is a version conflict problem, because they use a global object (window, globals) to optimize the use of the HTML coding function. It was decided in clone 1.2.1 in that the global name of the encoding function was chosen dependent on the name of the engine and version. It turned out like this:
var encHt = '_'+dS.globalName + doT.version.replace(/\./g,''). ... encHtmlStr:'var encodeHTML=typeof '+ encHt +'!="undefined"?'+ encHt +':function(c){return((c||"")+"").replace(' + (dS.doNotSkipEncoded ?'/[&<>"\'\\/]/g':'/&(?!#?\\w+;)|[<>"\'/]/g') +',function(s){return{"&":"&","<":"<",">":">",\'"\':""","\'":"'","/":"/"}[s]||s})};'
We get the string to insert into the template function, but if
selfcontained = false
and there is
{{! }}
{{! }}
, then we limit ourselves to executing it in the global object, in order to use
encodeHTML()
from it.
doNotSkipEncoded: false,
Argument
doT.encodeHTMLSource()
. Works for functions
{{! }}
{{! }}
- issuing safe (without executable tags) HTML-code. If they are in any environment template, the first time
_globals._encodeHTML
function is
_globals._encodeHTML
generate safe characters to save its repeated calls. Made to solve such bugs:
github.com/olado/doT/issues/106 . If true, all codes of the form
"&....;"
are not encoded
"&....;"
, and the main result is non-encoding of the ampersend in
'&'
in such expressions.
Conclusion
For compilation speed, obviously, you need such parameters that require less action: if possible,
selfcontained = false
,
strip: false
,
append: true
. The remaining parts of doT are well optimized, the fastest solutions are selected on average. The speed of the versions depends on the specific type of templates, so the speed statement can only be averaged over the range of tasks.
doNotSkipEncoded
affects the result in the
{{!...}}
commands: if
true
coded characters of the form
&...;
.
In general, a clone compiles a template into a function somewhat slower due to the increased amount of code analysis that needs to be done to solve some bugs. For example, the unescape () function has been extended. If you remove the last two replace from it, the speed will increase by 3% (Chrome v.61 Canary), but there will be some bugs.
, doT.js — , , . ES6 — . , ( IE8). IE11 . test.index.html
performance.now()
.
• , DoT — DoT12:
https://jsfiddle.net/spmbt/v3yvpbsu/26/embedded/#Result • , DoT — DoT12:
https://jsfiddle.net/spmbt/v3yvpbsu/26/embedded/#Result :
https://jsfiddle.net/spmbt/v3yvpbsu/26/•
DoT12.js ( ),
DoT.js .
•
JSFiddle ( 2013 ) ( Dot; , ; — «Run»).
•
DoT.js 2013 .
*
Closure Compiler — Advanced mode, Uglify;
*
Using doT.js ( , 2012)
*
doT.js: chained if-else if in dot.js if-else;
*
, , , .