爆速でウェブアプリを公開する方法(無料でSSL独自ドメイン)/ React + Typescript + Cloud Function + Firebase Hosting

Shunsuke Sawada

知ってる人には当たり前かもですが、今回新しいプロジェクトを作成するにあたり、手っ取り早くウェブアプリを公開する方法を模索しました。
素早く公開する方法を調べていて、結局時間がかかっているというのは本末転倒ですね。


フロントエンド

React

何でも良いのですが、慣れてるもので。
ビルドの設定がいつも面倒くさいのですが create-react-app 使いました。Webpackとか開発用のサーバーとかテストとか、設定がほんとに一瞬で終わります。
https://github.com/facebook/create-react-app

Typescript

デフォルトは flow なのですが、今回は typescript で。
flowは遅いなーといつも思ってます。
https://github.com/wmonk/create-react-app-typescript
  
上の2つを組み合わせる手順はこちらが分かりやすいです。
https://github.com/Microsoft/TypeScript-React-Starter#typescript-react-starter
  
create-react-app とか使うとロックインされて逆に将来面倒くさいのでは?と思ってましたが、小さいプロジェクトで存続するかも分からないので、別に気にすることないなと感じました。プロジェクトが大きくなってカスタマイズが必要になっても npm run eject コマンドを叩くだけ。
ただ、ビルドプロセスのメンテを自分でやらないといけなくなるので、気軽に叩くのは止めたほうが良いと思います。

メモ

$ create-react-app --version
1.5.2

このバージョンでやりましたが、いきなりエラーが出たので解消方法。
https://github.com/wmonk/create-react-app-typescript/issues/314
https://github.com/wmonk/create-react-app-typescript/issues/282

package.json
1
2
3
"@types/node": "^9.6.7", // was 10.0.0(追記:直ってる模様)
...
"test": "react-scripts-ts test --env=jsdom --watchAll", // --watchAll を追加

tsconfig.json 下記を追加しました

/tsconfig.json
1
2
3
...
  "experimentalDecorators": true,
  "allowSyntheticDefaultImports": true

ホスティング

今まで Heroku 使ってましたが firebase hosting にしました。
無料で独自ドメインを設定することができます。

https://firebase.google.com/docs/hosting/quickstart

基本は firebase init でOK。

もちろんドメインは別途買う必要がありますが、無料でSSLに対応してくれるのが嬉しい。
あ、あとDNSで少しだけお金かかるかもですね。無視できるくらいだと思いますが。

create-react-app の本番ビルドが /build ディレクトリに設定されているので、
設定を少し変更する必要があります。

firebase.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "hosting": {
    "public": "build", // here
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [{
      "source": "**",
      "destination": "/index.html"
    }]
  }
}

firebaseの管理画面から新規プロジェクトを作成して、
プロジェクト名をファイルに書きます。このファイルの生成はなぜか自動でやってくれないみたい。

.firebaserc
1
2
3
4
5
{
  "projects": {
    "default": "your-project-name"
  }
}

サーバーサイド

CloudFunction
https://firebase.google.com/docs/functions/get-started

ホスティングに firebase なので CloudFunction が自然な流れかと。
DBも Firestore でOKです。

1
2
3
$ yarn build
=> フロントをビルド
$ firebase serve

ローカルで実行するときがちょっと面倒なので、ここは改善したいところ。
でも firebase.json の内容を読んで rewrites や redirects の設定を devサーバーに反映させるのは eject しないとちょっと無理そう。
サーバーサイドの開発中に頻繁にフロントを触ることは無いと思うので、まぁここは分離して考えるということで妥協しました。

開発を始めるにあたって、create-react-app の Typescript と CloudFunction の Typescript がうまく共存できないようなことがありました。CloudFunction で yarn build すると、なぜかプロジェクトルート(Reactの方)の node_modules のパッケージ関係でエラーがでる。
tsconfig.json のドキュメント読んでみましたが解決しなかったので、場当たり的ですが、下記のようにして回避しました。

functions/tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "compilerOptions": {
    "lib": ["es6", "dom"],
    "module": "commonjs",
    "noImplicitReturns": true,
    "outDir": "lib",
    "sourceMap": true,
    "target": "es6"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

firebase init functions で生成されるファイルを下記のように修正しただけです。

js
1
2
"lib": ["es6"] // was
"lib": ["es6", "dom"] // dom を追加

また、create-react-app の方の tsconfig.json は functions/ を無視するように設定しましょう。

tsconfig.json
1
2
3
4
5
6
{
  ...,
  "exclude": [
    "functions"
  ]
}

ServiceWorker

create-react-app が予め用意してくれてますが SWPrecacheWebpackPlugin が使用されているので workbox 使いたくなります。でも eject はしたくない。

これを使えば workbox でServiceWorker書けます。
https://www.npmjs.com/package/react-app-rewire-workbox

ドキュメント通りにインストールして設定ファイル置いたら、 package.json も編集しましょう。今回は Typescript を使ってるので -scripts-version react-scripts-ts が必要になります。 flow のままなら必要ないですが。

package.json
1
2
3
4
5
6
  "scripts": {
    "start": "react-app-rewired start --scripts-version react-scripts-ts",
    "build": "react-app-rewired build --scripts-version react-scripts-ts",
    "test": "react-app-rewired test --scripts-version react-scripts-ts --env=jsdom --watchAll",
    "eject": "react-scripts-ts eject"
  },

そのうち create-react-app も workbox に乗り換えそうなので、
そうなったらこの手順は不要です。


テスト

create-react-app には最初から jest がセットされてますので

1
$ yarn test

を実行するだけでテストが走ります。
ただこれだと実行中に処理を止めることができないので、新しく package.json にコマンドを追加しましょう。

package.json
1
2
3
4
<!-- filename -->
  "scripts": {
    "test:debug": "react-app-rewired --inspect-brk test --scripts-version react-scripts-ts --env=jsdom --watchAll --runInBand",
  }

  
あとはコードの中に debugger; を記述して、 yarn test:debug を実行。
chromeのURLバーに chrome://inspect ( about:inspect ) と入力。 inspect をクリックしたらデベロッパーツールが開きます。
  
開いたら勝手に処理が止まってますが、この画面を開くのを待ってくれているだけです。
実行 ▶ ボタンを押して、処理を再開したらdebugger; の所で止まります。

  

スナップショットテスト

コンポーネントが正しくレンダリングされているかを1つずつテストしていっても良いのですが、スナップショットテストが楽ですよね。

下記を追加して npm install します。

package.json
1
2
3
4
5
  "dependencies": {
    "@types/react-test-renderer": "^16.0.1",
    "react-test-renderer": "^16.3.2",
    ...
  }
jsx
1
2
3
4
5
6
7
8
9
import * as React from 'react';
import * as renderer from 'react-test-renderer';
import Hello from './Hello';

test('Hello renders correctly', () => {
  const tree = renderer.create(<Hello name="Shun" level={5} />)
                       .toJSON();
  expect(tree).toMatchSnapshot();
});

yarn test を実行するとスナップショットが作成される。便利ー。
react-scripts-ts 使っているので ts-jest とかを別途設定する必要はありません。

__snapshots__ ディレクトリがコンポーネントと同じ階層に作成されます。この中に入っている内容と、テスト結果が異なったらエラーになるという仕組み。
  
コンポーネントがいろんな階層に入っていてそれらをテストする場合、 __snapshots__ ディレクトリが色んなところにできてしまって、個人的には気持ち悪いです。ルートかどこかにまとめておきたい…。
この件についてはこちらで議論されていますが、進展はない様子。
https://github.com/facebook/jest/issues/1650
  
エラーになったけど、テスト結果のスナップショットの方が正しい場合 u をタイプすることでスナップショットが更新されます。ちゃんとgitに含んでおきましょう。


エディター

Atom

これも何でも良いと思いますがTypescriptとの相性が良いということで Atom。
こちらのプラグインをインストールが必要です。

https://atom.io/packages/atom-typescript

ドキュメントにもありますが、依存関係として atom-ide-ui こちらも apm でイントールを忘れずに。

便利機能

atom-import-js が .ts ファイルをインポートしてくれないので、こちらを使いました。
https://atom.io/packages/typescript-modules-helper

1
2
3
4
5
6
// 一度インポートしたいファイルを開いて
command + shift + P
build index とタイプして実行

// 読み込みたいファイルを開いて、読み込みたいコンポーネントにカーソルをあてた上で、
control + option + M

一度インデックスを作らないといけないのが面倒ですが、まぁそんなに手間でもないかな。
やらなくてもよくなるようにすると書いてありますが、そのまえに atom-typescript が実装するような気もする。

   
以上です。
最初は Webpack の設定とかコツコツやってたから時間がかかってしまった… 結局捨てたし…

次回からはこの流れですぐ作れそう。
  
と思いましたが、実際は案件によりますよね。
Railsと連携して色々やりたい、サーバーサイドレンダリングが必要、とかであればまた変わってくると思います。代替手段としてはこちらを参考に。
https://github.com/facebook/create-react-app/blob/master/README.md#popular-alternatives
  
それではー

  

2
Shunsuke Sawada

おすすめの記事

CloudFunction と React で共通のコードを読み込んで TypeScript でうまくコンパイルする方法
TypeScript と react-router と mobx でログインユーザーの状態管理
Firestore の Security Rule を理解する