💭

【番外編】HTMLを文字列で組み立てないようにしよう

2021/01/05に公開

これは本当は『保守性の高いソフトウェア開発のTips集』に載せようと思ったのですが、
世間では全くベストプラクティスとされていないと思うので番外編にします。

https://zenn.dev/riku/books/36d9873ee1c0e6

問題意識

HTMLを文字列で組み立てるとは

HTMLを文字列で組み立てるとはこれを指すとします。

  • +演算子などを使ってstringでHTMLを組み立てる
  • 各言語やライブラリのテンプレート機能を使う
    • JSP
    • Node.jsのEJS
    • PHPでHTMLとPHPタグを混在させる
    • Goのhtml/template

これらは使わない方がいいと思います。

+演算子で組み立てない方がいい理由

XSS対策

XSSのリスクがかなり高いです。

コードが汚くなる

その言語のインデントのルールとHTMLのインデントの都合が干渉してコードが汚くなります。

出力するHTMLが汚い

閉じタグ忘れなどによって出力するHTMLが汚くなります。
また、多くの場合改行の扱いの統一感がなくなります。

テンプレート機能を使わない方がいい理由

XSS対策

これもXSSの可能性があります。
逆に、Goのhtml/templateは自動でエスケープしてくれますが、
それはそれで二重でエスケープされてしまったりするというややこしさは残ります。

プログラマブルでない

まず、組み立てている最中にTextがprintされて行ってしまうため、処理の呼び出し順序に制約を受けることがあります。
また、共通の枠組みを様々なコンテンツで使いまわすという事がし辛いです。

<共通枠組み> コンテンツA </共通枠組み>
<共通枠組み> コンテンツB </共通枠組み>

見づらい

僕の主観ですが、HTMLテンプレートエンジンが提供するHTMLに似せたForやIfの構文は非常に見辛くストレスです。


{{if .Empty}}
  <p>未登録</p>
{{else}}
<ul>
  {{range .Member}} <li> {{.Name}} </li> {{end}}
</ul>
{{end}}

これはあまり親切ではないと思います。
誰がうれしいんですかねこれ。

出力するHTMLが汚い

インデントがそろわないなどの問題があります。

対策

方針

大体どの言語にもXMLライブラリやHTMLライブラリというのがあります。
それを使ってHTMLを組み立てるといいと思います。

この場合、基本的にXSSの心配は全くありません。
また、閉じタグ忘れなども原理的に発生しません。

appendする順番さえあっていれば実行順序の制約がありません。
むしろ、子要素の作成を簡単に関数にくくりだせるのでコードの見通しがよくなります。
また、子要素を引数に取る関数を作るだけで、簡単に外枠の共通化ができます。

function main() {
    const doc = document.createElement("html");
    doc.append(createBody());
    console.log(doc.outerHTML);
}

function createBody() {
    const body = document.createElement("body");
    const liList = [];
    for (let i = 0 ; i < 10 ; i ++) {
        liList.push(createLi(i));
    }
    body.append(createUl(liList));
    return body;
}

function createUl(liList) {
    const ul = document.createElement('ul');
    for(const li of liList) ul.append(li);
    return ul;
}

function createLi(i) {
    const li = document.createElement('li');
    li.textContent = i;
    return li;
}

このままだと改行もインデントもないですが、
大体pretty出力の方法などが簡単に見つかるのでそれ使うことも検討しましょう。

お勧め

ただ、この方法も少し冗長に感じるかもしれません。
(実際には規模が大きくなればなるほどコンポーネントの共通化ができて気にならなくなります)
それでももっと簡単に書きたいという方のために、
僕が最もお勧めするのがTSXです。

TSXは現状最も使いやすいテンプレートエンジンだと思っています。
TSXは文字列でHTMLを組み立てているのではなく
あくまでDOMのツリーを返す純粋関数の便利構文です。
なので、解決策の基本方針に反しないのです。
TSXで書いてrenderToStringするだけでいいのです。

例えNode以外の言語でサーバーサイドを書いていても、簡単に実行環境を用意することができます。
AWSのLambdaやGCPのCloud Functionsなどが使えるからです。
ビジネスロジックはGo/Javaなどで書きHTMLをTSXで組み立てるという事も出来ます。

Discussion