SECRET OF CSS

How to “bake” a Javascript HTML Template Engine in Three Lines of Code | by Riccardo Marzi


Use regular expression, template strings, and dynamic functions to manage HTML template strings in a simple and fast way

Ingredients: Function class, Regular Expressions, Template Literals, and some curiosityCooking time: 10 minutes

There are several JavaScript template engines available on the web. Ranging from those that make performances their strong point, to those that emphasize syntax and code comprehensibility.

Beyond these aspects, there are very good products that are definitely the ideal choice in the case of projects that make intensive use of text templates.

Of course, it won’t be something written in three lines to compete with much more powerful systems, but it can be useful and interesting to consider other mechanisms for producing text starting from a template.

In addition, something cooked off the menu might taste interesting.

Normally a template engine provides its own syntax, which uses to call specific operations, such as a for loop that allows us to repeat text by varying the values of the variables involved in the iteration. Having its own syntax, it’s normal that every engine must implement a parser, and other functionalities, to be able to interpret correctly the instructions and consequently produce the text we expect in output.

However, the title of the article talks about three lines of code. So, how is it possible to implement a parser and all the functionality needed by an engine in just three lines of code? Well… it is not possible, but this is not a problem.

Actually, we already have a pretty good interpreter available and it is the same JavaScript runtime where our code runs. If we add the Template Strings of ES6 and a pinch of Regular Expressions, here we have all the ingredients for our compact engine.

For more details:

Let’s say we have a data structure and we need to produce some HTML code by injecting values into it and also having to repeat blocks of code. Clearly, each repeated code block will have to inject different values read from the source data.

Data structure:

What we’d like to achieve:

Obviously, the solution is not to create a trivial JavaScript function were to write some code that concatenates strings, reading the passed data. Of course, this type of solution would produce a good result, but it is not versatile at all and it is far from the concept of a template engine.

We want to have our template inside a JavaScript string containing some text, in this case, HTML code, together with some JavaScript code that can perform iterations on the data and other operations typical of the language.

Oh, I forgot, we don’t even want to write a parser! (at least not today).

Let’s try to decompose the parts involved taking into consideration the starting data and the result we want to obtain.

We have the first piece of data that we want to produce in the output: the TV series title. In this case, it’s a “one-off” script that only requires injecting the value of the title property inside a <div> tag.

Our template might start like this:

<div class="title">{= data.title =}</div>

We immediately notice the use of a particular syntax within the tag and, in turn, within this, a typically JavaScript expression for accessing a title property of an object named data.

Let’s analyze these first elements immediately:

The syntax {= ... =} allows us to introduce an instruction inside the template in order to resolve a value reading it from the passed data contextually to the call of the next generation.

The identifier data refers to the root of the passed object, which could be a JavaScript object, an array, or any other value. In our case, it is an object which contains a first property title, which is the one we are interested in resolving as a value to be inserted in the output.

The choice of using {= and =} as delimiters is entirely arbitrary. You can use any character composition you want as long as it doesn’t go in conflict with other parts of the template. That is, there are no ambiguities with other possible elements of the template that should end up in the output without being transformed.
For example, if we trivially used the characters < and > it would be more difficult to distinguish a placeholder of a value than an output HTML tag.

At this point we need to transform the child elements of our “tv series” object, scrolling through the episode’s names and inserting them into the output inside a div tag.

But before we decided that the child elements must reside inside an additional div tag representing the list of episodes. Therefore we should add the following opening tag to the template:

<div class="episodes-list">

This part will be printed in the output without any transformation.

Different conditions for the next part which instead will have to vary from what is expressed in the template. We’ll have to declare a variable containing the episode number (which will be dynamically calculated by incrementing the variable in the loop) and, to scroll the episodes, we’ll need some JavaScript code as a loop for..of.

In our template, we will add the variable and the initial part of the for..of construct:

let episodeNumber = 1;
for (let episodeName of data.episodes) {

Again we refer to the main object data of which we browse the array contained within the episodes property.

Inside the loop, we should print the HTML code with the episode number and the relative value of episodeName, as the nth element of the iteration.

<div class="episode-name"><span>{= episodeNumber =}</span><span>{= episodeName =}</span></div>

We increment our variable and conclude by closing the for..of construct:

episodeNumber++;
}

Finally, we also close the div tag of the chapter list, opened before the for..of loop.

</div>

Putting it all together let’s imagine that we have built the following template:

At a first glance, it might seem all right but in this way our “engine” is not yet able to distinguish output text from JavaScript code, so we have to take a small step back to modify our template by inserting a delimiter (one or more characters) that indicates the transition from static text to JavaScript instructions and vice versa.

The criterion to choose such a delimiter can vary on several factors, such as the type of output text (HTML or other) or for example the position where our text is located (in an external HTML file, inside a multi-line JavaScript string, etc…).

For this specific simple case, I’ve chosen to use the sequence of characters ## because visually it highlights the transition from HTML code to JavaScript code and because it could hardly conflict with anything else.
At this point our template should look like this:

Or this way if we find it more readable (the two blocks of code are equivalent).

Nothing more. At the template level, nothing else is needed to make our “engine” do its job.

In this way, it will be able to separate the text from the instructions with very few operations and transform it into the final text.

So far we have only seen the part related to the template writing technique, but what was promised at the beginning was the recipe to create a template engine in only three lines of code, so it’s time to move to the part where we actually “cook” our engine.

The steps required are few and concise:

  1. Post-processing of the template string:
    A. adds at the beginning and at the end of the template text the same delimiter used to separate static code from JavaScript instructions.
    B. replaces, with two different regular expressions, the delimiters with specific text (which we will see shortly).
    C. replaces, again by regular expression, the placeholders of the values {= exp =} with the same expressions in the form ${exp}.
    E.g.: {= data.title =} becomes ${data.title}.
    D. removes any orphan delimiters at the beginning and at the end of the template string.
  2. Dynamically produces a Function that takes care of transforming the text.
  3. Calls the Function passing it the object used as an argument and produces the final text.

Let’s see everything inside a simple function that accepts two arguments: the first one is the template text (static text and possible JavaScript instructions), and the second one is any data object from which the values will be read.

Ok, okay. I said three lines and here we have at least 20… but if we remove all the unnecessary comments and carriage returns, what’s left is

Ready on the table!

Some of you may have noticed that I could have written “…in a single line of code” (which is possible!) but perhaps, given the illegibility of the code produced, it would have been to clickbait.
Anyway 😉

Let’s see what happens in detail in single steps, even seeing how our template text transforms from time to time.

We start from this condition

We add delimiters at the beginning and at the end

We use the regular expression ##\s*< to capture the pattern ## + (space or empty) + < and to replace them with (new line) + out.push(`<, and the regular expression >\s*## for patterns > + (space or empty) + ## replacing them with >`); + (new line). What we will get is:

At this point there is the last post-processing step which consists in replacing our placeholders (if any) with the placeholder format managed by the JavaScript template literals. We are going to replace the characters {= with ${ and =} with }, in order to obtain:

With this last substitution we have transformed our template into correct JavaScript code. Now we only need to insert it into a dynamic function that is able to execute it and, finally, call the function to produce the final result.

The Function class of the JavaScript language will be used to generate the function, through which it is possible to define as string, both the parameters that it will accept, and the body of the function itself.

For more details:
Function MDN

This is our reference code:

I have inserted two lines of delimiting comment in order to compare it better with the final function produced

We are creating a dynamic function through the Function class, to which we are passing a first string that represents the name of a parameter, and a second (last) argument that represents the body of the function. We can see the use of the multiline string, for the definition of the body, which allows us to insert a placeholder that calls the template transformed previously.
What the runtime will generate in memory will be something like this:

The composeFunc constant will contain the reference to the function, which you can call like any other explicitly declared function.
Therefore:

return composeFunc(data);

will return the final text we set out to get:

which rendered with simple styles will look like this:

.title {
font-weight: 700;
font-size: 1.2em;
margin-bottom: 10px
}
.episode-name > span:first-child {
display: inline-block;
text-align: right;
font-weight: 500;
margin-right: 20px;
min-width: 2ch;
}
1*sD WpVlxFm13Fa2UpML8 w

We have seen how to use regular expression, template strings and dynamic functions to manage HTML template strings in a simple and fast way.
It is right to specify that what we have realized, although versatile, is far from being considered a real template engine. It can be a good starting idea but it would be necessary to introduce many other features and validations that would make the system more practical and secure.

What we have done is more an exercise in style that allows us to “get our hands dirty” and see closely how some features work when combined together.

As with any new dish, the first realization often brings with it flaws and approximations. But the beauty of experimenting is learning new things, both when the ingredients combine well and when they don’t. This doesn’t take away from the fact that recipes can be improved and, sometimes, flaws can be transformed into strengths. Um… I already have something in mind, I think we’ll revisit it in the future.

Stay curious, stay creative!

A big thank you to my friend Chiara Bernardini for her support (L)



News Credit

%d bloggers like this: