はじめに
週末プロジェクトで、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
による環境構築
- preact にも React の
create-react-app
と同様にボイラープレートのツールがあります。 - https://github.com/developit/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 やホットリローディングにも対応しています。素晴らしい!!
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点となっています💯
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の更新処理の実装
unistore
が redux
よりも手軽だなあと感じたのは、以下の理由があります。
- State管理をする上で、登場人物(人じゃないけど。。)が少ない
- Action は、いるが、Reducerはなく、
Store
オブジェクトのsetState
関数で、Action内で、直接更新 -
redux-thunk
など別にミドルウェアを入れなくても、Promise
とasync/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-redux
のconnect
と同じく、第1引数は、mapStateToProps
の要素をオブジェクトではなく文字列で指定します。
今回は、カウントとユーザー情報を表示したいので、'count,users'
で、複数指定しています。
ここまで、画面としては、以下のようになり、ボタンをクリックしたら、数字のインクリメントやユーザー情報が表示されます。
最後に、ここまでの変更をビルドして、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 で、パフォーマンス計測します✅
多少、パフォーマンスの数値は変動しますが、PWAのスコアは、相変わらず100点でした💯
ということで、ReactライクなコンポーネントとFluxベースのState管理でフロントエンド開発できるPWAのベースができました!
今日1日触ってみただけなので、まだいろいろと気になることはありますが、暇を見つけて、知見を深めていこうと思います。
少しでも参考になれば、幸いです🙏