A few years ago, ES6 introduced template literals, allowing among other things for multi-line strings, embedded expressions, and string interpolation.
That means that the following snippets of code could be written as follow:
console.log("This is the first line of a multi-line string.\n"+"And this is the second line!");console.log(`This is the first line of a multi-line string.
And this is the second line!`);
consta=22;constb=20;console.log("The answer to the ultimate question of life, the universe, and everything is "+(a+b)+"!");console.log(`The answer to the ultimate question of life, the universe, and everything is ${a+b}!`);
Template literals are already fairly useful with the above syntactic features, but there is more: template literals can be tagged!
Template tags are (mostly) functions that take in an array of strings as their first argument, and all expressions as the following arguments. Tags can then parse template literals as they see fit and return whichever value they see fit (not limited to strings).
constname1="Alice";constname2="Bob";functionmyTag(strings,fromName,toName){console.log(strings);// ["Template literal message from", " to ", " ..."]console.log(fromName);// "Alice"console.log(toName);// "Bob"...}console.log(myTag`Template literal message from ${name1} to ${name2} ...`);
If no tags are provided to the template literal, the default tag simply concatenates the strings and expressions into a single string, for example:
functiondefaultTag(strings,...expressions){letstr="";for (leti=0,l=strings.length;i<l;i++){str+=strings[i]+(expressions[i]!=null?expressions[i]:"");}returnstr;}constname1="Alice";constname2="Bob";consta=22;constb=20;console.log(defaultTag`Template literal message from ${name1} to ${name2}: 'The answer to the ultimate question of life, the universe, and everything is ${a+b}!'`);// "Template literal message from Alice to Bob: 'The answer to the ultimate question of life, the universe, and everything is 42}!'"
Note that there will always be more strings than expressions as empty strings will be added around expressions.
Now, we can probably build something a bit more interesting than just the default tag being applied to templates without tags!
Let's build a template tag that would allow us to format currency and numbers in certain ways. To better understand what we will build, let's look at an example:
constname="Alice";constnumber=42;constprice=20;console.log(fmt`${name}:s has ${number}:n(1) oranges worth ${price}:c(USD)!`);// "Alice has 42.0 oranges worth US$20.00!"
Here, we specify that the value interpolated by ${name} should be treated as a string, the value interpolated by ${number} should be displayed as a number with one digit, and that the value interpolated by ${price} should be displayed with the USD currency, all that while respecting the user's locale.
First, we need to define a way to extract the formatting information from the string literals:
As an aside, every time I'm using regular expressions I am reminded of the following quote:
Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems. - Jamie Zawinski
But anyway, here we use a regular expression to match strings with our previously defined format, starting with : then a lower case letter, then an optional extra information in parenthesis.
The extractFormatOption() function simply helps us return the value of format and whatever option might have been passed as well. For example:
const{format,option}=extractFormatOption(`:c(USD)!`)// format = "c"// option = "USD"
Next, we need a way to actually format those values. We will use an object whose fields correspond to the potential values of format.
Here we do a look-ahead and extract any format and option indications in the template literal (which default to "s"), then apply the corresponding formatter to the current expression we are interpolating.
As I found that exercise actually quite useful, I've published an npm package with more formatting options:
Template literals and template tags provide a unique API to build tools around strings.
What started as a fun blog post about template tags ended up being this full-fledged library that might hopefully be useful to someone!
Usage
You can use this library either as an ES module or a CommonJS package:
importfmtfrom"fmt-tag";
- or -
constfmt=require("fmt-tag");
Please note that this library uses extensively Intl, which is not supported on older browsers (https://caniuse.com/?search=Intl) or Node versions < 16.
You can tag any template literal and append formatting hints right after interpolations to apply specific formatting to that substitutive value.
constname="Alice";constmoney=20;console.log(fmt`${name} has ${money