Javascript ES6 part 4 : template literals
Javascript

Javascript ES6 part 4 : template literals (template strings)

Template literals are a feature of ES2015 that you can’t ignore. Indeed, it add many useful things. For example, you can :

  • Multiline
  • Insert variables and/or expressions inside strings
  • Create tagged templates

First, multiline

Before, with classics strings, the only way to do multiline was to add the break line character. And to make it lisible, generally, we used concatenation. It might be something like :

const uglyString = "A useless paragraph"
  + "\nAnother useless paragraph";

Let’s see the new code for the same text, with template literals :

const betterString = `A useless paragraph
Another useless paragraph`;

Better, right? 🙂

Variables and expressions with template literals

With classics strings, to use variable, you must only use concatenation :

const name = "Kévin";
const dynamicString = "Hello, " + name + ","
  + "\nHow are you today?";

Verbose, and ugly right? So let’s take a look to the same with template literals :

const name = "Kévin";
const dynamicString = `Hello, ${name},
How are you today?`;

As you can see, it’s really clean, really better. Moreover, you can add more complex expressions instead of variables. Look at the following :

const users = [
  { name: 'Julian', age: 16 },
  { name: 'Yoan', age: 28 },
];

const getWelcomeMessage = ({ name, age }) => `Hello ${name},
You're so ${age < 21 ? 'cute, little child' : 'strong, big guy'}?
`;

console.log(getWelcomeMessage(user[0]));
// Hello Julian,
// You're so cute, little child?

console.log(getWelcomeMessage(user[1]));
// Hello Yoan,
// You're so strong, big guy?
Illustration : template literals are awesome

Another awesome feature : tagged templates

Maybe have you already seen syntax like variable`text`? For example, a well-known javascript library use them, it’s styled-components.

Okay, I will explain you how it work, but sometimes, examples are better than words.

function myTag() {
  return 'Bye!';
}

console.log(myTag`Hello!`);

In your opinion, what text will be displayed in the console? “Hello!”? You looooooose.

“Bye!” will be displayed! With tagged templates, the value of your string will be the return of the tag function.

Tagged template arguments

How to retrieve string from a tagged template? You have the first argument, it’s an array of strings, and it contains pieces of string exploded by expressions.

So, consider the following template strings :

`Hello ${'Kévin'}, how are you, during this ${'sunny'} day?`

In this example, the string will be divided like :

['Hello ', ', how are you, during this ', ' day?']

It will be the value of the first argument.

Others arguments will be passed to the function : it’s resolved expressions. So in this example, it will be 'Kévin' and 'sunny'.

But, don’t believe me, test it yourself!

const tag = (strings, firstname, weather) => {
  console.log(strings);
  // ['Hello ', ', how are you, during this ', ' day?']
  
  console.log(firstname); // Kévin
  console.log(weather); // weather
};

tag`Hello ${'Kévin'}, how are you, during this ${'sunny'} day?`

What? It’s not exactly what you see?! You’re right.

raw strings in tagged template literals
Here, the result of strings in the console

As you can see, there is a raw array at the end of the line.

Raw strings in tagged templates

So, we will log this array. Consider this code :

function tag(strings) {
  console.log(strings[0]);
  console.log(strings.raw[0]);
}

tag`string text line 1 \n string text line 2`;

The result of these console.log will be similars but with raw, special characters will not be escaped. The result :

raw strings example in template strings
raw strings example in template strings

Advanced template literals

For fun, now, let’s try with a more complex example. We will try to create a similar/ver light function as styled-components, but without react. It will generate pure html, not React components, and it will do it with template literals.

const styled = (() => {
  let id = 0;
  let allStyles = [];
  
  // Generate an unique className
  const generateClassName = () => `s${id++}`;
  
  // We just rebuild the css from the original string
  const interpolate = (strings, vars) => strings.map((string, index) => {
    return string + (vars[index] ? vars[index] : '');
  }).join(''); 
  
  // Minify final css
  const minifyCss = css => css.replace(/(\r\n|\n|\r)/g, '')
      .replace(/(\s+)/g, ' ')
      .replace(/;}/g, '}')
      .replace(/{\s/g, '{')
  
  const propsToString = props => {
    return Object.keys(props).map(key => ` ${key}="${props[key]}"`)
      .join('');
  };
  
  // We concat styles for all styled components
  const renderCss = () => {
    const css = minifyCss(
      allStyles
        .map(({ css, className }) => `.${className}{${css}}`)
        .join('')
    );

    return `<style>${css}</style>`;
  };
  
  // The proxy is here to let us use fake properties on the styled object
  // So, we can use all html tags as we want
  return new Proxy({}, {
    get: (target, tagName) => function () {
      if (tagName === 'renderCss') {
        return renderCss();
      }

			const [strings, ...vars] = arguments;
      const generatedClassName = generateClassName();
      const css = interpolate(strings, vars);

      allStyles = [ ...allStyles, { css, className: [generatedClassName] } ]

      return (children, properties = {}) => {
        const { className = '', ...props } = properties;

        const classProp = `class="${generatedClassName}${className && ` ${className}`}"`;
        const otherProps = propsToString(props);
        
        return `<${tagName} ${classProp}${otherProps}>${children}</${tagName}>`;
      };
    }
  })
})();

const App = styled.div`
  margin-top: 10px;
  background-color: ${'blue'};
`;

const Hello = styled.span`
  color: ${'red'};
  font-weight: bold;
`;


App(
  Hello(`Hello ${'World'}`),
  {
    id: 'app'
  }
)
+ styled.renderCss()

You can test it on jsfiddle