JavaScriptでクラスを使ったテンプレートエンジンの構築方法

JavaScriptのテンプレートエンジンは、HTMLやその他の出力フォーマットを動的に生成するための強力なツールです。テンプレートエンジンを使うことで、コードの再利用性を高め、アプリケーションのメンテナンスを容易にすることができます。本記事では、JavaScriptのクラスを使って独自のテンプレートエンジンを構築する方法を詳しく解説します。基本概念から始まり、具体的な実装手順、応用例、さらには演習問題までを網羅し、初心者から上級者までが学べる内容となっています。テンプレートエンジンの構築を通じて、JavaScriptのクラスの理解を深め、実践的なスキルを身につけましょう。

目次

テンプレートエンジンとは

テンプレートエンジンは、テンプレートとデータを組み合わせて動的なコンテンツを生成するためのツールです。テンプレートは、静的な部分と動的に置き換えられる変数を含むプレーンテキストで構成され、データは変数に埋め込まれる実際の値です。

テンプレートエンジンの基本概念

テンプレートエンジンの基本概念は次の通りです:

  • テンプレート:動的な部分(プレースホルダー)を含む静的なテキスト。
  • データ:テンプレート内のプレースホルダーを置き換える値。
  • レンダリング:テンプレートとデータを組み合わせて最終的な出力を生成するプロセス。

テンプレートエンジンの重要性

テンプレートエンジンを使うことで、コードの可読性と再利用性が向上し、以下の利点が得られます:

  • 一貫性のある出力:テンプレートを使うことで、同じフォーマットの出力を簡単に生成できます。
  • コードの分離:テンプレートとロジックを分離することで、コードの保守性が向上します。
  • 効率的な開発:テンプレートを再利用することで、開発速度が向上します。

テンプレートエンジンは、Web開発だけでなく、メール生成やドキュメント生成など、さまざまな場面で活用されています。次章では、JavaScriptでのテンプレートエンジンの構築に必要な基礎知識であるクラスについて説明します。

JavaScriptのクラスとは

JavaScriptのクラスは、オブジェクト指向プログラミングの概念を取り入れた構文で、オブジェクトの設計図として機能します。クラスを使用することで、オブジェクトのプロパティやメソッドを効率的に定義および管理できます。

クラスの基本構文

JavaScriptのクラスは、classキーワードを使用して定義されます。以下は基本的なクラスの例です:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

const person1 = new Person('Alice', 30);
person1.greet(); // Hello, my name is Alice and I am 30 years old.

コンストラクタ

コンストラクタは、クラスの新しいインスタンスを作成するための特殊なメソッドです。constructorメソッドは、新しいオブジェクトが作成される際に呼び出され、初期化処理を行います。

メソッド

クラス内に定義された関数はメソッドと呼ばれます。メソッドは、クラスのインスタンスに対して動作を定義するものです。

継承

JavaScriptのクラスは、extendsキーワードを使用して他のクラスを継承することができます。これにより、親クラスのプロパティやメソッドを子クラスに引き継ぐことができます。

class Employee extends Person {
  constructor(name, age, jobTitle) {
    super(name, age);
    this.jobTitle = jobTitle;
  }

  work() {
    console.log(`${this.name} is working as a ${this.jobTitle}.`);
  }
}

const employee1 = new Employee('Bob', 25, 'Developer');
employee1.greet(); // Hello, my name is Bob and I am 25 years old.
employee1.work(); // Bob is working as a Developer.

このように、JavaScriptのクラスを使うことで、オブジェクトの設計がより直感的かつ整理されたものになります。次章では、これらのクラスを使ってテンプレートエンジンの基本設計を行います。

テンプレートエンジンの基本設計

テンプレートエンジンを設計するにあたり、テンプレートを解析し、データを埋め込み、最終的な出力を生成する仕組みが必要です。JavaScriptのクラスを活用して、このプロセスを効率的に管理します。

設計の概要

テンプレートエンジンの基本設計は以下のステップで構成されます:

  1. テンプレートの定義:テンプレート文字列を定義します。
  2. データの入力:テンプレートに埋め込むデータを提供します。
  3. 解析と置換:テンプレートを解析し、プレースホルダーをデータで置換します。
  4. 出力の生成:最終的な文字列を生成します。

クラスの構造

テンプレートエンジンのクラスは以下のように設計します:

  1. コンストラクタ:テンプレート文字列を受け取り、初期化します。
  2. メソッド:データをテンプレートに埋め込み、出力を生成するメソッドを定義します。

以下は基本的なテンプレートエンジンクラスの例です:

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;
    for (const key in data) {
      const placeholder = `{{${key}}}`;
      output = output.replaceAll(placeholder, data[key]);
    }
    return output;
  }
}

テンプレートの定義

テンプレートは、プレースホルダーを含む文字列として定義します。プレースホルダーは、{{key}}の形式で記述されます。

const template = "Hello, {{name}}! You have {{messages}} new messages.";

データの入力とレンダリング

テンプレートエンジンクラスのインスタンスを作成し、renderメソッドを使用してデータを埋め込みます。

const data = {
  name: "Alice",
  messages: 5
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result); // Hello, Alice! You have 5 new messages.

このように、テンプレートエンジンの基本設計は、テンプレートの定義、データの入力、解析と置換、出力の生成というプロセスを通じて構築されます。次章では、テンプレートの構造と仕様について詳しく説明します。

テンプレートの構造と仕様

テンプレートエンジンを効果的に使用するためには、テンプレートの構造と仕様を理解することが重要です。ここでは、テンプレートの基本構造、プレースホルダーの使い方、および仕様について説明します。

テンプレートの基本構造

テンプレートは、静的なテキストと動的に置き換えられるプレースホルダーで構成されます。プレースホルダーは、{{key}}形式で記述され、テンプレートエンジンによってデータで置換されます。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{title}}</title>
</head>
<body>
  <h1>{{header}}</h1>
  <p>{{message}}</p>
</body>
</html>

プレースホルダーの使い方

プレースホルダーは、テンプレート内の任意の場所に配置できます。テンプレートエンジンは、データオブジェクトのキーと一致するプレースホルダーを対応する値に置き換えます。

const template = `
  <h1>{{title}}</h1>
  <p>{{content}}</p>
`;

const data = {
  title: "Welcome",
  content: "This is a simple template engine example."
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Welcome</h1>
// <p>This is a simple template engine example.</p>

仕様

テンプレートの仕様には以下の要素が含まれます:

  1. プレースホルダー形式:プレースホルダーは{{key}}形式で記述します。これにより、テンプレート内で動的にデータを挿入できます。
  2. エスケープシーケンス:プレースホルダー内で特殊文字を使用する場合は、エスケープシーケンスを使用します。
  3. デフォルト値:プレースホルダーに対応するデータが存在しない場合のデフォルト値を設定できます。

例:デフォルト値の実装

デフォルト値を設定することで、データが不足している場合でもテンプレートが正しく動作するようにします。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;
    for (const key in data) {
      const placeholder = `{{${key}}}`;
      output = output.replaceAll(placeholder, data[key] || '');
    }
    return output;
  }
}

const template = `
  <h1>{{title}}</h1>
  <p>{{content}}</p>
  <p>Author: {{author}}</p>
`;

const data = {
  title: "Welcome",
  content: "This is a simple template engine example."
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Welcome</h1>
// <p>This is a simple template engine example.</p>
// <p>Author: </p>

このように、テンプレートの構造と仕様を理解することで、より柔軟で再利用可能なテンプレートエンジンを構築できます。次章では、テンプレートエンジンクラスの実装手順について詳しく解説します。

テンプレートエンジンクラスの実装

テンプレートエンジンの基本設計が理解できたところで、実際にクラスを使ってテンプレートエンジンを実装してみましょう。ここでは、テンプレートの解析と置換、動的データの埋め込みを行うクラスの実装手順を詳しく説明します。

基本構造の定義

まず、テンプレートエンジンのクラスの基本構造を定義します。TemplateEngineクラスは、テンプレート文字列を受け取り、データを使ってレンダリングする機能を持ちます。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;
    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, data[key]);
    }
    return output;
  }
}

詳細な実装

次に、クラスの詳細な実装を見ていきましょう。以下のコードでは、テンプレート内のプレースホルダーを動的にデータで置き換える処理を行います。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  // データを使ってテンプレートをレンダリングするメソッド
  render(data) {
    let output = this.template;
    // プレースホルダーをデータで置き換える
    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, data[key] || '');
    }
    return output;
  }
}

このrenderメソッドは、次のステップを踏みます:

  1. テンプレート文字列をoutput変数にコピーします。
  2. dataオブジェクト内の各キーに対して対応するプレースホルダーを検索し、データの値で置き換えます。
  3. すべてのプレースホルダーが置き換えられた後、最終的なoutputを返します。

動作確認

このテンプレートエンジンのクラスを使って実際に動作を確認してみましょう。

const template = `
  <h1>{{title}}</h1>
  <p>{{content}}</p>
  <p>Author: {{author}}</p>
`;

const data = {
  title: "Welcome",
  content: "This is a simple template engine example.",
  author: "John Doe"
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Welcome</h1>
// <p>This is a simple template engine example.</p>
// <p>Author: John Doe</p>

エスケープ文字の処理

HTMLテンプレートを扱う際に、特殊文字のエスケープ処理を行うことが重要です。以下のようにしてエスケープ処理を追加できます:

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  escapeHtml(str) {
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
  }

  render(data) {
    let output = this.template;
    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, this.escapeHtml(data[key] || ''));
    }
    return output;
  }
}

このように、テンプレートエンジンのクラスを実装することで、柔軟で強力なテンプレート処理が可能になります。次章では、テンプレート内で変数を埋め込み、処理する方法について詳しく説明します。

変数の埋め込みと処理

テンプレートエンジンの核心は、テンプレート内の変数を正しく埋め込み、データを動的に処理することです。ここでは、変数の埋め込み方法と、さまざまなデータ型や条件を扱う方法について説明します。

基本的な変数の埋め込み

テンプレート内で変数を埋め込むには、プレースホルダーを使用します。プレースホルダーは、{{variable}}の形式で記述され、テンプレートエンジンがデータオブジェクトの対応する値に置き換えます。

const template = `
  <h1>{{title}}</h1>
  <p>{{message}}</p>
`;

const data = {
  title: "Hello, World!",
  message: "Welcome to the JavaScript template engine tutorial."
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Hello, World!</h1>
// <p>Welcome to the JavaScript template engine tutorial.</p>

複数のプレースホルダーを含むテンプレート

テンプレート内には複数のプレースホルダーを含めることができます。エンジンは、各プレースホルダーを対応するデータで置き換えます。

const template = `
  <h1>{{title}}</h1>
  <p>{{message}}</p>
  <footer>{{footer}}</footer>
`;

const data = {
  title: "Hello, World!",
  message: "Welcome to the JavaScript template engine tutorial.",
  footer: "© 2024 JavaScript Academy"
};

const result = engine.render(data);
console.log(result);
// <h1>Hello, World!</h1>
// <p>Welcome to the JavaScript template engine tutorial.</p>
// <footer>© 2024 JavaScript Academy</footer>

条件付きの変数埋め込み

テンプレートエンジンで条件付きの内容を表示するには、テンプレート内に条件文を記述し、それをエンジンが解析するようにします。以下は、if文を使った例です:

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;

    // 条件付きの処理
    output = output.replace(/{{#if (\w+)}}(.*?){{\/if}}/gs, (match, p1, p2) => {
      return data[p1] ? p2 : '';
    });

    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, data[key] || '');
    }
    return output;
  }
}

const template = `
  <h1>{{title}}</h1>
  <p>{{message}}</p>
  {{#if showFooter}}
  <footer>{{footer}}</footer>
  {{/if}}
`;

const data = {
  title: "Hello, World!",
  message: "Welcome to the JavaScript template engine tutorial.",
  showFooter: true,
  footer: "© 2024 JavaScript Academy"
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Hello, World!</h1>
// <p>Welcome to the JavaScript template engine tutorial.</p>
// <footer>© 2024 JavaScript Academy</footer>

ループ処理

ループを使ってリストや配列のデータをテンプレート内に埋め込むことも可能です。以下の例では、for文を使ってリストアイテムを生成します。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;

    // 条件付きの処理
    output = output.replace(/{{#if (\w+)}}(.*?){{\/if}}/gs, (match, p1, p2) => {
      return data[p1] ? p2 : '';
    });

    // ループの処理
    output = output.replace(/{{#each (\w+)}}(.*?){{\/each}}/gs, (match, p1, p2) => {
      return data[p1].map(item => p2.replace(/{{this}}/g, item)).join('');
    });

    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, data[key] || '');
    }
    return output;
  }
}

const template = `
  <h1>{{title}}</h1>
  <p>{{message}}</p>
  <ul>
    {{#each items}}
    <li>{{this}}</li>
    {{/each}}
  </ul>
`;

const data = {
  title: "Shopping List",
  message: "Here are the items you need to buy:",
  items: ["Milk", "Bread", "Eggs"]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Shopping List</h1>
// <p>Here are the items you need to buy:</p>
// <ul>
//   <li>Milk</li>
//   <li>Bread</li>
//   <li>Eggs</li>
// </ul>

このように、テンプレートエンジンを使うことで、複雑な条件やループを扱いながら動的なコンテンツを生成することができます。次章では、条件分岐とループの実装についてさらに詳しく説明します。

条件分岐とループの実装

テンプレートエンジンで条件分岐やループを実装することで、より柔軟で動的なコンテンツを生成することができます。ここでは、条件分岐とループの具体的な実装方法について説明します。

条件分岐の実装

テンプレート内で条件分岐を行うためには、{{#if}}...{{/if}}構文を使用します。これにより、特定の条件に基づいてテンプレートの一部を表示したり隠したりできます。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;

    // 条件付きの処理
    output = output.replace(/{{#if (\w+)}}(.*?){{\/if}}/gs, (match, p1, p2) => {
      return data[p1] ? p2 : '';
    });

    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, data[key] || '');
    }
    return output;
  }
}

const template = `
  <h1>{{title}}</h1>
  <p>{{message}}</p>
  {{#if showFooter}}
  <footer>{{footer}}</footer>
  {{/if}}
`;

const data = {
  title: "Hello, World!",
  message: "Welcome to the JavaScript template engine tutorial.",
  showFooter: true,
  footer: "© 2024 JavaScript Academy"
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Hello, World!</h1>
// <p>Welcome to the JavaScript template engine tutorial.</p>
// <footer>© 2024 JavaScript Academy</footer>

上記の例では、showFootertrueの場合にのみ<footer>要素が表示されます。

ループの実装

テンプレート内でリストや配列のデータをループ処理するためには、{{#each}}...{{/each}}構文を使用します。これにより、配列内の各要素をテンプレート内で繰り返し処理することができます。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  render(data) {
    let output = this.template;

    // 条件付きの処理
    output = output.replace(/{{#if (\w+)}}(.*?){{\/if}}/gs, (match, p1, p2) => {
      return data[p1] ? p2 : '';
    });

    // ループの処理
    output = output.replace(/{{#each (\w+)}}(.*?){{\/each}}/gs, (match, p1, p2) => {
      return data[p1].map(item => p2.replace(/{{this}}/g, item)).join('');
    });

    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, data[key] || '');
    }
    return output;
  }
}

const template = `
  <h1>{{title}}</h1>
  <p>{{message}}</p>
  <ul>
    {{#each items}}
    <li>{{this}}</li>
    {{/each}}
  </ul>
`;

const data = {
  title: "Shopping List",
  message: "Here are the items you need to buy:",
  items: ["Milk", "Bread", "Eggs"]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Shopping List</h1>
// <p>Here are the items you need to buy:</p>
// <ul>
//   <li>Milk</li>
//   <li>Bread</li>
//   <li>Eggs</li>
// </ul>

この例では、items配列の各要素が<li>タグ内に繰り返し埋め込まれます。

ネストされたループと条件分岐

条件分岐とループはネストして使用することもできます。これにより、より複雑なテンプレートを作成できます。

const template = `
  <h1>{{title}}</h1>
  <ul>
    {{#each items}}
    <li>
      {{this.name}}
      {{#if this.inStock}}
      <span>In Stock</span>
      {{/if}}
    </li>
    {{/each}}
  </ul>
`;

const data = {
  title: "Product List",
  items: [
    { name: "Milk", inStock: true },
    { name: "Bread", inStock: false },
    { name: "Eggs", inStock: true }
  ]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Product List</h1>
// <ul>
//   <li>Milk <span>In Stock</span></li>
//   <li>Bread </li>
//   <li>Eggs <span>In Stock</span></li>
// </ul>

このようにして、テンプレートエンジンを使って動的なコンテンツを柔軟に生成することができます。次章では、テンプレートエンジンを使った動的なHTML生成の応用例について詳しく説明します。

応用例:動的なHTML生成

テンプレートエンジンを使うことで、動的なHTMLコンテンツを効率的に生成できます。ここでは、具体的な応用例として、ブログ投稿の一覧を動的に生成する方法を説明します。

ブログ投稿一覧の生成

まず、ブログ投稿データを含むテンプレートを作成し、それをテンプレートエンジンで動的にレンダリングします。

const template = `
  <h1>{{title}}</h1>
  <ul>
    {{#each posts}}
    <li>
      <h2>{{this.title}}</h2>
      <p>{{this.content}}</p>
      <small>Author: {{this.author}}</small>
    </li>
    {{/each}}
  </ul>
`;

const data = {
  title: "Blog Posts",
  posts: [
    {
      title: "First Post",
      content: "This is the content of the first post.",
      author: "Alice"
    },
    {
      title: "Second Post",
      content: "This is the content of the second post.",
      author: "Bob"
    },
    {
      title: "Third Post",
      content: "This is the content of the third post.",
      author: "Charlie"
    }
  ]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Blog Posts</h1>
// <ul>
//   <li>
//     <h2>First Post</h2>
//     <p>This is the content of the first post.</p>
//     <small>Author: Alice</small>
//   </li>
//   <li>
//     <h2>Second Post</h2>
//     <p>This is the content of the second post.</p>
//     <small>Author: Bob</small>
//   </li>
//   <li>
//     <h2>Third Post</h2>
//     <p>This is the content of the third post.</p>
//     <small>Author: Charlie</small>
//   </li>
// </ul>

ユーザープロファイルページの生成

次に、ユーザープロファイルページを動的に生成する例を示します。ユーザーデータをテンプレートに埋め込み、各ユーザーのプロファイル情報を表示します。

const template = `
  <h1>{{title}}</h1>
  {{#each users}}
  <div class="user-profile">
    <h2>{{this.name}}</h2>
    <p>Email: {{this.email}}</p>
    <p>Age: {{this.age}}</p>
  </div>
  {{/each}}
`;

const data = {
  title: "User Profiles",
  users: [
    {
      name: "Alice",
      email: "alice@example.com",
      age: 30
    },
    {
      name: "Bob",
      email: "bob@example.com",
      age: 25
    },
    {
      name: "Charlie",
      email: "charlie@example.com",
      age: 35
    }
  ]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>User Profiles</h1>
// <div class="user-profile">
//   <h2>Alice</h2>
//   <p>Email: alice@example.com</p>
//   <p>Age: 30</p>
// </div>
// <div class="user-profile">
//   <h2>Bob</h2>
//   <p>Email: bob@example.com</p>
//   <p>Age: 25</p>
// </div>
// <div class="user-profile">
//   <h2>Charlie</h2>
//   <p>Email: charlie@example.com</p>
//   <p>Age: 35</p>
// </div>

製品カタログの生成

最後に、製品カタログのページを動的に生成する例を示します。製品データをテンプレートに埋め込み、各製品の詳細を表示します。

const template = `
  <h1>{{title}}</h1>
  <div class="product-catalog">
    {{#each products}}
    <div class="product">
      <h2>{{this.name}}</h2>
      <p>Price: ${{this.price}}</p>
      <p>{{this.description}}</p>
    </div>
    {{/each}}
  </div>
`;

const data = {
  title: "Product Catalog",
  products: [
    {
      name: "Product 1",
      price: 19.99,
      description: "This is the description for product 1."
    },
    {
      name: "Product 2",
      price: 29.99,
      description: "This is the description for product 2."
    },
    {
      name: "Product 3",
      price: 39.99,
      description: "This is the description for product 3."
    }
  ]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Product Catalog</h1>
// <div class="product-catalog">
//   <div class="product">
//     <h2>Product 1</h2>
//     <p>Price: $19.99</p>
//     <p>This is the description for product 1.</p>
//   </div>
//   <div class="product">
//     <h2>Product 2</h2>
//     <p>Price: $29.99</p>
//     <p>This is the description for product 2.</p>
//   </div>
//   <div class="product">
//     <h2>Product 3</h2>
//     <p>Price: $39.99</p>
//     <p>This is the description for product 3.</p>
//   </div>
// </div>

これらの応用例を通じて、テンプレートエンジンを使った動的なHTML生成の基本的な方法を理解できたと思います。次章では、実際に手を動かして学ぶための演習問題を提供します。

演習問題

テンプレートエンジンの理解を深めるために、以下の演習問題に取り組んでみましょう。これらの問題を解くことで、実際にテンプレートエンジンを使用して動的なコンテンツを生成するスキルを身につけることができます。

演習1:シンプルなプロフィールカードの生成

次のテンプレートとデータを使用して、ユーザーのプロフィールカードを生成するテンプレートエンジンを実装してください。

const template = `
  <div class="profile-card">
    <h2>{{name}}</h2>
    <p>Age: {{age}}</p>
    <p>Email: {{email}}</p>
  </div>
`;

const data = {
  name: "John Doe",
  age: 28,
  email: "john.doe@example.com"
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <div class="profile-card">
//   <h2>John Doe</h2>
//   <p>Age: 28</p>
//   <p>Email: john.doe@example.com</p>
// </div>

演習2:条件付き表示の実装

次のテンプレートを使用して、条件に基づいてメッセージを表示するテンプレートエンジンを実装してください。isLoggedIntrueの場合、ウェルカムメッセージを表示し、falseの場合はログインメッセージを表示します。

const template = `
  <div class="message">
    {{#if isLoggedIn}}
    <p>Welcome, {{username}}!</p>
    {{else}}
    <p>Please log in.</p>
    {{/if}}
  </div>
`;

const data1 = {
  isLoggedIn: true,
  username: "Alice"
};

const data2 = {
  isLoggedIn: false
};

const engine = new TemplateEngine(template);

const result1 = engine.render(data1);
console.log(result1);
// <div class="message">
//   <p>Welcome, Alice!</p>
// </div>

const result2 = engine.render(data2);
console.log(result2);
// <div class="message">
//   <p>Please log in.</p>
// </div>

演習3:商品リストの生成

次のテンプレートを使用して、商品リストを生成するテンプレートエンジンを実装してください。商品データには名前と価格が含まれます。

const template = `
  <h1>Product List</h1>
  <ul>
    {{#each products}}
    <li>{{this.name}} - ${{this.price}}</li>
    {{/each}}
  </ul>
`;

const data = {
  products: [
    { name: "Laptop", price: 999.99 },
    { name: "Phone", price: 499.99 },
    { name: "Tablet", price: 299.99 }
  ]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>Product List</h1>
// <ul>
//   <li>Laptop - $999.99</li>
//   <li>Phone - $499.99</li>
//   <li>Tablet - $299.99</li>
// </ul>

演習4:複雑な条件分岐とループの実装

次のテンプレートを使用して、複雑な条件分岐とループを含むテンプレートエンジンを実装してください。各ユーザーのステータスに基づいてメッセージを表示し、ユーザーごとにタスクリストを表示します。

const template = `
  <h1>User Tasks</h1>
  {{#each users}}
  <div class="user">
    <h2>{{this.name}}</h2>
    {{#if this.isActive}}
    <p>Status: Active</p>
    {{else}}
    <p>Status: Inactive</p>
    {{/if}}
    <ul>
      {{#each this.tasks}}
      <li>{{this}}</li>
      {{/each}}
    </ul>
  </div>
  {{/each}}
`;

const data = {
  users: [
    { name: "Alice", isActive: true, tasks: ["Task 1", "Task 2"] },
    { name: "Bob", isActive: false, tasks: ["Task 3"] },
    { name: "Charlie", isActive: true, tasks: ["Task 4", "Task 5", "Task 6"] }
  ]
};

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result);
// <h1>User Tasks</h1>
// <div class="user">
//   <h2>Alice</h2>
//   <p>Status: Active</p>
//   <ul>
//     <li>Task 1</li>
//     <li>Task 2</li>
//   </ul>
// </div>
// <div class="user">
//   <h2>Bob</h2>
//   <p>Status: Inactive</p>
//   <ul>
//     <li>Task 3</li>
//   </ul>
// </div>
// <div class="user">
//   <h2>Charlie</h2>
//   <p>Status: Active</p>
//   <ul>
//     <li>Task 4</li>
//     <li>Task 5</li>
//     <li>Task 6</li>
//   </ul>
// </div>

これらの演習問題に取り組むことで、テンプレートエンジンの実装と使用方法についての理解が深まります。次章では、テンプレートエンジンを使用する際に発生する可能性のある問題とその解決方法について説明します。

トラブルシューティング

テンプレートエンジンを使用して動的なコンテンツを生成する際に、さまざまな問題が発生することがあります。ここでは、よくある問題とその解決方法について説明します。

プレースホルダーの置換が行われない

プレースホルダーが正しく置換されない場合、主に以下の原因が考えられます:

  1. プレースホルダーの形式が正しくない:テンプレート内のプレースホルダーが{{key}}形式になっているか確認してください。
  2. データが不足している:データオブジェクトにプレースホルダーに対応するキーが含まれているか確認してください。
const template = "<p>{{name}}</p>";
const data = { name: "Alice" };

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result); // <p>Alice</p>

条件分岐が正しく動作しない

条件分岐が期待通りに動作しない場合、以下の点を確認してください:

  1. 条件の評価:データオブジェクト内の条件が正しく評価されているか確認してください。
  2. 条件式の記述:テンプレート内の条件式が{{#if condition}}...{{/if}}形式になっているか確認してください。
const template = `
  {{#if isLoggedIn}}
  <p>Welcome, {{username}}!</p>
  {{else}}
  <p>Please log in.</p>
  {{/if}}
`;

const data = { isLoggedIn: true, username: "Alice" };

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result); // <p>Welcome, Alice!</p>

ループが正しく動作しない

ループが正しく動作しない場合、以下の点を確認してください:

  1. 配列データの提供:データオブジェクト内のループ対象が配列であるか確認してください。
  2. ループの記述:テンプレート内のループ構文が{{#each array}}...{{/each}}形式になっているか確認してください。
const template = `
  <ul>
    {{#each items}}
    <li>{{this}}</li>
    {{/each}}
  </ul>
`;

const data = { items: ["Item 1", "Item 2", "Item 3"] };

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result); 
// <ul>
//   <li>Item 1</li>
//   <li>Item 2</li>
//   <li>Item 3</li>
// </ul>

エスケープ処理の問題

テンプレートエンジンを使用してユーザー入力を処理する場合、HTMLエスケープを行わないとXSS(クロスサイトスクリプティング)攻撃のリスクがあります。データをHTMLに埋め込む前に適切にエスケープ処理を行いましょう。

class TemplateEngine {
  constructor(template) {
    this.template = template;
  }

  escapeHtml(str) {
    return str.replace(/&/g, "&amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#039;");
  }

  render(data) {
    let output = this.template;
    for (const key in data) {
      const placeholder = new RegExp(`{{${key}}}`, 'g');
      output = output.replace(placeholder, this.escapeHtml(data[key] || ''));
    }
    return output;
  }
}

const template = "<p>{{message}}</p>";
const data = { message: "<script>alert('XSS');</script>" };

const engine = new TemplateEngine(template);
const result = engine.render(data);
console.log(result); // <p>&lt;script&gt;alert('XSS');&lt;/script&gt;</p>

デバッグ方法

テンプレートエンジンをデバッグする際には、以下の方法を試してみてください:

  1. コンソールログの使用:テンプレートやデータの中間状態をconsole.logで出力し、正しく処理されているか確認します。
  2. テストケースの作成:さまざまな入力データに対してテンプレートエンジンの動作をテストし、期待通りの出力が得られるか確認します。
// テストデータの例
const templates = [
  { template: "<p>{{name}}</p>", data: { name: "Alice" }, expected: "<p>Alice</p>" },
  { template: "{{#if isLoggedIn}}<p>Welcome, {{username}}!</p>{{/if}}", data: { isLoggedIn: true, username: "Alice" }, expected: "<p>Welcome, Alice!</p>" },
  { template: "{{#each items}}<li>{{this}}</li>{{/each}}", data: { items: ["Item 1", "Item 2"] }, expected: "<li>Item 1</li><li>Item 2</li>" }
];

templates.forEach(({ template, data, expected }) => {
  const engine = new TemplateEngine(template);
  const result = engine.render(data);
  console.log(result === expected ? "Passed" : "Failed");
});

これらのトラブルシューティング方法を活用して、テンプレートエンジンの問題を効果的に解決してください。次章では、本記事の内容をまとめます。

まとめ

本記事では、JavaScriptでクラスを使ったテンプレートエンジンの構築方法について詳しく解説しました。テンプレートエンジンの基本概念から始まり、クラスの基本構造、テンプレートの設計、変数の埋め込み、条件分岐とループの実装、動的なHTML生成の応用例、演習問題、そしてトラブルシューティングの方法までを網羅しました。

テンプレートエンジンは、コードの再利用性と保守性を高め、動的なコンテンツ生成を効率的に行うための強力なツールです。この記事を通じて、テンプレートエンジンの基本的な仕組みを理解し、実際に実装するためのスキルを身につけることができたと思います。

これからの開発において、テンプレートエンジンを活用して効率的なコーディングを目指してください。テンプレートエンジンの応用範囲は広く、Web開発だけでなく、メールテンプレートやドキュメント生成など、さまざまな場面で役立つことでしょう。実際に手を動かし、さらに高度なテンプレートエンジンの機能を探索してみてください。

コメント

コメントする

目次