ChatGPTを使ってDockerコンテナでNext.jsのモックを動かす

以前、[ChatGPTでBootstrapを使ったHTMLのモックを作ってみる - Qiita](https://qiita.com/chovin/items/9842c7e1b7cc30c097fd)をやってみてChatGPTでモックが作れることを知りました。
しかし、HTMLだと実際の開発では「作り直しが大変」「見た目がかなり違う」などが起こりやすいのでNext.jsでモックを作ろうと思います。
とはいえ、モック用に環境を作るのも面倒くさいので環境構築もChatGPTにお手伝いしてもらいました。

今回は有料版の「ChatGPT 4」でやってみました。


# 1. ChatGPTにdocker-compose.ymlを作ってもらう
まずは、ChatGPTに環境の要件を伝えてdocker-compose.ymlと使い方を聞きます。

```
以下のdocker-compose.ymlを作って

- Next.jsを使用する
- Node.jsは最新バージョンを使う
- npmとNext.jsのインストールもやる
- ホストでのポートは9300
- コンテナを作ってからプロジェクトを手動で作成する
```

<details><summary>GhatGPTの出力結果</summary><div>

> コンテナを作成した後で手動でNext.jsプロジェクトを作成するためのdocker-compose.ymlファイルは以下のようになります。

```yaml:docker-compose.yml
version: '3.8'

services:
  nextjs:
    image: node:latest
    working_dir: /app
    volumes:
      - ./nextjs-app:/app
    ports:
      - "9300:3000"
    command: bash -c "sleep infinity"
```

> この設定では、Node.jsの最新バージョンを使用してコンテナを作成し、sleep infinityコマンドを使用してコンテナを起動したままにします。これにより、コンテナ内で手動でNext.jsプロジェクトを作成することができます。
> プロジェクトをセットアップするには、次のステップを実行します：
> 1. 上記のdocker-compose.ymlファイルを作成します。
> 2. nextjs-appディレクトリを作成します（このディレクトリは空で構いません）。
> 3. Dockerコンテナを起動するために、次のコマンドを実行します：`docker-compose up -d`
> 4. コンテナ内でNext.jsプロジェクトを作成するために、次のコマンドを実行します：`docker-compose exec nextjs npx create-next-app .`
> 5. 開発サーバーを起動するために、次のコマンドを実行します：`docker-compose exec nextjs npm run dev`
> 6. ブラウザで`http://localhost:9300`にアクセスして、Next.jsアプリケーションが動作していることを確認します。
</div></details>

# 2. DockerコンテナとNext.jsプロジェクトを作成する

1. ChatGPTの指示に従い、docker-compose.ymlとnextjs-appディレクトリを作成し、コンテナを作成
1. ChatGPTの指示に従い、Next.jsプロジェクトを作成して起動
    - 本当は、コンテナを作成時に一気にやりたかったのですが、プロジェクト作成は対話形式なので断念しました。

```bash
# コンテナを作成する
$ docker-compose up -d
[+] Running 9/9
# ...省略...
 ✔ Container downloads-nextjs-1  Started

# プロジェクトを作成する
$ docker-compose exec nextjs npx create-next-app .
Need to install the following packages:
create-next-app@14.1.4
Ok to proceed? (y) y
✔ Would you like to use TypeScript? … No / Yes
# ...省略...プロジェクト作成時の設定はお好みで
Success! Created app at /app
npm notice 
npm notice New patch version of npm available! 10.5.0 -> 10.5.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.5.1
npm notice Run npm install -g npm@10.5.1 to update!
npm notice

# 起動する
$ docker-compose exec nextjs npm run dev
```

ChatGPTの指示に従い、http://localhost:9300 にアクセスすると起動していることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1743295/dbe3010a-e67a-5078-5aef-88ebd38abb64.png)

# 3. ChatGPTにdocker-compose.ymlを再作成してもらう
最初のdocker-compose.ymlでは、`command: bash -c "sleep infinity"`でコンテナを起動したままにさせてプロジェクト作成をしました。
プロジェクト作成を完了したので、これからは「コンテナの再起動すればアプリも再起動」するように作り直します。

```
以下のdocker-compose.ymlを作って

- Next.jsを使用する
- Node.jsは最新バージョンを使う
- npmとNext.jsのインストールもやる
- ホストでのポートは9300
- コンテナを作成したら開発サーバを起動する
```

<details><summary>GhatGPTの出力結果</summary><div>

> 以下のdocker-compose.ymlファイルは、指定された要件に基づいて構成されています。


```yaml:docker-compose.yml
version: '3.8'

services:
  nextjs:
    image: node:latest
    working_dir: /app
    volumes:
      - ./nextjs-app:/app
    ports:
      - "9300:3000"
    command: bash -c "npm install && npm run dev"
```

> この設定では、Node.jsの最新バージョンを使用してコンテナを作成し、Next.jsプロジェクトの依存関係をインストールした後、開発サーバーを起動します。ホストマシンのポート9300がコンテナのポート3000にマッピングされ、Next.jsの開発サーバーにアクセスできるようになります。
> プロジェクトをセットアップするには、次のステップを実行します：
> 1. 上記のdocker-compose.ymlファイルを作成します。
> 2. nextjs-appディレクトリを作成し、その中にNext.jsプロジェクトのファイルを配置します（例えば、package.jsonなど）。
> 3. Dockerコンテナをビルドして実行するために、次のコマンドを実行します：`docker-compose up --build`
> 4. ブラウザで`http://localhost:9300`にアクセスして、Next.jsアプリケーションが動作していることを確認します。
</div></details>

# 4. Dockerコンテナを再作成する
ChatGPTが教えてくれた内容で先ほど作ったdocker-compose.ymlを上書き保存します。
そして、リビルドして起動します。

```bash
# リビルドして起動する
$ docker-compose up --build nextjs
[+] Building 0.0s (0/0)                                                                                                                                                                                    docker:default
# ...省略...
downloads-nextjs-1  |  ✓ Compiled in 211ms (233 modules)
```

http://localhost:9300 にアクセスすると起動していることが確認できました。


# 5. モックを表示する
## 5-1. ChatGPTにコードを作ってもらう
モックでまだインストールしていないなライブラリを使うことがあるので「使うライブラリはpackage.jsonに書く」を要件の1つとしてChatGPTに伝えます。

```

ファイルアップロード画面をNext.jsのTSXで出力して。

- 使うライブラリはpackage.jsonに書く
- UIライブラリにMantineを使用する
- Dropzoneコンポーネントは@mantine/dropzoneからインポートする
- tailwindcssを使う
- ドラックアンドドロップでアップロードできるようにする
- アップロード後に「登録」ボタンでアップロード履歴に履歴を表示する
- 「登録」ボタンは右端に表示する
- 「登録」はファイルがアップロードされていない場合は非活性にする
- アップロード履歴を下に一覧で表示する
- アップロード履歴には「ファイル名」「アップロード日時」「状態」を表示する
- 状態には「アップロード中」「成功」「失敗」がある
- アップロード履歴にはサンプルデータを表示して
- アップロード欄には選択したファイル名を表示する
- 「登録」ボタンを押下したらアップロード欄のファイル名はクリアする
- アップロード履歴はアップロード日時の降順で表示する
- 「アップロード日時」の表示フォーマットは「YYYY-mm-dd HH:MM:DD」
```

<details><summary>GhatGPTの出力結果</summary><div>

> 以下は、指定された要件に基づいたNext.jsのTSXでのファイルアップロード画面の実装例です。package.jsonに必要なライブラリを追加し、MantineとTailwind CSSを使用してUIを構築しています。
> package.jsonの依存関係に以下を追加してください：

```json
{
  "dependencies": {
    "@mantine/core": "^5.0.0",
    "@mantine/dropzone": "^5.0.0",
    "@mantine/hooks": "^5.0.0",
    "date-fns": "^2.28.0",
    "next": "^12.0.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "tailwindcss": "^3.0.0"
  }
}
```
> 次に、pages/index.tsxファイルに次のコードを追加します。
```tsx
// pages/index.tsx
import { useState } from 'react';
import { format } from 'date-fns';
import { Button, Text, Table, Paper } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { useListState } from '@mantine/hooks';
import 'tailwindcss/tailwind.css';

interface UploadHistoryItem {
  fileName: string;
  uploadDate: string;
  status: 'アップロード中' | '成功' | '失敗';
}

const initialHistory: UploadHistoryItem[] = [
  { fileName: 'sample1.txt', uploadDate: '2024-04-10 12:00:00', status: '成功' },
  { fileName: 'sample2.txt', uploadDate: '2024-04-09 15:30:00', status: '失敗' },
];

export default function FileUploadPage() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [uploadHistory, uploadHistoryHandlers] = useListState<UploadHistoryItem>(initialHistory);

  const handleDrop = (files: File[]) => {
    if (files.length > 0) {
      setSelectedFile(files[0]);
    }
  };

  const handleRegister = () => {
    if (selectedFile) {
      const newHistoryItem: UploadHistoryItem = {
        fileName: selectedFile.name,
        uploadDate: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
        status: 'アップロード中',
      };
      uploadHistoryHandlers.prepend(newHistoryItem);
      setSelectedFile(null);
    }
  };

  return (
    <div className="container mx-auto p-4">
      <Text className="text-4xl mb-6">ファイルアップロード</Text>

      <Paper className="p-4 mb-4 border-2 border-dashed cursor-pointer" shadow="sm">
        <Dropzone onDrop={handleDrop} className="flex justify-center items-center h-32">
          <Text align="center">
            {selectedFile ? selectedFile.name : 'ドラッグアンドドロップまたはクリックしてファイルをアップロード'}
          </Text>
        </Dropzone>
      </Paper>

      <div className="float-right mb-6">
        <Button
          onClick={handleRegister}
          disabled={!selectedFile}
        >
          登録
        </Button>
      </div>

      <div className="clear-both">
        <Text className="text-2xl mb-4">アップロード履歴</Text>
        <Table striped className="w-full">
          <thead>
            <tr>
              <th>ファイル名</th>
              <th>アップロード日時</th>
              <th>状態</th>
            </tr>
          </thead>
          <tbody>
            {uploadHistory.map((item, index) => (
              <tr key={index}>
                <td>{item.fileName}</td>
                <td>{item.uploadDate}</td>
                <td>{item.status}</td>
              </tr>
            ))}
          </tbody>
        </Table>
      </div>
    </div>
  );
}
```
> このコードは、指定された要件に従ってファイルアップロード画面を作成します。
> Dropzoneコンポーネントを使用してファイルをドラッグアンドドロップでアップロードできるようにし、Tailwind CSSでスタイリングしています。また、ファイル選択後に「登録」ボタンをクリックすると、選択されたファイルの情報がアップロード履歴の一番上に追加され、アップロード履歴は最新のものが上に表示されるようになっています。date-fnsライブラリを使用して日付をフォーマットし、MantineのuseListStateフックを使用してアップロード履歴の状態を管理しています。
> 上記のコードには、サンプルデータを含む初期アップロード履歴が設定されており、ページが読み込まれるとすぐに表示されます。また、アップロード履歴はアップロード日時の降順で表示されます。
> このコードを使用することで、指定された要件に基づいたファイルアップロード画面をNext.jsのTSXで出力することができます。
</div></details>


## 5-2. Dockerコンテナをリビルドして起動する
1. コンテナが実行中であれば停止する
1. {プロジェクトディレクトリパス}/pages/index.tsx をChatGPTが出力した内容で上書き保存する
1. {プロジェクトディレクトリパス}/package.json にChatGPTが出力した内容で不足しているライブラリを追記する
1. コンテナをリビルドして起動する
1. http://localhost:9300 にアクセスするして画面を確認する

```bash
# 実行中の場合は「Ctrl+C」で停止する
Gracefully stopping... (press Ctrl+C again to force)
Aborting on container exit...
[+] Stopping 1/1
 ✔ Container downloads-nextjs-1  Stopped                                                                                                                                                                             0.9s 
canceled

# index.tsxとpackage.jsonを更新する

# コンテナをリビルドして起動する
$ docker-compose up --build nextjs
[+] Building 0.0s (0/0)
# ...省略...
```

かっこよくないモック画面ができました。
![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1743295/9edae218-c3d9-2236-1e30-745240da1843.gif)

# さいごに
Next.jsやReactのコードをChatGPTに作ってもらうのは、HTMLよりも難しいとわかりました。
「どんなライブラリを使用するかの指定で見た目が変わる」ことや「Layoutなしで1ファイルだけで見た目を作る」ことに苦戦しました。
ある程度のコードを作って、後は手動でコードを修正したほうが早いと思いました。

もしかしたら、HTMLを作ってChatGPTでNext.jsやReactのコードに変換するほうが効率的なのかもしれません。