◆ タグ側で処理する段階で単純に文字列埋め込みするだけの部分は埋め込み済みになってるようにしたい

やりたいこと

タイトルじゃイマイチ何言ってるのかわからないと思うので例です

const x = "foo"
const y = "bar"
const value = tag`<div class="${x}">${y}</div>`

のようにテンプレートストリングを使うとき tag の機能で可変部分の x や y に特殊な処理する場合があります
x の方は単純に埋め込みたいだけで y だけを対象にしてほしいということは少なからずあります
tag 側で単純埋めこみと特別な処理をする場合が分けられていない場合は困ります

ただの文字列が返ってくるなら tag を使う部分を分けて

`<div class="${x}">` + tag`${y}</div>`

とすればいいのですが tag が返す値はただの文字列じゃないときには 全体をまとめて tag に渡さないといけないこともあります

つくったもの

これに対処したいなと思ってこういうものを作ってみました

const stable = Symbol()
const emb = x => ({ [stable]: String(x) })

const cache = new WeakMap()

const pass = (strs, ...values) => {
if (!cache.has(strs)) {
const new_strs = [strs[0]]
const new_raw = [strs.raw[0]]
const new_value_indexes = []
for (let i = 1; i < strs.length; i++) {
const value = values[i - 1]
if (value instanceof Object && stable in value) {
const last = new_strs.length - 1
const value_str = String(value[stable])
new_strs[last] += value_str + strs[i]
new_raw[last] += value_str + strs.raw[i]
} else {
new_strs.push(strs[i])
new_raw.push(strs.raw[i])
new_value_indexes.push(i - 1)
}
}
Object.freeze(new_raw)
new_strs.raw = new_raw
Object.freeze(new_strs)
cache.set(strs, { new_strs, new_value_indexes })
}
const { new_strs, new_value_indexes } = cache.get(strs)
return [new_strs, ...new_value_indexes.map(e => values[e])]
}

export { stable, emb, pass }
単純に文字列埋め込みをするときは emb 関数を通します

import {emb, pass} from "./pass-tag.js"

const x = "foo"
const y = "bar"
pass`<div class="${emb(x)}">${y}</div>`
// [["<div class="foo">", "</div>", raw: Array(2)], "bar"]

タグ機能を使った場合に引数に渡される形式の配列で返ってきます
なので本来渡したいタグ関数が tag なら

tag(...pass`<div class="${emb(x)}">${y}</div>`)

とすれば可変部分は y だけとしてタグ関数に渡せます

固定の部分なのにタグ関数側で毎回文字列同じ文字列を処理してほしくないという目的なので同じテンプレートでの埋め込み部分は毎回変更されず最初の実行時に固定されます

pass`<div class="${emb(1)}">${y}</div>`[0]
// ["<div class="1">", "</div>", raw: Array(2)]

pass`<div class="${emb(2)}">${y}</div>`
// ["<div class="2">", "</div>", raw: Array(2)]

const fn = value => pass`<div class="${emb(value.x)}">${value.y}</div>`[0]
fn({x: 1, y: 100})
// ["<div class="1">", "</div>", raw: Array(2)]

fn({x: 2, y: 200})
// ["<div class="1">", "</div>", raw: Array(2)] <-- class="1" になる

HTML を作るテンプレートでクラス名は全部文字列埋め込みだけど 数が多くて全部に処理させたくないなんてときには良いと思います