「Image Uploader」作成記録 - React + Firebaseで作るWebアプリケーション開発


「Image Uploader」作成記録 - React + Firebaseで作るWebアプリケーション開発

devchallengesのImage Uploaderに取り組んだ記録です。

何を作ったか?

画像アップロードが行えるWebアプリケーションを作成しました。下記の機能が利用できます。

  • 任意の画像を指定してアップロードできる。
  • 画像のダウンロードURLを取得できる。

ただし、下記の制約があります。

  • 特に判定をしていないため、画像以外でもアップロードできてしまう。
  • ストレージのセキュリティ設定が不安だったため、書き込みを競合させている。そのため、他の画像をアップロードすると前の画像が削除される。(全ユーザーで1画像のみ)

リポジトリ

成果物は下記のリポジトリにコミットしています。使い方はREADME.mdを参照してください。

アプリケーション

下記の画像は、本アプリケーションのスクリーンショットです。

  • アップロード画面から、画像のアップロードが行えます。アップロードの方法は、下記の2通りがあります。
    • 青点線の中にドラッグ&ドロップすると、画像のアップロードが行えます。
    • 「Choose a file」ボタンを押すと、ファイル選択のダイアログが表示されます。ファイルを選択すると、アップロードが行えます。
  • 画像をアップロードすると、プログレスバーが表示されます。
  • 画像のアップロードが完了すると、完了画面が表示されます。
  • 「COPY LINK」ボタンを押すと、ダウンロード用のURLをクリップボードにコピーします。

自動テスト

seleniumに触ってみたかったため、各種自動テストのサンプルも作成しました。ただし、CI&CDの設定とかはやってないです。カバレッジも計測していません。

  • React部品テスト
    • react-testing-libraryを使った基本的な単体テスト。
  • E2Eテスト
    • seleniumを使ったE2Eテスト。

どうやって作ったか?

Webアプリケーションの中身や、作成時に調べたことをまとめています。

採用した技術/ライブラリ

  • React
    • SPAのフレームワークのように利用。
  • TypeScript
    • せっかくなので、型付けができるTypeScriptを選択。
  • Firebase
    • バックエンド。ホスティング・アップロードデータの保存に利用。
  • Material-UI
    • 画面のUI。
  • react-dropzone
    • ドラッグ&ドロップ用の画面要素を提供するライブラリ。
  • copy-to-clipboard
    • クリップボードへのコピー機能を提供するライブラリ。
  • React Testing Library
    • React部品の単体テストフレームワーク。
  • selenium-webdriver
    • E2Eテストのための、ブラウザ操作の自動化ツール。

前提知識

ReactとTypeScriptに関する知識は多少ありましたが、それ以外の知識(主にバックエンドとホスティング)は皆無でした。そのため、下記の講座を受講しました。

この講座では、React+FirebaseでWebアプリケーションをリリースするハンズオンを学べます。また、TypeScript、Redux、MaterialUIといった、各種技術の導入も学べます。ただし、簡単なハンズオンのみなので、Reactの知識がない方は別で学んでおくことをおすすめします。

(後者の無料講座の最後にクーポンがついていて、いつでもお安く買えます。)

アプリ

初期環境構築

前提知識で紹介した講座でも使っていた、下記のテンプレートで組みました。そのため、自動的にRedux-toolkitもついてきましたが、使ってはいません。

$ npx create-react-app <project-name> --template redux-typescript

これだけで、React + Redux + TypeScriptのテンプレートが自動的に構築されます。

画面遷移

今回は最上位のコンポーネントでstateを見て描画するコンポーネントを変更しています。(react-router-domを使ってもよかったのですが、それほど大きくなかったので...)

最上位のコンポーネント「App.tsx」には、下記の2つのstateがあります。

  • hasUploadedFile:ファイル選択前はfalse、ファイル選択後はtrue。
  • fileUrl: アップロードしたファイルのURL。初期値は空文字。

上記のstateに応じて、下記のように画面描画を切り分けています。

  • hasUploadedFileがfalseの場合、アップロード画面。
  • hasUploadedFileがtrueで、fileUrlが空文字の場合、プログレスバーの画面。
  • hasUploadedFileがtrueで、fileUrlが空文字でない場合、アップロード完了画面。

スタイリング

CSS等もほぼ初心者だったため、手探りでいろいろ設定しました。

ドラッグ&ドロップ

react-dropzoneを知っていたので、そのまま利用しました。GitHubのUsageに従ってHookを利用しました。

クリップボードへのコピー

copy-to-clipboardを利用しました。とても手軽にクリップボードを利用できる。

ボタン入りテキストボックス

Input Adornmentsという機能を利用しました。成果物は、下記のURLです。

バックエンド

FirebaseのStorageを利用しました。基本的な使い方は、前提知識で紹介した講座で学べます。

セキュリティルールはすべて許可とかやると怖かったので、下記のように設定しています。設定方法は、公式ドキュメントのFirebase セキュリティ ルール スタートガイドで調べました。

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /images/testFile.jpg {
      allow read, write: if true;
    }
  }
}

また、ホスティングもFirebaseで行っています。

React単体テスト

初期環境構築時のテンプレートにReact Testing Libraryが自動でくっついてきます。ほとんどこれを利用しているだけです。

waitFor

なぜか、初期構築のテンプレートからだとwaitForが利用できませんでした。。。いったん、自前で「@testing-library/dom」をnpmからインストールして、それ経由で利用しています。

E2Eテスト(selenium)

seleniumを使ってみたかったため、E2Eの自動テストを作成しました。seleniumの使い方自体は、公式ドキュメントがしっかりしているため特に困らずに実装が進められました。

絶対パスへの変換

Node.JSの標準機能でPathというモジュールがあり、そこから使えます。注意点として、渡す相対パスはプロジェクトのルートディレクトリからの相対パスみたいです。

import { resolve } from 'path';
const filePath = resolve('<プロジェクトルートからの相対パス>')

ファイルダウンロード

seleniumのWebDriverからダウンロードしたかったのですが、いまいち調べきれませんでした。今回は認証/認可が絡まないダウンロードのため、普通にfetch APIを使ってダウンロードを実装しました。

ファイルのハッシュ値を取得

アップロードしたファイルと、ダウンロードリンクからダウンロードしたファイルが等価であることを検証するために、ハッシュ値で比較しました。ハッシュ値の取得は下記を参考にしています。

Jestの設定

普通に「jest」コマンドをたたくと、React単体テストもテスト対象になってしまいます。そのため、設定ファイル「jestSeleniumTest.config.js」を使ってテストファイルの検索対象を絞っています。

設定ファイルを有効にする場合、jestコマンドの「--config」オプションを使って、下記のように設定します。参考:Jest CLI Options · Jest

package.json
{
  "scripts": {
    "jesttest": "jest --config jestSeleniumTest.config.js",
  },
}

設定ファイルでは、下記のようにして検索対象を絞っています。参考:Configuring Jest · Jest

module.exports = {
  "testMatch": ["<rootDir>/selenium_test/test/**/*.test.ts"]
};

謝辞