Tagged Template Literals Are the Worst Addition to Javascript

What are tagged template literals?

I will borrow the excellent straightforward definition from Exploring JS:


The following is a tagged template literal (short: tagged template):

tagFunction`Hello ${firstName} ${lastName}!`

Putting a template literal after an expression triggers a function call, similar to how a parameter list (comma-separated values in parentheses) triggers a function call. The previous code is equivalent to the following function call (in reality, first parameter is more than just an Array, but that is explained later).

tagFunction(['Hello ', ' ', '!'], firstName, lastName)

Thus, the name before the content in backticks is the name of a function to call, the tag function. The tag function receives two different kinds of data:

Additionally, tag functions get two versions of each template string:


This is literally all there is to tagged template literals. It’s a function call with an array of strings (each string is in two versions), and an array of substitutions between the elements of said strings.

What are tagged template literals presented as?

I believe this is the future of JSX, and it’s only just getting started. There are some very interesting optimizations Tagged Templates make available, like identifying and hoisting static parts of a view (essentially memoizing them).

Jason Miller

Writing HTML in HTML files, writing code between <script> tags, writing Javascript in .js files is the same as writing code in string blobs and parsing them at runtime.

A conversation among several people

These are tagged templates, not strings. They work as code at runtime, and it already has support to optimize it, compile it, etc. This is JS all the way down.

magicalist

You parse the string literal, just like you parse the string that is the contents of a JavaScript file.

spankalee

There are many more, but they all basically come down to the same thing: there’s no difference between a JS-file loaded into and run by the JS VM, and run-time function calls with arrays of opaque strings. And this is the future, and it’s amazing.

So why do I think these are bad?

Once again, I’ll steal a quote:

…we’ve learned not to write code in strings. You’d think this would be a fundamental law of software engineering

Writing code, not strings, means you can do everything to it that you can do to code. You can type check code. You can lint it. You can optimize it, compile it, validate it, syntax highlight it, format it with tools like Prettier, tree shake it…

stevebmark

In a language that’s already a laughing stock for its insane dynamic type system, we said: “It’s all right, we’ll have all our code in strings now, thank you very much, and we’ll make sure we parse it at runtime because what can possibly go wrong”.

The only reason projects like lit-html, HTM and some others can boast about their “accomplishments” building fast, effecient libraries is because string handling has been insanely optimised by the modern Javascript VMs. And that .innerHTML is no longer the slowest operation on the DOM. Let’s look at some examples, shall we?

Examples

lit-html

Project: https://github.com/polymer/lit-html, https://lit-html.polymer-project.org.

Tagline: “An efficient, expressive, extensible HTML templating library for JavaScript.”

Alternative tagline: “Next-generation HTML Templates in JavaScript.”

Sample code:

html`<h1>Hello ${name}</h1>`

html`<input .value=${value}>`

html`<button @click=${(e) => console.log('clicked')}>Click Me</button>`

Yup. It’s not just template literals. It’s an additional templating syntax on top of template strings.

lit-html literally parses HTML with regular expressions, appends a bunch of strings together, and then just dumps them into the DOM via .innerHTML.

NΘ stop the an​*̶͑̾̾​̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s ͎a̧͈͖r̽̾̈́͒͑e n​ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ.

HTM

Project: https://github.com/developit/htm

Tagline: “Hyperscript Tagged Markup: JSX alternative using standard tagged templates, with compiler support.”

Alternative tagline: “htm is JSX-like syntax in plain JavaScript - no transpiler necessary.”

Sample code:

html`
  <div class="app">
    <${Header} name="ToDo's (${page})" />
    <ul>
      ${todos.map(todo => html`
        <li>${todo}</li>
      `)}
    </ul>
    <button onClick=${() => this.addTodo()}>Add Todo</button>
    <${Footer}>footer content here<//>
  </div>
`;

JSX-like syntax in plain Javascript is somewhat of a lie. That’s HTML(-like) syntax inside plain strings that are processed by plain Javascript at run time. So I guess it still makes this plain Javascript I guess 🤷‍♂️?

When I first heard of HTM, it looked something like this. Of course, it was doing exactly the same thing as lit-html: regular expressions, string blobs, and innerHtml before it got React/Preact integrations.

TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ.

Thankfully, the current version is smarter and smaller. It creates what is essentially an AST of the parsed structure that’s then passed on to the actual rendering function. However it’s still the same thing: parsing a bunch of opaque strings at run time. Opaque because neither the JS VM nor the browser know what’s in those strings.

And I mean… Why parse strings and build (potentially huge) ASTs at runtime when you can do actual function calls which, you know, can be optimised by the VM etc.? But I digress.

Styled Components

Project: https://github.com/styled-components/styled-components, https://www.styled-components.com

Tagline: “Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅”

Sample code:

const Button = styled.a`
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  ${props => props.primary && css`
    background: white;
    color: palevioletred;
  `}
`

The codebase for Styled Compomnents is huge.

It maintains its own list of tags (new tags and web compoinents are out of question?), it will generate and inject styles on render by going to great lengths to figure out what the hell we got passed, there are some regexps here and there.

Well, you got the gist. String blobs are being painstakingly parsed, and then reassembled.

Others

There are other projects like sql-tag or node-sql-template-strings. There’s common-tags.

They all do the same thing: the parse the strings at runtime. In many cases they concatenate some strings or produce some objects. They return some result or just directly dump it into the DOM.

Why is this bad again?

Because this isn’t code. This is literally taking a bunch of opaque string blobs, parsing them at runtime, and producing some result. All of programming has been busy moving away from coding in strings and parsing stuff at runtime. For the past few years Javascript has been happily re-introducing the worst programming practices. And devs get away with it, too, just because modern Javascript VMs and browser DOM are optimised way more than they have any right to.

Are they DSLs or embedded DSLs? They are probably weak embedded DSLs, but I’ll let people more knowledgeable than I decide.

They also lead to some horrible developer experiences. You cannot place a breakpoint on a string. You can place a breakpoint on the interpolated expressions inside one, or on the actual tagged literal function call, but good luck figuring out if you made a type somewhere.

Since these are just arbitrary strings, no common tools will be able to lint them, analyse them, optimise them unless you write a very specific tool for this particular very specific string structure. And yes, that includes JS VMs.

As you’ve seen above, people are in all seriousness talking about re-implementing optimisations that compilers already do for actual code: memoisation, inlining, optimising common paths, code elimination. Pure madness.

You wanted macros? Here, have run-time string concatenation and regexp parsing, and stringly-typed everything.