Web アプリ「#FF11っぽい告知画像を作ろう」を作った

FF11 公式サイトの告知っぽい画像を作れる Web アプリ「#FF11っぽい告知画像を作ろう」をリリースしました。

PC でもスマホでも使えます。

どんなアプリ?

FF11 公式サイトには様々な告知が載っていますが、その多くには、いわゆるアイキャッチ画像が付けられています。

f:id:sardine:20181027183758p:plain

このアイキャッチ画像には定型があって、概ね、以下の 3 パターンのいずれかです。

  • 451×100 の枠あり、分割なし
  • 451×100 の枠あり、2 分割
  • 451×100 の枠あり、3 分割

逆に言うと、この定型に沿って画像を組み合わせるだけで、FF11 公式サイトっぽくなります。

それを簡単にできたら、FF11 公式ごっこが捗るなあと思っていたので、それを実際に Web アプリとして作ってみたのが、今回の「#FF11っぽい告知画像を作ろう」です。

f:id:sardine:20181027184454j:plainf:id:sardine:20181027184452j:plain

手持ちの画像を選んで、位置やサイズを調整するだけで、こんな感じのそれっぽい画像が作れます。

f:id:sardine:20181027183756p:plain

f:id:sardine:20181027183753p:plain

作った画像はダウンロードすることも、Twitter や LINE で共有することもできます。

実は 2009 年にも一度トライしていたのですが、当時は途中で力尽きてしまいました。今回は当時の記憶を元にしながら、ゼロから作り直したのですが、無事にリリースまでこぎつけることができて、嬉しい限りです。

どうぞご利用ください。

開発に使ったソフトウェア

ここからは、プログラムの話です。

恥ずかしながらこれまでもっぱら「Java おじさん」だったので、今まで使ったことがないものを色々試しました。

クライアント側

リアルタイムに画像を描画したかったので、メインのページは SPA (シングルページアプリケーション) 的な構成にしました。

使ったもの 簡単な説明
TypeScript 言語 (AltJS)
React UI フレームワーク
Semantic UI CSS フレームワーク
Semantic UI React React と Semantic UI の統合
npm パッケージマネージャ
webpack バンドラ
webpack-dev-server 開発用 Web サーバ
Cacoo 作図の Web サービス
Visual Studio Code 開発者用テキストエディタ
Git バージョン管理
GitLab (gitlab.com) Git リポジトリホスティング

このうち、今までに使ったことがあるのは、Git と Cacoo だけです。

Cacoo は何度か使っていて、このブログ用の図を作るのに使ったこともありますが、今回はモックアップの作成に利用しました。

TypeScript、React、npm、webpack あたりについては、使っていなくてもネット上で色々目にするので、雰囲気はわかっていたものの、具体的な使い方は調べながら進めました。

CSS フレームワークは、ボタンやタブなど、使いたいコンポーネントが含まれているものを探した結果、見た目もよさげな Semantic UI を使うことにしました。ただし、最終的にはタブを使いませんでした (理由は後述)。

エディタは、Atom は以前に使ったことがあったので、同系統で人気の高い Visual Studio Code を選びました。開発元が TypeScript と同じ Microsoft ということで、手厚くサポートされていそうと思ったことも選定理由の一つです。

Git のホスティングについては、GitHub は使ったことがあるのですが、プライベートリポジトリを無料で作れるということで今回は GitLab を使うことにしました。

プライベートリポジトリにこだわったのは、初心者丸出しのコードが人目に触れるのが恥ずかしいというのもありますが、PaaS を使っている人の失敗談として何度か「秘密鍵リポジトリに上げてしまい、高額請求が来た」というのを見かけていたためです。

GitLab には CI/CD の機能もありますが、そちらはまだ使っていません。Jenkins を使ったことがあるので CI/CD の便利さはよくわかっているのですが、初めて JavaScript (TypeScript) ベースの開発環境を構築するにあたり、CI/CD を考慮する余裕がなかったので、今回は考えないことにしました。

サーバ側

完成した画像をツイートするボタンや LINE で送るボタンを設置するため、クラウドに画像を保存する必要がありました。そこで、node.js でサーバ側のロジックを作ることにしました。

フロントエンドが SPA なら、バックエンドは JSON でやり取りする API だけを出力するのが本来のかたちですが、今回はサーバ側でも HTML を生成しています。

さらに言えば、React で SSR (サーバサイドレンダリング) する方法ではなく、昔ながらのテンプレートエンジンを使っています。言語も TypeScript ではなく JavaScript (EcmaScript) です。これには技術的な理由はなくて、クライアント側でもう使ったのとは違うものを試したかったのと、サーバ側の環境構築で悩んだ (後述) ために疲れてしまったので、SSR のための環境構築がめんどくさくなってしまったためです。

使ったもの 簡単な説明
node.js JS アプリケーション実行環境
JavaScript (ES5〜) 言語
Express サーバ側フレームワーク
Pug テンプレートエンジン
npm パッケージマネージャ
webpack バンドラ
nodemon 開発用 Web サーバ
Google App Engine PaaS
Google Cloud Strage ストレージサービス
Visual Studio Code 開発者用テキストエディタ
Git バージョン管理
GitLab (gitlab.com) Git リポジトリホスティング

このうち、今まで使ったことがあるのは、Git と、JavaScript (ただし ES3 世代まで) です。

Google App Engine (GAE) は、Java の Standard 環境のみ使ったことがありました。今回は node.js の Standard 環境を使っています。

画像の保存は、Google Cloud Storage で行うことにしました。Google Cloud Storage に保存したファイルには公開 URL が生成されるので、ファイルをダウンロードするだけの処理を作り込まなくていい、というのが一番大きな理由です。ただしこれは最終的にはうまくいきませんでした (後述)。他の理由として、NoSQL DB の Google Cloud Datastore は使ったことがあったので、別のものを試したかったのと、Google Cloud Storage の方が運用コストが安く済みそうという点もありました。

node.js の最新はバージョン 10 系ですが、GAE の node.js Standard 環境では、LTS のバージョン 8 系しか選択できなかったので、開発中も 8 系を使いました。

Express は、node.js 用の Web アプリフレームワークの中で最もよく使われているもののひとつらしい、ということで選びました。

テンプレートエンジンは、Express を使う前提で、苦労せずに素直に使えそうなものの中から、Pug と Mustache のどちらかがよさそうかなと絞り込みました。個人的には、HTML のテンプレートエンジンとしては、静的な HTML をそのまま HTML として書けるものの方が好みです。別にデザインしたものを取り込みやすいからです。それで、Pug のように独自の構文から実行時に HTML を生成するテンプレートエンジンは使ったことがなかったので、今回はあえて Pug を試してみることにしました。先に React で JSX に触っていたので、実行時に HTML を生成するタイプも意外と便利と感じていたことも、影響しているかもしれません。

所感

うまくいったこと

初めての割には、大きく詰まることなく開発できた

古い世代 (ES3 まで) であっても JavaScript に慣れていれば、最新の JavaScript (ES5 以降) や TypeScript にも入りやすいですね。

JavaScript は動的型付け言語であり、値には型があるけれど、変数には型がありません。だから楽に自由に書けるというメリットと、間違えた時に気づきづらいというデメリットがあります。

そこで、静的型付け言語である TypeScript が候補に上がってくるわけですが、今回のように React 使うの初めてという場合には、TypeScript と Visual Studio Code の連携よって、最初から正しいコードを書きやすい環境は、かなり便利でした。

ググればノウハウがたくさん見つかる

基本的なところから細かい部分まで、node.js や React や Express についてのノウハウはネット上に蓄積されていて、検索すればたいていの疑問は解決しました。

このあたりは、後発組の楽なところですね。

ツールやライブラリが潤沢

npm 経由で利用できるツールやライブラリが多数あります。中には関数を 1 つ提供するだけの小さなライブラリもあります。

ここは、ライブラリといえばある程度の規模と機能と複雑さを持つのが当たり前になっている Java とは違うところです。

既存のオブジェクトやプロトタイプも動的に変更できる JavaScript の長所と、webpack によりリリース時のパフォーマンス低下を防げることがあいまって、気軽に必要最小限のツールやライブラリを提供できるのだと思いました。

苦労したこと

開発環境の構築に時間がかかった

JavaScript 開発には、使うツールやライブラリからディレクトリ構成に到るまで、デファクトスタンダードになっている慣習がないようです。クライアント側もサーバ側もそうです。

例えば、React のチュートリアルを見て Hello World を作ると、ある程度以上の規模の開発には向かない、いわば「チュートリアル専用」のディレクトリ構成になっていました。

ではどうするのがいいかと思ってネットで検索すると、いくつか情報が見つかるものの、人によって言っていることが違います。

今回は、最初にクライアント側を作り始めて、途中でサーバ側も作ろうと思い立ったのですが、昔、node.js について「クライアント側とサーバの側の両方を JavaScript (あるいは TypeScript などの AltJS) で作れば、ロジックを共有するのも簡単だ」という意見を見た記憶がありました。

だとしたら、クライアントとサーバを一緒に開発するためのノウハウなんて、ググればすぐに出てくるだろうと思ったのですが、いくつかの記事やリポジトリは見つかったものの、内容は見事にバラバラで、初心者たる自分にはどれがいいのか判断できませんでした。

結局今回は、クライアントとサーバは別々に開発することにして、クライアント側でビルドしてできたファイルを、シェルスクリプトでサーバ側のディレクトリにコピーするという、レトロな方法にしました。

個人的には、ある程度のデファクトスタンダードがあって初心者はそこから入り、その発展としてカスタマイズもできるというのが、バランスいいのではないかと思います。

ノウハウの情報が古い、間違っていることが結構あった

ネット上の情報が古かったり間違っていたりというのは、どのジャンルでもあることです。ただ、JavaScript ベースの開発についての情報は、どんどん状況が変わっていくこともあり、陳腐化した記事や誤ったサンプルコードが全体に占める割合は、かなり大きいのではないかと感じます。

クライアントとサーバを 1 つのフォルダ (プロジェクト) にまとめる方法を調べていた時、あるテンプレート (ボイラープレート) のリポジトリを見つけましたが、それで生成した設定ファイルを見ていたら、脆弱性のある古いライブラリが使われていました。しかも、GitHub の Issue ですでに指摘されているのに、半年以上経っても対応されていないようだったので、使うのを断念しました。

あと、先日 Android アプリを作っていた時にも思っていたことですが、Google の開発者向けドキュメントは日本語訳がどんどん陳腐化している上に、あまりメンテナンスされていないようです。例えば下記のバグ報告を見ると、3 月に日本語版の誤植を伝えているようなのですが、いま見ても修正されていません。

Semantic UI React のタブがうまく使えない

最初、JSX をふつうに書いて、タブの子要素として別のコンポーネントを書いたところ、「タブを切り替えるたびに、タブ内の情報がリセットされる」という結果になりました。

Semantic UI React のタブは、切り替えるたびに元のタブの内容をアンマウント (破棄) して、切り替え先の内容をマウント (生成) しているためです。

この動作をオフにして、すべてのタブのを保持するための属性も用意されているのですが、それを使うとタブの中身が描画されなくなるという不具合がある上に、タブについては近いうちに API を設計し直す予定のため、当面は修正しないという方針のようです。

公式サイトのサンプルコードを見ると、あらかじめ各タブの中身を生成して配列に入れておき、タブにその配列を渡していました。この方法だと、アンマウントされてもステートを保持したまま配列に残り、次にマウントされる時も同じインスタンスが使われるから、情報がリセットされずに済むようです。

ただ、今回の場合、この方法だと実装上の不都合が出て、自分の TypeScript/React スキルでは解決できなかったので、タブを使うのを諦めて、アコーディオンに替えてしまいました。

Semantic UI React だけでなく Material-UI のタブもアンマウント・マウントを行う、という情報を見かけたので、正当な対処方法が何かあるのだと思いますが、見つけられませんでした。

Google Cloud Storage に保存した画像が Safari に読み込まれない

Google Cloud Storage に保存したファイルには、公開用の URL を生成できます。

保存したファイルをダウンロードできるようにするためには、その URL を返せばいいだけです。DB に保存した BLOB のようにダウンロードのためだけのサーバ側ロジックを作り込む必要はない、ということでした。

今回のアプリでは、画像を保存してあとで閲覧できるようにする必要があり、Google Cloud Storage はちょうどいいと思いました。

実際、Chrome (macOS 版と Android 版) で試したところ、問題なく表示できたのですが、Safari (macOS 版と iOS 版) では、なぜか画像が表示されませんでした。

どういうわけか、アドレスバーに URL を直打ちすると表示できるのですが、img 要素の src 属性に指定すると表示されません。Content-Type ヘッダが怪しい気がするのですが、解決策が見つかりませんでした。結局、画像ダウンロードのためのサーバ側ロジックを作り込みました。

iOSSafari で画像をダウンロードできない

今回のアプリには、作った画像をダウンロードするボタンがあります。

当然、ボタンを押したらダウンロードが始まるようにしたいのですが、iOS 版の Safari のみ対応できませんでした。

以下のようなことを試してみましたが、結局解決していません。

  • このような場合の一般的なやり方に従って、Content-Disposition ヘッダを設定してみた
  • iOS では画像しかダウンロードできないという情報を見て、Content-Type ヘッダも設定してみた
  • ダウンロードの開始をボタンではなく a 要素にして、download 属性を付けてみた

プログラムが間違っているのか、iOS 版の Safari の仕様なのか、わかりませんでした。

やむなく、iOS からのアクセス時に限り、「別窓が開くから、長押しして保存してください」という注意書きをつけるかたちにしました。

終わりに

作り始めたのが 10/22 で、リリースが 10/27 なので、開発にかかった時間は 6 日くらいでした。

環境作りや調べ物に時間がかかっていて、慣れたら簡単なアプリをさくっと作れそうという感触は得られました。

まだちょっと改善したい点も残っていますが、おいおいやっていこうと思います。