Preact & unistore & Firebaseで、Flux な PWAのベースを作る

# はじめに
週末プロジェクトで、PWA構成のちょっとしたアプリでも作ってみたいなあと思ったので、今日やったことの記録。
同じようなことをしている日本語情報が、あまりなかったので、メモとして残すことにしました。

React が好きなのとどうせならパフォーマンスにこだわりたいので、フレームワークは **React** ではなく[**Preact**](https://preactjs.com/) 。
ステート管理は、**Redux** ではなく、**unistore** を使ってみることにしました。

どちらも、パフォーマンスが良く、ライブラリのサイズが非常に小さいのが、特徴です。
# この記事を読んで得られる情報

- Preactで、PWAアプリのベースを作る方法
 - Preact によるUIコンポーネントの実装(Reactとほぼ同じ)
 - unistore による State 管理
 - PWAアプリを Firebase にデプロイする方法
 - [Lighthouse](https://developers.google.com/web/tools/lighthouse/?hl=ja) によるパフォーマンスチェック

# セットアップ

## Node.js 
- マシンの OS は、Mac OSで、Node.js がすでにインストールされていることを前提とします

今回は、現時点で、Node.js の LTS である `v10.13.0` を使用します。

```bash
node -v
v10.13.0
```

## `preact-cli` による環境構築
- preact にも React の `create-react-app`と同様にボイラープレートのツールがあります。
 - https://github.com/developit/preact-cli

```bash
npm i -g preact-cli
```

### プロジェクトの作成

```bash
preact create <template-name> <project-name>
```

`<template-name>`は、初期実装時のCSSスタイルやレイアウトを設定します。`default`, `material`, `simple`, `widget`のいずれかを指定します。今回は特にこだわりなく、`default`にして、任意のアプリケーション名を入力します。

パッケージ管理は、`yarn` を使いたいので、オプションで、`--yarn` を追加します。

```bash
preact create default preact_pwa --yarn
cd preact_pwa
yarn start
```

`yarn start` すると、爆速で、Preact アプリケーションが立ち上がります🎉
`preact-cli`で作成したアプリケーションは、PWAの要件を満たす構成を自動で生成し、SSR(サーバーサイドレンダリング)、プレレンダリング [^1] やホットリローディングにも対応しています。素晴らしい！！

[^1]: https://www.netlify.com/blog/2016/11/22/prerendering-explained/

<img width="470" alt="スクリーンショット 2018-11-17 23.59.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/41385/6c6a3f2f-1cd8-0994-10e3-26b284fb8681.png">

## Firebase の設定とデプロイ

```bash
npm i -g firebase-tools
firebase login
# Googleアカウントでログイン

firebase init
# hosting を有効にする
# hosting 対象のパスを 'public' ではなく 'build' に設定する

yarn run build
firebase deploy --only hosting
```

## Lighthouse によるパフォーマンスチェック
- Lighthouse を使って、アプリケーションがPWAの要件を満たしている確認します。

Firebase hosting にデプロイした先程のアプリを Lighthouse で計測するとPWAのスコアは100点となっています💯

<img width="855" alt="スクリーンショット 2018-11-18 0.14.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/41385/9e665a6e-3af7-c3a9-a841-0a8063c44da1.png">

# State管理の仕組みを導入する
Reactなら、Reduxで State管理することが多いですが、Preactでも、Reduxを使うことはできます。

ただ、筆者が、最近 Redux に疲れたので、もう少しとっつきやすいものはないかと調べていたら、`Preact`の作者が作ったライブラリである`unistore`が、あったので使うことにしました。 

State管理の方法自体は、Reduxとそんなに変わらないです。

ここでは、

- クリックイベントによる数字のカウント機能
- モック用データを使った外部APIのデータ取得を想定した非同期処理

を`unistore`で、実装してみます。

### Store の作成

`src/store.js`

```javascript
import createStore from 'unistore';

import devtools from 'unistore/devtools';

const initialState = { count: 0, users: [] };
const Store = process.env.NODE_ENV === 'production' ?  createStore(initialState) : devtools(createStore(initialState));

export default Store;
```

なんと、State の状態変化は、Redux-devtools で確認や編集が可能です！

### アプリ全体に `unistore` によるState管理を適用させる

`src/provider.js`

```javascript
import { h } from 'preact';
import { Provider as PreactProvider } from 'unistore/preact';
import App from './components/app';
import Store from './store';

const Provider = () => (
	<PreactProvider store={Store}>
		<App />
	</PreactProvider>
);

export default Provider;

```

### Action と Stateの更新処理の実装

`unistore` が `redux` よりも手軽だなあと感じたのは、以下の理由があります。

- State管理をする上で、登場人物(人じゃないけど。。)が少ない
 - Action は、いるが、Reducerはなく、`Store`オブジェクトの`setState`関数で、Action内で、直接更新
- `redux-thunk`など別にミドルウェアを入れなくても、`Promise` と `async/await`だけで非同期処理が済む

まずは、モック用途のデータと非同期なGET処理を実装します。

`src/config/sampleData.js`

```javascript
const sampleData = [
  {
    id: 1,
    name: 'Erik Raymond',
    work: 'ForeTrust',
    email: 'erik.raymond@foretrust.net',
    dob: '1941',
    address: '31 Hill Street',
    city: 'Fresno',
    optedin: true
  }
];

export default sampleData;
```

`src/config/mockApi.js`

```javascript
import sampleData from './sampleData';

const delay = ms => (
	new Promise(resolve => setTimeout(resolve, ms))
);

export const fetchSampleData = () => (
	delay(1000).then(() => (
		Promise.resolve(sampleData)
	))
);
```

#### Action と State 更新の実装

`src/components/actions/index.js`

```javascript
import { fetchSampleData } from '../../config/mockApi';

const actions = (store) => ({
	increment: ({ count }) => ({ count: count+1 }),
	async getUsers(state) {
		const res = await fetchSampleData();
		return { users: res };
	}
});

export default actions;
```

### Component を Store と Action に紐付ける

`src/components/app.js`

```javascript
import { h } from 'preact';
import { connect } from 'unistore/preact';
import actions from './actions';

const App = props => {
  const { count, increment, users, getUsers } = props;
    
  return (
    <div>
      <p>Count: {count}</p>
      {users.length > 0 && users.map(user => (
        <p>Name: {user.name}</p>
      ))}
      <button onClick={getUsers}>Display Usernames</button>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default connect('count,users', actions)(App);
```

`connect`関数は、`react-redux`の`connect`と同じく、第１引数は、`mapStateToProps`の要素をオブジェクトではなく文字列で指定します。
今回は、カウントとユーザー情報を表示したいので、`'count,users'`で、複数指定しています。

ここまで、画面としては、以下のようになり、ボタンをクリックしたら、数字のインクリメントやユーザー情報が表示されます。

<img width="465" alt="スクリーンショット 2018-11-18 1.28.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/41385/86e7ad5a-259f-8bf1-f0c7-6f6b6c72bd9b.png">

最後に、ここまでの変更をビルドして、Firebaseにデプロイし、Lighthouse で、パフォーマンスチェックをします。

```bash
yarn run build
firebase deploy --only hosting
```

💣 ちなみに、筆者の環境では、ビルドが失敗しため、以下の作業が必要となりました。

- npm で、グローバルでインストールした`preact-cli`が、古いバージョンだったので、`package.json`を書き換え、現時点での latest release 版の`v2.2.1`にアップデート
- `preat-cli`のビルドツールは、デフォルトでは、`async/await` 構文のビルドがサポートされていないため、[このIssue](https://github.com/developit/preact-cli/issues/578) を参考に、別途プラグインの追加と設定ファイルを作成する
 - `yarn add --dev fast-async`
 - プロジェクトのルートディレクトリに`preact.config.js`を作成

`preact.config.js`

```javascript
// async/await 構文をビルド可能にするための設定
// https://github.com/developit/preact-cli/issues/578
export default (config, env, helpers) => {
	if (env.isProd) {

		// Make async work
		let babel = config.module.loaders.filter( loader => loader.loader === 'babel-loader')[0].options;
		// Blacklist regenerator within env preset:
		babel.presets[0][1].exclude.push('transform-async-to-generator');
		// Add fast-async
		babel.plugins.push([require.resolve('fast-async'), { spec: true }]);
	}
};
```

再度、`yarn run build`を実行すれば、ビルドが成功したので、Firebaseにデプロイ

そして、Lighthouse で、パフォーマンス計測します✅

<img width="848" alt="スクリーンショット 2018-11-18 1.41.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/41385/fac09f4b-5693-ff1e-2c95-a649500ec2a4.png">

多少、パフォーマンスの数値は変動しますが、PWAのスコアは、相変わらず100点でした💯

ということで、ReactライクなコンポーネントとFluxベースのState管理でフロントエンド開発できるPWAのベースができました！

今日１日触ってみただけなので、まだいろいろと気になることはありますが、暇を見つけて、知見を深めていこうと思います。
少しでも参考になれば、幸いです🙏