Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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

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

どちらも、パフォーマンスが良く、ライブラリのサイズが非常に小さいのが、特徴です。

この記事を読んで得られる情報

  • Preactで、PWAアプリのベースを作る方法
    • Preact によるUIコンポーネントの実装(Reactとほぼ同じ)
    • unistore による State 管理
    • PWAアプリを Firebase にデプロイする方法
    • Lighthouse によるパフォーマンスチェック

セットアップ

Node.js

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

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

node -v
v10.13.0

preact-cli による環境構築

npm i -g preact-cli

プロジェクトの作成

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

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

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

preact create default preact_pwa --yarn
cd preact_pwa
yarn start

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

スクリーンショット 2018-11-17 23.59.57.png

Firebase の設定とデプロイ

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点となっています💯

スクリーンショット 2018-11-18 0.14.22.png

State管理の仕組みを導入する

Reactなら、Reduxで State管理することが多いですが、Preactでも、Reduxを使うことはできます。

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

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

ここでは、

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

unistoreで、実装してみます。

Store の作成

src/store.js

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

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の更新処理の実装

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

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

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

src/config/sampleData.js

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

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

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

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-reduxconnectと同じく、第1引数は、mapStateToPropsの要素をオブジェクトではなく文字列で指定します。
今回は、カウントとユーザー情報を表示したいので、'count,users'で、複数指定しています。

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

スクリーンショット 2018-11-18 1.28.22.png

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

yarn run build
firebase deploy --only hosting

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

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

preact.config.js

// 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 で、パフォーマンス計測します✅

スクリーンショット 2018-11-18 1.41.58.png

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

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

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

samuraikun
トッキョーで、ウェッブエンジニャーやってます。 フロントエンドやサーバレスまわりの技術が大好きなので、そこらへんでいろいろ知見を共有していきたいです🚀
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした