JavaScriptのテンプレートエンジンをつかって簡易テンプレートエンジンをつくる 後編

この記事は次の記事の続きです。

https://qiita.com/ledsun/items/7073c673d8c7b35fda94

前編では、JavaScritのタグ付きテンプレートをつかって、自動的にHTMLエスケープをするテンプレートエンジン 3号を作りましたた。

## 課題

次のような課題が残っています。

1. 「埋め込み式」がHTML文字列を返すときに、テンプレートエンジン 3号を使うと、エスケープしなくてよいHTML文字列をエスケープする
1. 特に、部分テンプレートを使う時に、HTML文字列が返ってくるのかエスケープしていない値が返ってくるのか、判別がややこしい


テンプレートエンジン 3号に渡す文字列が、エスケープが必要かどうかの情報を付加したいです。
に何かしらのフラグを持たせたいです。

## 案1 エスケープ済みフラグ

例えば テンプレートエンジン で生成する文字列にエスケープ済みのフラグを付けてはどうでしょうか？
つぎのように、テンプレートエンジンが返す文字列にプロパティを追加します。

```javascript
import escape from 'lodash.escape'

function autoEscape(strings) {
  const [first, ...rest] = strings
    
  let out = first
  for (const value of [...arguments].slice(1)) {
    if (value.isAlreadyEscaped) {
      // すでにエスケープ済みのとき
      out = `${out}${value}${rest.shift()}` 
    } else {
      out = `${out}${escape(value)}${rest.shift()}` 
    }
  }

  out.isAlreaddyEscaped = true
    
  return out
}
```

残念ながら、これは上手く動きません。
JavaScriptではプリミティブ型にプロパティを追加できません。
文字列はプリミティブ型なのでプロパティを追加できません。
次のように、Stringクラスのインスタンスにするとプロパティを追加できます。

```javascript
import escape from 'lodash.escape'

function autoEscape(strings) {
    const [first, ...rest] = strings
    
    let out = first
    for (const value of [...arguments].slice(1)) {
        if (value.isAlreadyEscaped) {
            out = `${out}${value}${rest.shift()}` 
        } else {
            out = `${out}${escape(value)}${rest.shift()}` 
        }
    }

    out = new String(out) // Stringのインスタンス化
    out.isAlreaddyEscaped = true
    
    return out
}
```

まあ、でもテンプレートエンジンの出力した文字列に、テンプレートエンジン独自のフラグが入っているのはかっこ悪いですよね。
JavaScript標準の文字列が返るべきですよ。

## 案2 アロー関数式

案1では、テンプレートエンジンの出力にフラグを立てました。
では、テンプレートエンジンの入力にフラグを立てられないでしょうか？
次のイメージです。

```javascript
autoEscape`
  <ul>
    ${ new EscapedString(items.map(item => `<li>${item}</li>`)) }
  </ul>
`
```

これならテンプレートエンジン内で、型を判定して分岐出来そうです。
しかし、`new EscapedString()`で、かこうのはちょっと面倒ですし、かっこよくないです。
できれば、頭にマークを付けるくらいで、フラグを立てられないでしょうか？

できます。

まず、テンプレートリテラルに渡される`$hoge`は埋め込み式です。
そう式なんです。
一方、JavaScriptにはアロー関数式と呼ばれる式があります。
代表的な記法が

```
() => 式
```

です。
そう、式の頭に`() =>`がついた式です。
これを使えば、頭にマークを付けるくらいで、フラグを立てられます。
次の書き方を想定しています。

```javascript
autoEscape`
  <ul>
    ${ () => items.map(item => `<li>${item}</li>`) }
  </ul>
`
```

`new EscapedString()`で、かこうのにくらべてシンプルになりました。

アロー関数式は式です。そのまま埋め込み式として使えます。
また、アロー関数式は値ではなく関数を返します。
テンプレートエンジンは、埋め込み式で渡された値が「関数であればエスケープしない」と判断できます。

```javascript
import escape from 'lodash.escape'

function autoEscape(strings) {
    const [first, ...rest] = strings
    
    let out = first
    for (const value of [...arguments].slice(1)) {
        if (typeof value === 'function') {
            // 関数のときは、エスケープしない。関数を実行して値を取り出す。
            out = `${out}${ value() }${rest.shift()}` 
        } else {
            out = `${out}${escape(value)}${rest.shift()}` 
        }
    }
    
    return out
}
```

テンプレートエンジン 4号の完成です。

## 使用例

### HTML文字列を埋め込む

```javascript
function renderHTML(name, email, selfIntroduction) {
    return autoEscape`
        <div class="profile">
          <div class="name">${name}</div>
          <div class="e-mail">${email}</div>
          <div class="badge">${() => badge}</div>
        </div>
    `
}

element.innerHTML = renderHTML('ledsun', 'ledsun@example.com', '<img src="..."></a>')
```

埋め込み式として渡す、値が安全なHTML文字列だと知っている場合は、冒頭`() =>`をつけると二重エスケープが回避できます。

### 部分テンプレート

部分テンプレートを使う場合も、次のように書けます。

```javascript
// 部分テンプレート
function renderList(item) {
  return () => autoEscape`<li>${(item)}</li>`
}
```

部分テンプレートは自身がエスケープ済みの文字列を返すか知っています。
エスケープ済みの文字列を返すときは関数を返します。

```javascript
function renderHTML(items) {
  return autoEscape`
    <ul>
      ${ items.map(renderList) }
    </ul>
`
}

element.innerHTML = renderHTML(['リンゴ', 'みかん', 'なし'])
```

部分テンプレートを埋め込むテンプレートは、埋め込まれているものがエスケープ済みの文字列を返すかどうか気にせず埋め込むことが出来ます。

## まとめ

`() =>` つけるの、かっこいいでしょう？

## 参考

- [アロー関数式 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Functions/Arrow_functions)