PythonでHTMLテンプレートにJSONデータを流し込んで複数のHTMLファイルを一括生成する

共通フォーマットのHTMLテンプレートファイルに対して、別で管理しているデータを流し込んで複数のHTMLファイルを一括で生成してみたいと思います。

## 実行環境
・Node.js 14.15
・Python 3.8
・Jinja 

## 手順
1. JSONデータの作成
2. テンプレートHTMLの作成
3. Pythonスクリプトの作成
4. 実行

## JSONデータの作成
JSONのデータは外部APIなどで取得してもよいのですが、今回はローカルで作成し、管理・運用がしやすいようにTypeScriptのオブジェクトから作成したいと思います。

```types/index.ts
// データの型定義ファイル

type Post = {
  id: string;
  date: string;
  title: string;
  author: string;
  body: string;
};

export interface PageData {
  filename: string;
  meta: {
    title: string;
    description: string;
  }
  posts: Post[];
}
```

`/data/pages` ディレクトリにそれぞれのページごとにデータを管理します。

`sample-a.html` を管理するデータ

```/data/pages/sample-a.ts
import { PageData } from '../../types';

export const sampleA: PageData = {
  filename: 'sample-a.html',
  meta: {
    title: 'サンプルA',
    description: 'サンプルAの説明文',
  },
  posts: [
    {
     id: '001',
     date: '2021/01/01',
     title: '初めての投稿',
     author: 'いぬ',
     body: 'hoge',
    },
    {
     id: '002',
     date: '2021/01/02',
     title: '面白いタイトル！',
     author: 'ねこ',
     body: 'hogehoge',
    }
  ]
}
```

`sample-b.html` を管理するデータ

```/data/pages/sample-b.ts
import { PageData } from '../../types';

export const sampleB: PageData = {
  filename: 'sample-b.html',
  meta: {
    title: 'サンプルB',
    description: 'サンプルBの説明文',
  },
  posts: [
    {
     id: '003',
     date: '2021/01/05',
     title: '心に残る映画',
     author: 'いぬ',
     body: '〇〇〇〇〇〇〇〇〇〇',
    },
  ]
}
```

これらのTypeScriptオブジェクトを結合してJSONデータを生成します。

JSONデータを生成するためのスクリプトを書きます。

```script.ts
import { writeFile } from "fs"; // <- 事前に "yarn add -D fs" でモジュールをインストール
import { PageData } from "./types";
// データのインポート
import { sampleA } from "./data/pages/sample-a";
import { sampleB } from "./data/pages/sample-b";

// 配列に入れる
const data: PageData[] = [sampleA, sampleB];

// JSONデータのファイルとして書き出し
writeFile('data.json', JSON.stringify(data, null, "  "), () => {});
```

これをJSにコンパイルしてNode.jsで実行します。

```bash
tsc script.ts
node script.js
```

これで `data.json` が生成されます。

```data.json
[
  {
    "filename": "sample-a.html",
    "meta": {
      "title": "サンプルA",
      "description": "サンプルAの説明文"
    },
    "posts": [
      {
        "id": "001",
        "date": "2021/01/01",
        "title": "初めての投稿",
        "author": "いぬ",
        "body": "hoge"
      },
      {
        "id": "002",
        "date": "2021/01/02",
        "title": "面白いタイトル！",
        "author": "ねこ",
        "body": "hogehoge"
      }
    ]
  },
  {
    "filename": "sample-b.html",
    "meta": {
      "title": "サンプルB",
      "description": "サンプルBの説明文"
    },
    "posts": [
      {
        "id": "003",
        "date": "2021/01/05",
        "title": "心に残る映画",
        "author": "いぬ",
        "body": "〇〇〇〇〇〇〇〇〇〇"
      }
    ]
  }
]
```


## テンプレートHTMLの作成
共通フォーマットのテンプレートHTMLを作成します。
テンプレートは Python のライブラリ Jinja2 で使用するので、Jinja2 の構文に従って作成します。

```templates/page.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ meta.title }}</title>
  <meta name="description" content="{{ meta.description }}">
</head>
<body>
  <h1>{{ meta.title }}</h1>
  <div class="posts">
    {% for post in posts %}
      <div class="post" data-id="{{ post.id }}">
        <div class="date">{{ post.date}}</div>
        <h2 class="title">{{ post.title }}</h2>
        <div class="author">{{ post.author }}</div>
        <div class="body">{{ post.body }}</div>
      </div>
    {% endfor %}
  </div>
</body>
</html>
```
作成した `PageData` の型に従って、JSONから受け取ったデータを入れる変数を `{{  }}` の中に記述します。

forループやif文などの制御構文は `{%  %}` の中に記述して使うことができます。
詳細については [Jinja2](https://jinja.palletsprojects.com/en/2.11.x/) のページをご参照ください。


## Pythonスクリプトの作成
データとテンプレートは出来上がったので、いよいよビルドするためのスクリプトをPythonで書いていきます。

まずは Jinja2 ライブラリをインストールします。

```bash
pip install jinja2
```

```generate.py
from jinja2 import Environment, FileSystemLoader
import json
import os

# 1.JSONからデータの読み込み
json_open = open('./data.json', 'r')
# データの格納された配列
items = json.load(json_open)

# 2.テンプレートファイルの読み込み
env = Environment(loader = FileSystemLoader('./templates'), autoescape = True)
template = env.get_template('page.html')

# 3.テンプレートにデータの流し込み
build_dir = 'dist'
# distディレクトリの存在を確認。なければ作成する
if not os.path.isdir(build_dir):
  os.mkdir(build_dir)

# データ配列の数だけ実行
for item in items:
  # テンプレートにデータを流し込んでHTMLを作成
  parse_html = template.render(item)

  # 保存先のパスを作成
  path = f"{build_dir}/{item['filename']}"

  # HTMLファイルの生成と保存
  with open(path, 'w') as file:
    file.write(parse_html)
```

## ビルドの実行
```bash
python generate.py
```

これで、`dist/` ディレクトリに目的のHTMLファイル（ここでは `sample-a.html` と `sample-b.html` ）が生成されていれば完了です！


ソースコードはGitHubに載せています。
https://github.com/hiropy0123/python-html-generate