【Claude】バイブコーディングで作ったHTML一本のアプリをVite+モジュール化してリリースするまで

AI

先日、グラデーションサムネメーカーというシンプルな画像生成アプリをリリースしました。
とってもおめでたい。

“シンプルな”と言っておりますが、こちら、お世辞抜きで非常にシンプルです。
ブラウザを開いて、色を選んで、テキストを打って、ダウンロード。それだけ。

ここ最近はAIの話題で持ち切りということもあり、我が家のClaudeAIをお供にバイブコーディングに挑戦しました。
開発期間は1か月ぐらいだったかなぁ。
本業の合間に週2くらいで作業してので、工数的にはもっと短いかも。
「え、バイブコーディングなのに遅くね??」と思ったでしょ?

ええ、その通りです。超時間かかってます。

でも! でもね!!!
爆速バイブコーディングをしたにも関わらず、敢えて、わざわざ時間をかけて、自分のためにやったことが多々あるんです!

そんな「敢えて」が詰まった開発工程を記事にまとめていきます。
初学者のみんな! ぜひ見て!!

きっかけ

WEBアプリを開発するうえで、最初に「何を作ろうか?」と考えるわけですが、やっぱり自分が今すぐ使いたい!と思えるものを作りたかったんですよね。
ちょうどこの技術ブログを開設したばっかで、記事のサムネイルを手軽に作れたらな~と思っていたところでした。

そして、”使いたい”と思えるものであると同時に、バイブコーディングをするなら自分が”学びたい”と思えるものにしようとも意識していました。

今回のバイブコーディングがやたら遠回りになっているのは、この”学び”のために費やした時間が長かったというわけなんです。

バイブコーディング開始

まずは自然言語で指示しました。これがバイブコーディングの醍醐味ですもんね。
AIへの指示はマークダウン形式がいいとかなんとか、まったく気にしてないです。

筆者
筆者

背景がグラデーションで、角丸の長方形が上書きされているようなシンプルな画像作成アプリがほしいよ~

みたいな雑~~い指示だったと思います。

Claudeすごいんですよ、こんなよわよわ指示でもアプリが完成形で生成されるんです。
しかもローカルで使いやすいHTML一本の形式!
npm run dev なし! docker compose なし! 楽!!

実際、吐き出してくれたHTMLはちゃーんと使えるし、そこから「色選びたい」「画像サイズ選びたい」などのわがままもちゃんと反映してくれました。

いやもうこれでよくない? まじで一家に一台Claudeだよ。

と、大いに感動していたわけですが。
HTMLの中身ーーー処理はすべてJavaScriptで書かれていたんです。

JavaScript。

筆者がかなり、とても苦手意識のある言語(なおJSを扱って3年目である)。

そりゃ「あれしてこれして」って指示するほうが明らかにタイパはいい。

でもなぁ。
バイブコーディング、あまりにも全部できちゃいすぎる。
ソースの中身を知らないまま、勝手に動くシステム…。
うーーーん、エンジニアとしてなんだか見過ごせないというか…。

てなわけで私、脳筋エンジニア、Claudeを「先生」として敢えて色々やることとしたのです。

バイブじゃないコーディングへ

敢えてその1 HTMLで動くのに、ピュアJS+Viteで動かす

JavaScriptの開発と言えばViteと(勝手に)思っていたので、まずはHTMLに乱立するロジックをJSァイルに逃がし、npmで動かそうと決めました。

あと個人的にLaravelの create-project みたいな、初期のディレクトリ構成を整えてくれる機能に頼りたかったのもあります。

npm create vite@latest app -- --template vanilla
部分意味
npm create vite@latest最新版のViteでプロジェクトを作る
appプロジェクト名(フォルダ名)
--npmに対するオプションとViteへのオプションを区切る文字
--template vanilla素のJavaScriptテンプレートを使う、つまりフレームワークはなし

ちなみにこの --(ダブルダッシュ)、npm create vite 自体へのオプションと、その先のViteへ渡すオプションを区別するための区切り文字なんだそうです。
これがないと --template vanilla がnpmのオプションと誤解されちゃうらしい。

生成されるディレクトリ構成はこんな感じです。

app/
├── index.html
├── package.json
├── public/
└── src/
    ├── main.js
    └── style.css

シンプルですっきりしてます。非常に好み。

続いて依存パッケージをインストールし、開発サーバーを起動します。

cd app
npm install
npm run dev

ちなみにこの npm run dev を実行するとターミナルがサーバーの待機状態になって、コマンドが入力できなくなります(初学者殺しやめてね)。

初めて見たときは焦ったんですが、これは正常な状態です。
ブラウザで http://localhost:5173 を開くだけでOK。
別のコマンドを打ちたければ、ターミナルをもう1枚開けば解決です。

今回も、難なくViteのデフォルト画面が表示され一安心しました。

敢えてその2 HTML一本ではなく、JSファイルにモジュール化する

エンジニアの仕事の中でも、めっっっっちゃくちゃに好きな作業がリファクタリング、コンポーネント化、そしてモジュール化の筆者。

HTML一本で動くアプリ、しかも自分しか使わないものを、敢えてモジュール化してみました。

全然違うけど、ごちゃごちゃした書類を分類ごとにファイリングする…みたいな、地道な作業が大好きなんですよね。全然違うけど。

JavaScriptはそもそも読むことすら苦手なので、HTML内に書かれていた処理が「自分で理解できるか?」を試すために自力で機能ごとに切り分けてみました。

前述のコマンドで作成されたディレクトリ階層、src/配下にフォルダを作って………。

…。

…。

で、結局な~~~~んにも理解できてなかったんですヮ!!!!!!!!!!(爆笑)

大前提としてJavaScriptのモジュール化をしたことがなく、機能を分けすぎてしまったり、逆に全く異なる意図の処理が同じファイル内にあったり…。

  • HTMLから機能を切り分ける
  • これでどう?とClaudeに聞く
  • 「それキモいよ」と指摘される

の繰り返しでそれはそれは時間がかかりました。
試行錯誤の連続でしたが、これが結果的に一番の学びになったと思います。

最終的に行き着いたディレクトリ構成はこちら。

src/
├── main.js          ← 司令塔。全イベント登録・初期化をここで行う
├── state.js         ← 全設定値の初期値をここに集約
├── options.js       ← フォント・グラデ・パターンの選択肢データ
├── canvas/          ← 描画ロジック(Reactに依存しない純粋なJS)
│   ├── render.js
│   ├── drawGradient.js
│   ├── drawRect.js
│   ├── drawText.js
│   ├── makePatternTile.js
│   └── utils.js
├── ui/              ← UIパーツの動作
│   ├── colorSync.js
│   ├── gradTypeGrid.js
│   ├── patternGrid.js
│   ├── fontGrid.js
│   ├── download.js
│   └── themeToggle.js
└── styles/
    └── main.scss

設計のポイントは「何を変えたいか」でファイルを分けたことです。

フォントの種類を増やしたいなら options.js だけ触ればいい、グラデーションの描画を変えたいなら canvas/drawGradient.js だけ触ればいい、といった構成です。

敢えてその3 モジュール化したJSファイルを「何している処理か」対話形式で理解する

もう本当にお恥ずかしいのですが、JavaScriptのソースとにらめっこし、各ブロックごと(最小では1行ごと)に理解力チェックしました。

import文でどっから読み込んで、どんな風に操作させているのか…。
document.getElementById で呼んでいるのはHTMLのどこか…みたいな、基礎的なことを徹底的にClaude先生とやりとりするんです。
さすがに脳筋すぎる。

特に、関数の命名のクセや、処理のブロックの順序など、「なんでClaudeがそうしたのか?」を常に考えるように心がけました。

こうして対話を繰り返していくと、「アッこれ進研〇ミで見たやつだ」という具合に、よくあるJavaScriptの命令がちょっとずつ身についてくるんですよね。

こちらの記事に、特に頻出したコードをメモがてら書いておきます。

【JavaScript】初学者がアプリ開発で本当に使ったメソッドはこれだった
バニラJavaScriptでサムネイル作成アプリを開発した筆者が、実際のコードをもとに頻出メソッド10選を解説。getElementById・addEventListener・Canvas操作・localStorageなど、初学者がつまずきやすいポイントも交えて実践的にまとめています。

まとめてて思いますが、苦手意識がちょっとマシになった気がしますね。

敢えてその4 機能を追加したいときは変更場所”のみ”聞く

「○○って機能を実装したいから、どのファイルが変更対象か教えて」と相談するようにしました。

Claudeが先に全部書いちゃう前に、少しでも自分でロジックを組み立てるためです。
こうすると、間違ったコードを書いたとしても答え合わせができるというか、「なぜこの部分を変更する必要があるのか」が自然と頭に残りました。

結局自分のコードは穴だらけで、Claudeが提案してくれたコードを写経することが多く、自分でごりごり書く回数が少なかったよなぁと感じます。
もっと書いてたらスキルも上がっただろうに…。

敢えてその5 main.cssをSCSS化する

ある程度機能が固まったタイミングで、CSSを見直しました。
Claudeはmain.cssに一本化していたので、修正したい場所を見つけづらいかなぁと思ったんです。

最近業務でSCSSに触れる機会があり、その書きやすさに魅了されまして。
せっかくなのでSCSSでコンポーネント化していきました。

具体的にやったことはこちらです。

  • メディアクエリや似たようなレイアウトが複数箇所に分散していたため集約
  • セクションごとにコメントで区切り、読みやすくする
  • 未使用スタイルが散見されたので削除
  • CSSの見直しが終わったら、SCSSに移行して変数・ネストを活用

これ自体はバイブコーディングあんまり関係ないんですけどね。
やっぱり運用しやすい形にできると気持ちがいいです。

リリース前にやったこと

① コードレビュー

キリの良いタイミングで「ちょっとここらでデバッグして~」と、Claudeにコードレビュー(静的なデバッグ)をお願いしました。

  • コードを読んでバグの可能性がある箇所を指摘する
  • エラーが起きやすいパターンを修正する
  • npm run build のエラーを対処する

最新ソースを解析してもらい、「本番環境へのアップロードにおいてセキュリティ上問題となる部分」「ユーザーが利用する際に想定されるバグ」の2軸でチェックしてもらいました。

② class・idの命名規則統一

種別規則
classケバブケースhow-to-step
idキャメルケースgradTypeGrid

classなのにアンダースコアが使われていたり、idなのにハイフンが使われていたり、そういうの気になっちゃうのでコードレビューとあわせて命名規則を確認してもらいました。

ちなみに、classとidで命名規則が異なりますがこれは”敢えて”。

idはJSから getElementById で参照するためキャメルケースに、classはCSSで使うためケバブケースに統一しています。

③ ビルドサイズの確認

アプリがさくさく動くことにこだわりたかったので、ビルド後のファイルサイズを気にしてました。
機能追加・アニメーション追加のたびに「重くなってない!?」と詰めてましたね笑。

そういえばリファクタリングでファイル数は減ったのに、JSサイズが増えていたことがありました。
ビルドのたびに微妙に変わるんですって! 知ってました??
もちろん私は初見でした(白目)。

例えばこれくらいの差分。

15.23 kB → 15.38 kB(差分:+0.15 kB = 約150バイト)

150バイトはテキスト約150文字分。これ、ビルドの揺らぎの範囲内らしいです。
そもそも、ファイルを統合してもコードの総量は変わらないので、ビルド後のサイズには影響しないんだそう。Viteがビルド時にすべてのファイルを1つにまとめるためです。

補足:E2Eテストは自分もやる

結局のところ、自分の手でもテストはしたほうがよかったな~と思います。
Claudeが発見できなかったバグが見つかるんです。

例えば…。

バグ①:フォントが2回クリックしないと反映されない

フォントを変更するためボタンをクリックすると、1度目のクリックではcanvasに反映されず、同じボタンを2度クリックすることで反映される、というバグです。

原因はGoogle Fontsのフォント読み込みタイミングの問題でした。
日本語フォントはGoogle Fontsから文字の種類ごとに分割されたファイルとして配信されます。

RocknRoll One(フォント名)
  ├── latin.woff2      → A B C など欧文
  ├── japanese-1.woff2 → ひらがな・カタカナ
  ├── japanese-2.woff2 → 漢字(第1水準)
  └── ...

1回目のクリック時点ではlatinファイルしか読み込まれておらず、2回目でようやく日本語ファイルが揃う、という動きだったわけです。

修正は document.fonts.load() に実際のテキスト内容を渡すことで解決しました。

// 変更前:latinしか読み込まれない
await document.fonts.load(`400 32px '${f.id}'`)

// 変更後:テキスト内容に必要なファイルをすべて読み込む
await document.fonts.load(`400 32px '${f.id}'`, state.textContent)

バグ②:カナ・英・漢字が混在するとフォントが部分的にしか反映されない

「サムネABC年月日」のようなテキストを入力してフォントを切り替えると、1回目のクリックで「サムネABC」だけ反映され、「年月日」は2回目でようやく反映される、というバグです。
これもバグ①と同じ原因で、document.fonts.load() に渡す引数が足りていませんでした。バグ①の修正で合わせて解消できております。

結局のところ、私みたいな小心者は自分の目で確かめないと気が済まないんですよね~…。
AIが作ったコードでも、自分でテストして初めて「動いた!」って安心します。

リリースを終えて

HTML一本で動いていたものを、モジュール化・リファクタリングしてリリースまで持っていけたのはかなり達成感がありました。
実際、自分のVPS環境にアプリケーションを公開するのって初めてだったし。
Googleの検索に載ってるとなんかわくわくしますよね。

あれだけ苦手意識のあったJavaScriptも、Claude先生のお陰でちょっと好きになれました。

気になった方はぜひこちらのアプリ、使ってみてください。

ではまた次回!

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