📑

Chat-GPT と一緒に簡単なソフトウェア開発を行った所感

2023/04/16に公開

やったこと

静的サイトジェネレーター開発を Chat-GPT と共に行った。

  • 開発環境を準備させる
  • ロジックを実装させる
  • CSS を当てさせる

あたりを Chat-GPT に任せてみた。
AI 協働初期の人間クンの試行錯誤の跡として所感を記す。

できたもの

このサイト

https://github.com/azoson/azoson.github.io

良いと思ったこと

開発環境構築とかとりあえずのプロトタイピングとかの所謂「たたき台」を作ってもらうのは体験が良かった。
同時に Chat-GPT クンの出力する "それらしい" たたき台コードが動かない時に、人間サマの私が「こいつ嘘ついてんじゃねーか」とストレスを感じてしまった。
ところで CSS はテキトーに書いてもサイトが開けなくなったりはせず、それらしいコードでそれらしい見た目が出来上がってるように感じ、面白かった。

開発環境構築系

開発環境構築周りにおいて、ボイラープレート的な部分を雑に頼む相手として Chat-GPT を便利に感じた。
質問にもそれらしい事を教えてくれるんで、さわったことない技術を学ぶときなどに気持ちよさそう。

一方で、なんか上手く行かないのを詰めたときになんかテキトーに謝られてテキトーに別の方法を提示されたりする点は単純に気分が悪い。
最終的に動かしたり整えたりの部分を自分でやれるときにのみ使った方が良いのかもしれない。
開発環境構築 とかいう段階を仮定する開発方法自体がヒューマン本位の古臭いアホな考え方で AI サマに寄り添っていないという可能性もある。

nix-shell を吐き出して貰った時の会話は以下:

開発環境の構築について、ローカルの環境を汚染したくないので、開発環境用の nix-shell の準備から始めたいと思っています。
nix-shell 用の nix ファイルを出力してもらえますか?


Chat-GPT

もちろんです。以下に、nix-shell 用の nix ファイルを提供します。このファイルでは、Node.js, TypeScript, Handlebars などの必要なパッケージをインストールして、開発環境を構築します。

ファイル名: shell.nix

{ pkgs ? import <nixpkgs> { } }:

let
  nodePackages = (import "${builtins.fetchTarball {
    url = "https://github.com/nix-community/node2nix/archive/master.tar.gz";
  }}/default.nix").package.override {
    nodejs = pkgs.nodejs-16_x;
  };
in
pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs-16_x
    yarn
    git
    nodePackages.handlebars
    nodePackages.typescript
    nodePackages.ts-node
  ];

  shellHook = ''
    export NODE_PATH="${nodePackages}/lib/node_modules:${pkgs.nodejs-16_x}/lib/node_modules"
  '';
}

この shell.nix ファイルをプロジェクトのルートディレクトリに保存し、ターミナルで nix-shell コマンドを実行してください。これにより、Node.js, TypeScript, Handlebars などの必要なパッケージがインストールされた環境が構築されます。開発環境が整ったら、静的サイトジェネレーターの開発を進めましょう。


この shell.nix を利用すると、以下のエラーが出力されました。

error: unable to download 'https://github.com/nix-community/node2nix/archive/master.tar.gz': HTTP error 404

shell.nix を修正してください。


Chat-GPT

申し訳ありません。URLが間違っていました。以下に修正された shell.nix ファイルを提供します。

ファイル名: shell.nix

{ pkgs ? import <nixpkgs> { } }:

let
  nodePackages = (import "${builtins.fetchTarball {
    url = "https://github.com/svanderburg/node2nix/archive/1.9.0.tar.gz";
  }}/default.nix").package.override {
    nodejs = pkgs.nodejs-16_x;
  };
in
pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs-16_x
    yarn
    git
    nodePackages.handlebars
    nodePackages.typescript
    nodePackages.ts-node
  ];

  shellHook = ''
    export NODE_PATH="${nodePackages}/lib/node_modules:${pkgs.nodejs-16_x}/lib/node_modules"
  '';
}

この修正された shell.nix ファイルをプロジェクトのルートディレクトリに保存し、ターミナルで nix-shell コマンドを実行してください。エラーが解消され、Node.js, TypeScript, Handlebars などの必要なパッケージがインストールされた環境が構築されるはずです。開発環境が整ったら、静的サイトジェネレーターの開発を進めましょう。


nodePackages の download がやはり上手くいきません。
この nodePackags = の部分は何をしているのでしょうか?
また、あなたが提示した以外の方法はありますか?
その方法について公式なドキュメントがあれば提示してください。


Chat-GPT

すみません、問題が解決しなかったようで。nodePackages の部分では、node2nix を使って Node.js のパッケージを Nix で管理しやすい形式に変換しています。ただし、この方法で問題が発生しているため、別の方法を検討しましょう。

代わりに、package.json を使って Node.js のパッケージを管理する方法を提案します。次の手順で進めてください。

プロジェクトのルートディレクトリに package.json ファイルを作成します。
以下の内容を package.json に記述します。

{
  "name": "static-site-generator",
  "version": "1.0.0",
  "description": "A simple static site generator using TypeScript and Handlebars",
  "main": "index.ts",
  "scripts": {
    "build": "tsc"
  },
  "dependencies": {
    "handlebars": "^4.7.7"
  },
  "devDependencies": {
    "typescript": "^4.5.2",
    "@types/handlebars": "^4.1.1"
  }
}

以下の内容で修正された shell.nix ファイルをプロジェクトのルートディレクトリに保存します。

{ pkgs ? import <nixpkgs> { } }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs-16_x
    yarn
    git
  ];

  shellHook = ''
    export NODE_PATH="${pkgs.nodejs-16_x}/lib/node_modules"
  '';
}
ターミナルで nix-shell コマンドを実行して、開発環境を構築します。
nix-shell の中で yarn install コマンドを実行して、package.json で定義されたパッケージをインストールします。
これで、Node.js, TypeScript, Handlebars などの必要なパッケージがインストールされた環境が構築されます。開発環境が整ったら、静的サイトジェネレーターの開発を進めましょう。

公式ドキュメント:

初期プロトタイピング

一番最初の「とりあえず何か動く状態にしたい」みたいなところの実装やってもらうのは良さそう。
自分が知らんライブラリでもとりあえずで使ってもらえる。
使ったコードを読んでライブラリの使い方を察する「察し学習」ができて面白かった。

ところで、実のところ「とりあえず動く」になっておらず、動かすために人間サマが色々調整する必要があった。
対処法を人間サマの私が発見した後に AI クンの自主的な思考を促す虚無の対話をしてしまい、ややイラついた。
AI クンに教えてあげると今後の出力に何か良い影響があるのではと思ってやったことだが、別に良い影響は感じなかった。
これもまた AI サマに寄り添っていないやり口だったかもしれない。

会話の例は以下:

「Markdown ファイルを解析し、HTML に変換するロジック」の実装例を教えてください。


Chat-GPT

もちろんです。ここでは、marked というパッケージを使って Markdown ファイルを解析し、HTML に変換する実装例を示します。

まず、yarn add marked コマンドを実行して、marked パッケージをインストールします。また、型定義を追加するために yarn add -D @types/marked を実行します。

次に、src/index.ts ファイルに以下のコードを追加します。

import * as fs from 'fs';
import * as path from 'path';
import * as Handlebars from 'handlebars';
import * as marked from 'marked';

// ディレクトリを再帰的に読み込む関数
function readDirRecursively(dirPath: string): string[] {
  let filePaths: string[] = [];

  const entries = fs.readdirSync(dirPath, { withFileTypes: true });

  for (const entry of entries) {
    const entryPath = path.join(dirPath, entry.name);
    if (entry.isDirectory()) {
      filePaths = filePaths.concat(readDirRecursively(entryPath));
    } else {
      filePaths.push(entryPath);
    }
  }

  return filePaths;
}

// Markdown ファイルを HTML に変換する関数
function convertMarkdownToHtml(markdownFilePath: string): string {
  const markdownContent = fs.readFileSync(markdownFilePath, 'utf-8');
  const htmlContent = marked(markdownContent);

  return htmlContent;
}

// メイン処理
function main() {
  // コンテンツディレクトリからすべての Markdown ファイルを読み込む
  const contentDir = './src/content';
  const markdownFilePaths = readDirRecursively(contentDir).filter((filePath) =>
    filePath.endsWith('.md'),
  );

  // 各 Markdown ファイルを HTML に変換し、テンプレートに適用する
  for (const markdownFilePath of markdownFilePaths) {
    const htmlContent = convertMarkdownToHtml(markdownFilePath);

    // テンプレートを読み込む
    const templatePath = './src/templates/index.hbs';
    const templateContent = fs.readFileSync(templatePath, 'utf-8');
    const template = Handlebars.compile(templateContent);

    // テンプレートにデータを適用して HTML を生成する
    const data = {
      content: htmlContent,
    };
    const outputHtml = template(data);

    // 出力ディレクトリに HTML ファイルを保存する
    const outputDir = './dist';
    const outputFilePath = path.join(
      outputDir,
      path.relative(contentDir, markdownFilePath).replace(/\.md$/, '.html'),
    );
    fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });
    fs.writeFileSync(outputFilePath, outputHtml);
  }
}

main();

このコードには、Markdown ファイルを読み込んで HTML に変換し、Handlebars テンプレートに適用する処理が含まれています。また、readDirRecursively 関数を使って、ディレクトリ構造を再帰的に読み込む処理も行っています。このコードを実行することで、src/content ディレクトリ内のすべての Markdown ファイルが HTML に変換され、dist ディレクトリに出力されます。

この実装例では、handlebars テンプレートエンジンを使用していますが、他のテンプレートエンジンに切り替えることも可能です。また、必要に応じて、CSS や JavaScript の適用、ページのメタデータの設定などの機能を追加していくことができます。

静的サイトジェネレーターの基本的な機能が整ったら、GitHub Actions を使ってデプロイの自動化を行いましょう。これにより、リポジトリへのプッシュ時に自動でビルド・デプロイが実行され、GitHub Pages でサイトが公開されるようになります。


このコードを実行するためのコマンドはなんですか?


Chat-GPT

このコードを実行するには、まず TypeScript ファイルを JavaScript にコンパイルする必要があります。そのために、ts-node をインストールしましょう。ts-node は、TypeScript を直接実行するためのツールです。

以下のコマンドで ts-node をインストールしてください:

yarn add -D ts-node

インストールが完了したら、以下のコマンドで src/index.ts を実行できます。

yarn ts-node src/index.ts

このコマンドを実行すると、src/index.ts のコードが実行され、src/content 内の Markdown ファイルが HTML に変換されて dist ディレクトリに出力されます。

また、package.json にスクリプトを追加しておくと、コマンドの実行が簡単になります。package.json に以下の内容を追加してください。

{
  "scripts": {
    "build": "tsc",
    "start": "ts-node src/index.ts"
  }
}

これで、yarn start コマンドを実行することで、src/index.ts を実行できます。また、yarn build コマンドを実行することで、プロジェクト内のすべての TypeScript ファイルを JavaScript にコンパイルできます。


yarn ts-node src/index.ts したところ次のエラーが出力されました:

  • error TS2307: Cannot find module 'fs' or its corresponding type declarations.
  • error TS2307: Cannot find module 'path' or its corresponding type declarations.
  • error TS2307: Cannot find module 'marked' or its corresponding type declarations.

修正方法を教えてください


Chat-GPT

このエラーは、fs、path、および marked モジュールの型定義が見つからないために発生しています。fs と path は Node.js の組み込みモジュールであり、型定義は @types/node に含まれています。marked の型定義は @types/marked に含まれています。

まず、@types/node と @types/marked をインストールしてください:

yarn add -D @types/node @types/marked

次に、tsconfig.json ファイルをプロジェクトのルートディレクトリに作成し、以下の内容を追加してください:

{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "ES2019",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "lib": ["ES2019", "DOM"]
  },
  "include": ["src/**/*.ts"]
}

これにより、TypeScript コンパイラが型定義を適切に解決できるようになります。再度 yarn ts-node src/index.ts コマンドを実行して、エラーが解消されるか確認してください。


以下のエラーが出力されました:

src/index.ts:27:23 - error TS2349: This expression is not callable.
  Type 'typeof import("/mnt/c/Users/azoson/Documents/programming/my-ssg/node_modules/@types/marked/index")' has no call signatures.

27   const htmlContent = marked(markdownContent);

修正方法を教えてください


Chat-GPT

申し訳ありません。marked のインポートが正しくないため、このエラーが発生しています。marked をデフォルトインポートとして使用するために、esModuleInterop オプションが true に設定されていることを利用して、インポート方法を変更しましょう。

src/index.ts ファイル内の marked のインポート部分を次のように変更してください。

// Before
import * as marked from 'marked';

// After
import marked from 'marked';

これにより、marked 関数が正しくインポートされ、エラーが解消されるはずです。再度 yarn ts-node src/index.ts コマンドを実行して、エラーが解消されるか確認してください。


まず、marked が dependecies に追加されていないのではないですか?
@types/marked は型定義のみを含むので、marked を dependencies に追加しないと上記の実行ファイルは動きません。
また、handlebars について、あなたが上述した通り[1]公式の型定義が存在するので @types/ を devDependencies に追加する必要はありません。
よってこれを削除しました。

以上の対応の結果、 index.ts が実行可能になりました。

CSS を書かせる

これも色々新しいフレームワーク使うときに便利。
また、言葉で指定した外見を結構理解してくれたことへの嬉しさもあった。
今回の例については Tailwind 使わせてるからそれらしい話をしてくれただけかもしれんので、他のフレームワーク使わせたりフレームワークの利用を禁じたりした場合の対話も試してみたい。

会話例

ありがとうございます。
index.hbs に自己紹介用のカードを作りたいと思います。
カードは左右に縦罫線で2分割し、左側にアイコンを表示するつもりです。
右側には自己紹介文と各 SNS へのリンクを張るつもりです。
また、この自己紹介用のカードはウィンドウ幅が狭い時には上下分割に変化してほしいです。
index.hbs にどのような要素を追加するべきですか?


Chat-GPT

Tailwind CSS を使用して自己紹介用のカードを作成するには、以下のような要素を index.hbs に追加します。

<div class="flex flex-col md:flex-row items-center md:items-start bg-white shadow-md rounded-lg p-6 mb-8">
  <!-- 左側のアイコン部分 -->
  <div class="w-full md:w-1/2 md:pr-6 mb-6 md:mb-0">
    <img src="path/to/your/icon.jpg" alt="Your Name" class="rounded-full h-32 w-32 mx-auto md:mx-0">
  </div>
  <!-- 右側の自己紹介文とSNSリンク -->
  <div class="w-full md:w-1/2">
    <h2 class="text-xl font-bold mb-4">Your Name</h2>
    <p class="mb-4">
      自己紹介文をここに記述します。
    </p>
    <div class="flex space-x-4">
      <a href="https://twitter.com/your_username" class="text-blue-500 hover:text-blue-700" target="_blank" rel="noopener noreferrer">
        <i class="fab fa-twitter"></i>
      </a>
      <a href="https://github.com/your_username" class="text-gray-800 hover:text-gray-900" target="_blank" rel="noopener noreferrer">
        <i class="fab fa-github"></i>
      </a>
      <!-- 他のSNSリンクも追加できます -->
    </div>
  </div>
</div>

ここでは、flex と flex-col を使用して上下に分割されたレイアウトを作成し、md:flex-row を使用して中間デバイス(md 以上)で左右に分割されるように設定しています。また、アイコンと自己紹介文のエリアをそれぞれ 1/2 の幅で表示するために、w-full と md:w-1/2 を使用しています。

最後に、FontAwesome のアイコンを使用して SNS リンクを作成しています。FontAwesome の CDN を使用している場合は、<i class="fab fa-twitter"></i> のような記述でアイコンを表示できます。必要に応じて、他の SNS のリンクも追加できます。


w-full md:w-1/2 md:pr-6 mb-6 md:mb-0 とはどういう意味を持つのでしょうか?


Chat-GPT

w-full md:w-1/2 md:pr-6 mb-6 md:mb-0 というクラスは、Tailwind CSS を使用したレスポンシブデザインの設定です。それぞれのクラスは以下のような意味を持ちます。

  • w-full: 要素の幅を100%に設定します。これにより、小さい画面サイズでは要素が画面いっぱいに広がります。
  • md:w-1/2: 画面サイズが中間(md)以上の場合、要素の幅を50%に設定します。これにより、大きい画面サイズでは要素が画面の半分の幅を占めるようになります。
  • md:pr-6: 画面サイズが中間(md)以上の場合、要素の右側に内部余白(padding)を1.5rem(6 × 0.25rem)追加します。これは左右に分割されたカードの間にスペースを作るためです。
  • mb-6: 要素の下側に外部余白(margin)を1.5rem(6 × 0.25rem)追加します。これにより、要素の下にある他の要素との間にスペースができます。
  • md:mb-0: 画面サイズが中間(md)以上の場合、要素の下側の外部余白(margin)を0に設定します。これにより、大きい画面サイズでは左右に分割されたカードが上下に隙間なく並ぶようになります。

このように、Tailwind CSS を使って、異なる画面サイズに対して柔軟にレイアウトを調整することができます。


では、左側のアイコン部分をもう少し狭くしたい、具体的には画面横幅 1/3 程度にしたい場合はどうすればよいのでしょうか?


Chat-GPT

画面横幅の1/3に左側のアイコン部分を設定する場合は、次のようにTailwind CSSのクラスを修正します。

まず、左側のアイコン部分に適用するクラスを以下のように変更します。

<div class="w-full md:w-1/3 md:pr-6 mb-6 md:mb-0">

md:w-1/2md:w-1/3 に変更することで、中間画面サイズ(md)以上の場合に要素の幅を33.3333%に設定します。

次に、右側の自己紹介文とSNSリンク部分に適用するクラスを以下のように変更します。

<div class="w-full md:w-2/3 mb-6 md:mb-0">

md:w-1/2md:w-2/3 に変更することで、中間画面サイズ(md)以上の場合に要素の幅を66.6667%に設定します。

これにより、中間画面サイズ以上では、左側のアイコン部分が画面横幅の1/3を占め、右側の自己紹介文とSNSリンク部分が画面横幅の2/3を占めるようになります。


ありがとうございます。
今自己紹介用カードが画面上部横幅いっぱいに広がっていますが、これをページ一覧のカードと同じサイズ感にしたいです。
どうすればよいですか?


Chat-GPT

自己紹介用カードのサイズをページ一覧のカードと同じサイズ感にするには、自己紹介用カードの外側に max-wmx-auto クラスを適用するコンテナを追加します。これにより、自己紹介用カードの幅が最大幅に制限され、中央揃えになります。

以下のように自己紹介用カードの外側にコンテナを追加してください。

<div class="max-w-4xl mx-auto">
  <div class="flex flex-wrap">
    <div class="w-full md:w-1/3 md:pr-6 mb-6 md:mb-0">
      <!-- 左側のアイコン部分 -->
    </div>
    <div class="w-full md:w-2/3 mb-6 md:mb-0">
      <!-- 右側の自己紹介文とSNSリンク部分 -->
    </div>
  </div>
</div>

ここでは、max-w-4xl を使って最大幅を設定し(4xl = 56rem = 896px)、mx-auto で左右のマージンを自動設定して中央揃えにしています。必要に応じて、最大幅を他の値に変更できます。例えば、max-w-xlmax-w-2xlmax-w-3xlなどです。

これで、自己紹介用カードとページ一覧のカードが同じサイズ感になります。

工夫したこと

プロトタイピング以降、既存のコードに変更を加えていくわけだが、これはやや面倒に感じた。

最終的に

  • 現在のコード
  • 加えてほしい変更

の両方をメッセージに投げ込むと誠意が通じた。
また、加えてほしい変更について、「A をしないようにし、B をする」として外してほしい要件についても明言すると良かった。

これらのやり取りについて何度も自分で色々書くのはだるい。
関数の動作を説明するためのコメントを書くべく指示するとそれなりのものが出てくるしソースコード内からのコピペで諸々済むんで楽になった。

会話の例:

現在の generateIndexPage 関数の実装は以下です:

export function generateIndexPage(pages: Array<{ data: PageData; metaData: PageMetaData }>): void {
  const indexTemplate = fs.readFileSync(path.join(TEMPLATES_DIR, 'index.hbs'), 'utf-8');

  const sortedPagesByCategory = pages.reduce(
    (
      acc: {
        [category: string]: Array<{
          url: string;
          title: string;
          createdAt: string;
        }>;
      },
      page,
    ) => {
      const { metaData } = page;
      const category = metaData.category ?? '未分類';

      if (!acc[category]) {
        acc[category] = [];
      }

      acc[category].push({
        url: page.data.url,
        title: page.data.title,
        createdAt: metaData.createdAt,
      });
      return acc;
    },
    {},
  );

  Object.keys(sortedPagesByCategory).forEach((category) => {
    sortedPagesByCategory[category].sort((a, b) => {
      return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
    });
  });

  const sortedCategories = Object.keys(sortedPagesByCategory).sort();
  const context = {
    categories: sortedCategories.reduce(
      (
        acc: {
          [category: string]: Array<{
            url: string;
            title: string;
            createdAt: string;
          }>;
        },
        category,
      ) => {
        acc[category] = sortedPagesByCategory[category];
        return acc;
      },
      {},
    ),
  };

  const indexPageContent = Handlebars.compile(indexTemplate)(context);

  fs.writeFileSync(path.join(OUTPUT_DIR, 'index.html'), indexPageContent);
}

また、現在の index.hbs において、以下のように各カテゴリごとにカードを生成してカテゴリ内の記事を列挙しています:

      <h1 class='text-4xl font-bold mb-8'>ページ一覧</h1>
      {{#each categories}}
        <div class='bg-white shadow-md rounded p-4 mb-8'>
          <h2 class='text-2xl font-bold mb-4'>{{@key}}</h2>
          <ul class='divide-y divide-gray-200'>
            {{#each this}}
              <li class='py-2'>
                <a class='text-blue-600 hover:text-blue-800' href='{{url}}'>{{title}}</a>
              </li>
            {{/each}}
          </ul>
        </div>
      {{/each}}

ここで、以下の要件を満たすように関数および hbs に変更を加えてください:

  • 記事の createdAt の month 毎に、各月の記事が列挙されている
    • この列挙には - を頭に置くリストを使う
    • 月ごとに何月の記事かわかるように heading を挿入する
  • hbs / 関数ともに、同様の出力を得るための prompt(要件情報やそのほか前提条件を含むもの)をコメントとして書いておく
  • category 毎にページを分ける必要はありません
  • 記事の createdAt の month 毎に各月の記事を分けたいと考えています

Chat-GPT

generateIndexPage関数を修正して、ページをカテゴリーごとではなく、月ごとに分割しましょう。以下の実装は、各月にページをグループ化し、ページのリストに作成日の昇順でソートします。

export function generateIndexPage(pages: Array<{ data: PageData; metaData: PageMetaData }>): void {
  const indexTemplate = fs.readFileSync(path.join(TEMPLATES_DIR, 'index.hbs'), 'utf-8');

  const sortedPagesByMonth = pages.reduce(
    (
      acc: {
        [month: string]: Array<{
          url: string;
          title: string;
          createdAt: string;
        }>;
      },
      page,
    ) => {
      const { metaData } = page;
      const month = new Date(metaData.createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long' });

      if (!acc[month]) {
        acc[month] = [];
      }

      acc[month].push({
        url: page.data.url,
        title: page.data.title,
        createdAt: metaData.createdAt,
      });
      return acc;
    },
    {},
  );

  Object.keys(sortedPagesByMonth).forEach((month) => {
    sortedPagesByMonth[month].sort((a, b) => {
      return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
    });
  });

  const sortedMonths = Object.keys(sortedPagesByMonth).sort();
  const context = {
    months: sortedMonths.reduce(
      (
        acc: {
          [month: string]: Array<{
            url: string;
            title: string;
            createdAt: string;
          }>;
        },
        month,
      ) => {
        acc[month] = sortedPagesByMonth[month];
        return acc;
      },
      {},
    ),
  };

  const indexPageContent = Handlebars.compile(indexTemplate)(context);

  fs.writeFileSync(path.join(OUTPUT_DIR, 'index.html'), indexPageContent);
}

index.hbsファイルを更新して、月ごとにページを表示するようにします。

<h1 class='text-4xl font-bold mb-8'>ページ一覧</h1>
{{#each months}}
  <div class='bg-white shadow-md rounded p-4 mb-8'>
    <h2 class='text-2xl font-bold mb-4'>{{@key}}</h2>
    <ul class='divide-y divide-gray-200'>
      {{#each this}}
        <li class='py-2'>
          <a class='text-blue-600 hover:text-blue-800' href='{{url}}'>{{title}}</a>
        </li>
      {{/each}}
    </ul>
  </div>
{{/each}}

この変更により、ページが月ごとにグループ化され、昇順でソートされた作成日が表示されます。


ありがとうございます。
この generateIndexPage の動作について、 jsdocs 形式でコメントをお願いします。


Chat-GPT

以下に、generateIndexPage関数の動作を説明するためのJSDocコメントを記載します。

/**
 * Generates the index page for the static site.
 *
 * @param {Array<{ data: PageData; metaData: PageMetaData }>} pages - Array of page objects with data and metadata.
 * @returns {void} - No return value.
 *
 * @example
 * const pages = [
 *   {
 *     data: { title: 'Page 1', url: '/page-1' },
 *     metaData: { createdAt: '2023-01-01', category: 'Category 1' }
 *   },
 *   {
 *     data: { title: 'Page 2', url: '/page-2' },
 *     metaData: { createdAt: '2023-02-01', category: 'Category 2' }
 *   }
 * ];
 * generateIndexPage(pages);
 *
 * This function reads the index.hbs template file and processes it with Handlebars.
 * It groups the pages by month, based on their 'createdAt' property, and sorts the pages in each group by date in ascending order.
 * The resulting index page content is saved as index.html in the output directory.
 */
export function generateIndexPage(pages: Array<{ data: PageData; metaData: PageMetaData }>): void {
  // ...
}

このJSDocコメントは、generateIndexPage関数がどのように機能するかを説明し、適切な例を提供しています。関数のパラメーター、戻り値、および例を説明しています。

思ったこと

とりあえずのコードが発生してその後人間サマの私の手が動きやすいので良いと思った。
出力されたコードに不満はあるが、まあとりあえず何か出てくるんで良いじゃないですか。

不満なコードが出てくるというのも、コード設計上の要求を言語化せずにとりあえずで書かせているんで、しょうがないですねという気もする。
後からコード設計の意図を足す行為がかなり苦痛に感じてしまうが、それも「とりあえず何か出す」とのトレードオフかもしれない。

「変更前後で起きてほしいことを明示化する」みたいな工夫について、Chat-GPT クンの記憶力が悪いせいで毎回確認してるような気分になった。
これが本当に Chat-GPT クンの記憶力による問題なのかはわからないが、Chat-GPT クンにめちゃくちゃデカい前提情報を簡単に渡せると気持ちよさそうだと思った。

脚注
  1. handlebars について「公式の型定義が存在する」のでTypeScript project の利用においてオススメであると言ってきたことや、自分が「marked の型定義だけ環境に追加して本体は追加しない」頓珍漢なセットアップを出力してきたことを完全に忘れている・何も理解していない Chat-GPT クンに対し、私がイラついている ↩︎

Discussion