React アプリケーションの Apollo Client を使ったリビジョンアップ方法

React のアプリケーションを修正してリリースしてもユーザーの端末ではリロードしないと新しいスクリプトを読み込んでくれなくて古いアプリケーションが動いたままになることがあります。検索して以下のような情報がありました。

qiita.com

リビジョンIDをアプリケーション内とファイルで持っておいて、ファイルを読み込んで差異があったらリロードをするというものです。GraphQL のアプリケーションだったので今回はこれを参考にし、Apollo Client + Apollo Link Rest を使って実現します。

前提

  • Apollo Client + GraphQL のアプリケーションに追加する
  • TypeScript で実装している(コードの例は TypeScript です)

Apollo Link Rest をインストール

Apollo Link Rest は Apollo Client で REST API を叩けるモジュールです。GraphQL と同じような Query を作り、同じように処理を書くことができます。公式も参考に。

https://www.apollographql.com/docs/link/links/rest/

モジュールをインストールします。GraphQL 関連はインストール済みの前提で新たに必要なもののみ記載しています。

$ yarn add apollo-link-rest@0.7.3 graphql-anywhere qs

バージョンを 0.7.3 に固定しているのは 2020/4/23 時点で latest を取得すると apollo-client@3 以降を要求するバージョンになるためです。

クライアントを定義

ほぼ公式そのままです。

const restLink = new RestLink({
  uri: 'https://www.example.com/',
});

export const client = new ApolloClient({
  link: restLink,
  cache: new InMemoryCache(),
});

ここで作成したクライアントを使用して以下のクエリを叩きます。 GraphQL 用の client と被らないようにしましょう。

クエリを定義

type Revision = {
  revision: {
    value: string;
  }
};

const Query = gql`
query findRevision {
  revision @rest(type: "Revision", path: "revision.json") {
    value
  }
};
`

ここでは Revision という型を定義し、Query の戻りの型として設定しています。

クエリ処理

次に実際にクエリを叩くところです。 Hooks を利用しています。

const { loading, data } = useQuery<Revision>();

if (loading || !data) return null;

if (currentVersion !== data.revision.value) {
  window.location.reload(true);
}

このように、 GraphQL のクエリと同じ様に Rest を叩くことができます。 currentVersion はビルド時にリビジョンIDを生成して定義しておくといいでしょう。 例えばビルド時にタイムスタンプを生成して process.env.RevisionID とかにマッピングしておくとかがあります。 webpack を使用している場合は webpack.DefinePlugin で実現できます。 revision.json についても同じタイムスタンプにする必要があるので同じくビルド時にその値でファイルを作っておくといいと思います。

アプリケーションに組み込む

このままだとこれを組み込んだコンポーネントが読まれたときに1回実行されるだけになります。 react-router などを利用している場合はルーティングされるたびにマウントされる場所に置いておけばいいかもしれませんが頻度が高いかもしれません。それを解決する方法としては任意の間隔でポーリングする方法がありますが、 useQuery ではポーリングを設定できるのでそれを利用するのも1つです。

useQuery<Revision>({
  pollInterval: 60000, // ミリ秒
  fetchPolicy: 'network-only',
});

以上のようにすると60秒毎にクエリが発行されます。詳しくは公式を参考にしてください。

https://www.apollographql.com/docs/react/data/queries/

まとめ

axios などのモジュールを使わず useQuery で GraphQL を使っているかのように実装できました。

リビジョンチェックのサンプルです

json ファイル

{ "value": "20200401112233" }

コンポーネント

import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';

type Revision = {
  revision: {
    value: string;
  };
};

const Query = gql`
  query findRevision {
    revision @rest(type: "Revision", path: "revision.json") {
      value
    }
  }
`;

export const Reloader = () => {
  const { loading, data } = useQuery<Revision>(Query, {
    pollInterval: 60000,
    fetchPolicy: 'network-only',
  });

  if (loading || !data) return null;

  const currentVersion = process.env.RevisionID;

  if (currentVersion !== data.revision.value) {
    window.location.reload(true);
  }

  return null;
};