String.identity template string tag

So that we don't have to write this:

function identityTemplateTag(stringsParts, ...values) {
    let str = ''
    for (let i = 0; i < values.length; i++) str += stringsParts[i] + String(values[i])
    return str + stringsParts[stringsParts.length - 1]
}

Plus a built-in option could be more optimized.

This

const result = String.identity`
  value: ${someValue}
`

would be essentially be identical to:

const result = `
  value: ${someValue}
`

but then it is easy to simply opt into syntax highlighting in certain IDEs:

const html = String.identity

// Various syntax highlighters will now highlight the content of
// the string as HTML code, out of the box:
const result = html`
  <div>foo</div>
`

Notice how even ES.Discourse's own syntax highligher highlighted the html content!

Implementation here: highlight.js/src/languages/javascript.js at 9267f5022c14bcd848b8a9024f8c88230fcf7b54 ยท highlightjs/highlight.js ยท GitHub

I think there are IDE extensions that can do the same but with a magic comment:

let t = /* html */`<div></div>`;

If your template contains a DSL, shouldn't you use String.raw for that? In HTML, \n is not a newline, it's a backlash followed by a letter.

MDN provides a one-liner for your escaped-identity:

3 Likes

Ah, that's the word, "cooked"! Thanks!

1 Like

IIRC that was slower than plain-tag but snippet on MDN is indeed an easy way to obtain the same:

const tag = (strings, ...values) => String.raw({ raw: strings }, ...values);

Probably time to ditch plain-tag in favor of this ... thanks for the hint.


edit plain-tag in a tweet:

function tag(t) {
  for (var s = t[0], i = 1, l = arguments.length; i < l; i++)
    s += arguments[i] + t[i];
  return s;
}

The plain-tag one only works on "good-faith" input and doesn't deal with the tag not being used as a tag. But more importantly it uses the wrong coercion semantics too, for example you can't embed a Temporal instance and expect it to be coerced to string, because they have functional toString but throwing valueOf, and addition would call valueOf before toString.

1 Like

it's been working well for 4 years but yes, it doesn't expect fancy interpolations ... although all its dependent projects never complained about anything, it's supposed to be as minimal and as fast as possible. I will benchmark against the MDN suggested workaround though and update it if that results faster than it is now.

FYI these are the results:

node

plain: 0.649ms
fake: 2.689ms
noop: 0.903ms
std: 0.922ms

bun

[1.54ms] plain
[2.59ms] fake
[3.01ms] noop
[2.58ms] std

the std implementation is:

const { raw: $ } = String;
const std = (raw, ...values) => $({ raw }, ...values);

In node the difference is not too bad but in bun it's 1 whole ms and that's concerning.

In browser I can observe similar slowness of the raw suggestion ... it's 0.5 up to 4ms slower compared to plain tag.

Absolute differences in a micro-benchmark. I see ~50% slower and that may be acceptable, depending on use case. Btw your benchmark.js measures noop twice, and doesn't measure std.

1 Like