As mentioned in the previous article, HTM uses the Tagged Template Literals feature of JavaScript. Template Literal and tagged Template literals are two new literals introduced in ES2015. They look very similar, but in reality they are very different. We use more template literals, which are string literals that support interpolation and allow multiple lines. Tagged Template Literals, by contrast, is a low-key function call.

I’m sure most of you are already familiar with Template literals, so let’s focus on tagged Template. Tagged Template has not been used much, but it is familiar to those familiar with CSS-IN-JS. Styled – Components, as the influential CSS-in-JS library, uses the tagged Template syntax. Here is an example of a tagged template:

tagFunction`Hello ${firstName} ${lastName}! `;
Copy the code

It looks like a function in front of the Template string. This actually triggers a function call. The code above can be seen as such a call:

tagFunction(["Hello".""."!"], firstName, lastName);
Copy the code

The function called before the template string is called a tag function. The parameters accepted by tagged function can be separated into two parts: Template strings and substitutions. Let’s take a concrete example:

function tagFunction(tempObj, ... subs) {
  console.log(tempObj, subs);
}

const name = "World";

tagFunction`Hello ${name}! `;
// 输出: [ 'Hello ', '!' ] [ 'World' ]
Copy the code

Although the first parameter looks like a normal array, it also has a RAW property that holds the RAW version of the Template String. So what’s different about the RAW version? In fact, they are the same in most cases, except for the handling of the escape character \ :

function tagFunction(tempObj, ... subs) {
  console.log(tempObj);
  console.log(tempObj.raw);
}

tagFunction`Hello\${`;
tagFunction`Hello\``;
tagFunction`Hello\n`;

// The output is
// [ 'Hello${' ]
// [ 'Hello\\${' ]
// [ 'Hello`' ]
// [ 'Hello\\`' ]
// [ 'Hello\n' ]
// [ 'Hello\\n' ]
Copy the code

In the “cooked” version, \ serves:

  1. To prevent theThe ${Resolved to the beginning of Substitution
  2. will`Escape to a normal character
  3. and\As in String literals, willnxSuch characters escape to special characters

In the RAW version, \ also has the first two features. But what’s special is that even in this case, the escape function was retained in the RAW version. In other cases \ has been replaced with \\. That is, all \ are escaped as plain backslashes.

Tagged Templates is great for writing DSLS. Styled components and HTM (lite-html) mentioned earlier can be thought of as DSLS implemented in JavaScript. So let’s try to implement a DSL.

Markdown is a lightweight markup language that gives people the ability to write documents in plain text format. Markdown has become a standard feature of text editing software because of its easy-to-use syntax. This text, for example, was written using Markdown. Let’s try embedding Markdown in JavaScript.

import markdownIt from "markdown-it";

const md = new markdownIt();

function mark(template, ... substitutions) {
  const raw = template.raw;

  let result = "";

  substitutions.forEach((substitution, idx) = > {
    const t = raw[idx];

    result += t;
    result += String(substitution);
  });
  result += template[template.length - 1];
  const html = md.render(result);

  return String(html);
}
Copy the code

In the above code, a simple tag function mark is implemented. Its job is simply to convert the passed substitutions to a string and concatenate it with template strings to call it a complete Markdown string. This string is then converted to an HTML string using markdown-it. Here’s how it works:

const name = "Gary";
const html = mark`Hello *${name}* `; // "<p>Hello <em>Gary</em></p>\n"
Copy the code

One might wonder if this is any different from the following code:

import markdownIt from "markdown-it";

const md = new markdownIt();

const name = "Gary";
md.render(`Hello *${name}* `); // "<p>Hello <em>Gary</em></p>\n"
Copy the code

For this example, the output of the code from both versions is indeed consistent. But they are fundamentally different. As mentioned earlier, Template literals is a syntactic structure that produces a string, similar to string literals. Tagged Templates triggers a function call. That is, Md. render receives a string, while Mark receives Template strings and substitutions. This means that Mark has the ability to do various conversions and manipulations to the incoming substitutions that MD. render cannot. For example, Mark can do special handling for the array type substitutions:

if (Array.isArray(substitution)) {
  substitution = substitution.join("");
}
Copy the code

so

const names = ["Gary"."Tomas"];

mark`Hello *${names}* `; // "<p>Hello <em>Gary Tomas</em></p>\n"
Copy the code

The Md. render version returns

Hello Gary,Tomas

\n. This is because names are converted to strings before being concatenated. Tagged Template version of Mark has another advantage. Notice that we’re taking the raw version of template Strings. This leads to the following differences:

mark`\# Hello *World*`; // "<p># Hello <em>World</em></p>\n"

md.render(`\# Hello *World*`); // "<h1>Hello <em>World</em></h1>\n"
Copy the code

Note: \ also has the function of escaping in markdown.

In the Mark version, the escape character works correctly, preventing # from being used as the starting character of Heading. In md.render, \ does not work. This is because \ also acts as an escape character in Template literals. For template literals, \# is #. So the string mD. render gets is actually # Hello *World*. For \ to work correctly in markdown syntax, we need to escape \, passing \\# Hello *World*. Md. render now returns the same value as the Mark version. This is like having to use new RegExp(“\\\\”) to match a string containing \. Mark’s full code is on GitHub.