【初学者必見】テンプレートリテラルでHTMLを作成したらダメな理由!

JavaScript

はじめに

ES6で追加された機能で、テンプレートリテラル(テンプレートリテラル)というものがあります。テンプレートリテラルは文字列内に変数や式を埋め込むことができ、通常の文字列に比べて直感的で可読性の高い表現ができます。

しかし、テンプレートリテラルの好ましくない使い方もあります。特に、テンプレートリテラルを利用した安易なHTML要素の作成は、一見すると可読性の高い表現で簡潔なコードに見えますが、柔軟性やセキュリティにおいて問題となる可能性があります。

この記事では、HTMLの作成にテンプレートリテラルを使用してはいけない理由について、HTMLのコンポーネント化を例に学び、実際の開発ではどのような手法でHTMLのコンポーネント化を行うかについて解説します。

この記事の対象者

  • テンプレートリテラルをよく使う方
  • HTMLのコンポーネント化に興味がある方
  • JavaScriptライブラリに興味がある方

この記事で学べること

  • innerHTMLにHTMLコードを渡してはいけない理由
  • テンプレートリテラルでHTMLを記述するリスク
  • HTMLをコンポーネント化するためのJavaScriptライブラリ

HTMLのコンポーネント化について

JavaScriptでHTMLを作成する目的の一つとして、「HTMLのコンポーネント化」が挙げられます。コンポーネントとは「部品」と表現され、その粒度や設計においての考え方は様々です。ここでは「HTMLをテンプレート化して使いまわせるようにする」という解釈で問題ありません。

HTMLをコンポーネント化することは、コードの重複を防いで保守性を高める効果があります。例えば、サイト全体で表示されている共通ヘッダーに修正が必要な場合に全ページを修正しなければならないのは、それが簡単な作業だったとしても気分的にしんどいです。このような場合に共通ヘッダーをコンポーネント化することで、修正箇所を一つにすることができるようになります。

テンプレートリテラルの特徴

テンプレートリテラルは、ES6で導入された文字列型のデータを定義するための便利な機能です。

テンプレートリテラルは「+」演算子を用いた文字列連結を必要とせず、変数や関数の戻り値、式などを文字列内に展開することができます。また、テンプレートリテラル内の改行やスペースもそのまま出力されるので、直感的な表現で複数行の文字列を定義することができます。

これにより、通常の文字列による文字列連結に比べて冗長さが減り、コードの可読性を高める効果があります。

/*
============
 変数の展開
============
*/
const name = 'Taro';

// テンプレートリテラル
const message1 = `Hello, ${name}.`;

// 通常の文字列
const message2 = 'Hello, ' + name;

/*
================
 複数行の文字列
================
*/
// テンプレートリテラル
const multiLineText1 = 
`This is
a multi-line
text using
template strings.`;

// 通常の文字列
const multiLineText2 =
'This is\n' +
'a multi-line\n' +
'text using\n' +
'traditional strings.';

JavaScriptのテンプレートリテラルについて学び始めた方には、「これを使えばHTML要素をJavaScriptで簡単に定義できるのでは?」と考える方もいるかと思います。

確かに、テンプレートリテラルで表現されたデータは文字列なので、DOMから取得したHTML要素のinnerHTMLプロパティに代入することで動的にHTML要素の内部を書き換えることができます。

しかし、テンプレートリテラルで安易にHTML要素を作成することはオススメしません。この理由について解説していきます。

HTML作成には使わない理由

テンプレートリテラルは直感的で可読性の高い表現ができるため、これを使用することで簡単に階層構造を持ったHTMLをJavaScriptで作成することができます。これにより、工夫をすれば決められた構造のHTMLをコンポーネント化して使いまわすことが技術的に可能です。

しかし、それを実装するためにテンプレートリテラルを使用することはオススメしません。ここでは、テンプレートリテラルでHTMLを作成することがダメな理由として、以下の2つのポイントから解説します。

  • セキュリティリスク
  • メンテナンスの複雑さ

まずはセキュリティリスクから見ていきましょう。

セキュリティリスクの問題

テンプレートリテラルは複数行の文字列を表現できるため、createElement()やappendChild()などを使用せずに複雑な構造を持つHTMLを作成することが可能です。以下はテンプレートリテラルで表現したHTMLの簡単な例です。

const title = 'Title';
const content = 'Content goes here.';

// テンプレートリテラルでHTMLを記述
const htmlTemplate = `
  <div>
    <h1>${title}</h1>
    <p>${content}</p>
  </div>
`;

一見すると可読性が高く、ほぼHTMLの知識だけで内容を理解できるかと思います。しかし、セキュリティの観点から見るとテンプレートリテラルによるHTMLテンプレートの作成は問題を孕んでいます。なぜなら、上記で宣言したhtmlTemplateを使用するにはinnerHTMLプロパティにこの値を挿入する必要があり、これはXSS攻撃のターゲットにされる可能性があるからです。

XSS攻撃とは、悪意のあるユーザーがフォーム等から不適切なコードを送信し、他のユーザーのブラウザから実行されるJavaScriptを埋め込むことを指します。これにより、ユーザーが意図しない操作を実行したり、個人情報が盗まれたりする可能性があります。

XSS攻撃を可能とする例として、簡単なHTMLフォームを作って試してみます。

<body>
  <form action="" id="form">
    <input type="text" id="userInput">
    <button type="submit">送信</button>
  </form>

  <div id="container">
    <!-- ここに送信結果が表示されます -->
  </div>

  <script>
    const userInput = document.getElementById('userInput');
    const form = document.getElementById('form');
    const container = document.getElementById('container');

    form.addEventListener('submit', (e) => {
      e.preventDefault();

      const htmlTemplate = `
        <div>
          <p>${userInput.value}</p>
        </div>
      `;

      container.innerHTML += htmlTemplate;
    })
</script>
</body>

テキストフィールドと送信ボタンだけが表示されます。機能として、送信ボタンをクリックするとテキストフィールドの内容がdiv要素とp要素にラップされ、containerというidを持ったdiv要素内に挿入されます。

ここでの問題は、テキストの挿入にinnerHTMLプロパティを指定しているため、テキストフィールドの値がHTMLとして解釈されるところにあります。つまり、テキストフィールドにHTMLタグを使用するとHTML要素として挿入されます。

HTML要素にはscriptタグも含まれるため、悪意のあるユーザーはJavaScriptコードを自由に実行することができてしまい、XSS攻撃を可能とします。この対策には一般的に、innerHTMLではなくtextContentを使用するか、あるいはエスケープ処理(<や/>等の特殊文字を無害化する)を実装することになります。

textContentを使用する場合はテンプレートリテラルで表現したHTMLコード全てが無害化されるので、HTMLのテンプレートとして機能しなくなります(改行は半角スペースとして認識されます)。

form.addEventListener('submit', (e) => {
  …
  // 以下に変更します
  container.textContent += htmlTemplate;
})

エスケープ処理を実装することで問題を軽減できますが、テンプレートリテラルによるHTMLの作成は他にもデメリットが多いのでオススメしません。

XSS攻撃を簡単に説明するために入力フォームを例に挙げましたが、実際はブラウザ側で動的にHTMLを生成するタイミングで悪意あるスクリプトが実行されるような攻撃手法もあります。安易にテンプレートリテラルでHTMLを作成することは避けましょう。

テンプレートリテラルでのHTMLの作成は、保守性や可読性、再利用性などにおいてもデメリットが多く、快適な開発体験を阻害することになります。これについて見ていきましょう。

メンテナンスの複雑さ

テンプレートリテラルでHTMLを作成する場合、メンテナンスの複雑さが問題となります。これは、HTMLコードがJavaScriptコード内に混在することによって快適な開発を難しくすることを指します。

例として、VSCodeなどのエディタにはシンタックスハイライトや、コードの補完機能、それを拡張するプラグインなどが提供されていますが、これらは基本的にJavaScriptファイルやHTMLファイルを区別します。

テンプレートリテラルで記述されたHTMLコードはただのJavaScript上の文字列なので、これらの恩恵を受けられません。そのHTMLコードに修正が必要な場合、誤りの箇所に気付きにくいという問題が起こります。

この問題によって、複雑なコードとなった場合にどこで問題が発生しているかを特定することが難しくなり、保守性の低下に繋がります。コンポーネントは再利用する目的で構築されるので、記述の重複を防ぐためにも関数やクラスなどで定義する必要があり、特に複雑な構造になることが考えられます。また、HTMLとJavaScriptが機能的に分離できないこともコードを理解する妨げとなります。

このように、テンプレートリテラルでのHTMLの作成はメンテナンスを複雑にさせる可能性があり、ウェブ制作においての適切な設計を考えた場合に大きなリスクがあることを理解する必要があります。

JavaScript初学者の方でも手軽に実装できるメリットは唯一としてあるので、「テスト用に一時的に使用して後で書き直す」という使い方であれば問題はないかも知れません(直し忘れがあると結局リスクなのでオススメはしません)。

ならばどうするか

HTMLのコンポーネントを安全かつ効果的に構築するには、ReactやVue.jsといったコンポーネントベースのJavaScriptライブラリを使用することが一般的です。これらはモダンなウェブ開発において広く利用されており、昨今では大規模なウェブアプリケーションのみならず一般的なウェブサイト制作でも利用されるケースが増えています。

JavaScriptライブラリを使う

ReactやVue.jsといったコンポーネントベースのライブラリは、HTML/CSSやJavaScriptの基本について理解していれば学習を始めることができます。学習を進める中で、Node.jsのインストールやパッケージマネージャー(npm、yarn等)の使い方、ローカルサーバーの起動方法、ターミナルの操作といった実際のウェブ開発現場で必須となるスキルを学ぶ機会を得られます。

また、ReactやVue.jsといったライブラリを使用するプロジェクトでは「TypeScript」というJavaScriptを拡張したプログラミング言語が広く使われています。TypeScriptはJavaScriptの柔軟な仕様ゆえの問題を改善するために開発されており、JavaScriptをベースに型推論などコードを安全に扱うための機能が備えられているため、大規模なフロントエンド開発では広く利用されています。TypeScriptの利用は必須ではありませんが、ReactやVue.jsなどのフレームワークを利用することはこれを学び始める機会にもなります。

このように、初学者の方がReactやVue.jsといったコンポーネントベースのライブラリを利用するには、ライブラリの学習と同時に多くの技術について触れることになります。これは初学者の方が効率良くスキルを身に付けるための良い方法です。

ReactやVue.jsについて学ぶには、まずは公式サイトのチュートリアルを終えることをオススメします。以下はチュートリアルのリンクです。

クイックスタート – React
The library for web and native user interfaces
Vue.js
Vue.js - The Progressive JavaScript Framework

Reactは世界的にシェアが高く、Reactをベースにしたフレームワークも豊富で、恐らく現在(2024年2月)最も人気のあるJavaScriptライブラリです。Vue.jsは公式ドキュメントの日本語がわかりやすく、日本国内で人気があります。

どちらか一方が優れているとは言えませんが、上記のチュートリアルは二つともブラウザ上で試すことができるので、選択に迷う方はとりあえず両方試してみると良いと思います。記述方法の違いによって好みが分かれるところもあるので、理解しやすいと感じる方を選択すると良いでしょう。

ReactやVue.jsがとても難しく感じる場合

ReactやVue.jsの学習をとおしてウェブ開発について学ぶことは効率的ですが、初学者にとっては多くの高度な技術に触れることにもなるので学習コストが高いというのも事実です。もう少し簡単なところから学びたい方は、CDN経由で比較的簡単に使えるJavaScriptのテンプレートエンジンを使用するという選択肢もあります。

JavaScriptテンプレートエンジンはライブラリの一種です。ReactやVue.jsに比べて機能が限定的であり学習コストが低いことが強みですが、テンプレートエンジン特有の独特な記法を学ぶ必要があることや、機能が限定的であるがゆえに実装に限界があること、シェアが低いなどの理由で実際のプロジェクトで選ばれるケースが少ないというデメリットもあります。

また、CDN経由でのテンプレートエンジンの使用は、パフォーマンスや他のライブラリとの依存性の点で考慮すべき問題があり、大規模なウェブ開発ではあまり現実的ではありません。

しかし、CDNはHTMLにscriptタグを記述するだけで比較的簡単に使用できるため、高度な開発環境の構築に踏み切れない初学者の方でも手軽にJavaScriptライブラリを試すことができます。

CDN経由で利用できるテンプレートエンジンには以下のようなものがあります。使い方について学びたい方はリンク先を参照してください(公式サイトではないものも含まれます)。

Node.jsをローカルにインストールしていれば、パッケージマネージャーからこれらを開発環境に組み込むことも可能です。ある程度使い慣れてから次のステップとしてNode.jsでローカル開発環境を構築する方法について学ぶことで、いきなり高機能なライブラリを学ぶよりも学習の負担は少なくなるかと思います。

ちなみに、あまり一般的ではありませんがReactもCDN経由で使用することができます。React公式サイトにはダウンロードしてすぐに試せるHTMLコードが公開されています。

インストール – React
The library for web and native user interfaces

最後に

この記事では、HTMLの作成をテンプレートリテラルを使用してはいけない理由についてHTMLのコンポーネント化を例に学びました。また、HTMLのコンポーネント化を行う際に使えるJavaScriptライブラリについてご紹介しました。

テンプレートリテラルはとても便利なのですが、この記事で解説したようにHTMLの作成にはあまり向きません。シンタックスハイライトの問題についてはプラグインを利用することで解決できるのですが、JavaScriptコードとHTMLコードの混在はテンプレートリテラルでHTMLを定義する限り問題として残り続けます。

JavaScriptにはライブラリが豊富にあり、初学者の方でも学習コストの低いものを選択すれば高度な機能を実装することができます。ライブラリの種類としていくつかのコンポーネントベースのUIライブラリとテンプレートエンジンをご紹介しましたが、フロントエンドエンジニアとして効率良くスキルを高めたいのであればReactやVue.jsの学習をオススメします。これらを学ぶことで、Next.jsやNuxt.js、Astroといったウェブ開発を効率良く行う際に使用されるJavaScriptフレームワークについての学習を進めることができます。

しかし、これらの学習があまりにも難しく感じるようであれば、恐らくまだその時ではないのかもしれません。HTMLのコンポーネント化を手軽に始められるテンプレートエンジンの利用を検討してみるのもアリだと思います。

タイトルとURLをコピーしました