BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Pay IDアプリのWebViewにReact + Viteを導入した

はじめに

本記事は BASE Advent Calendar 2023 の17日目の記事です。

こんにちはPay IDでフロントエンドエンジニアをしているnojiです。普段はあと払い(Pay ID)に関するフロントエンド周りの開発をメインに行っています。

payid.jp

11月6日にあと払い(Pay ID)は口座振替機能をリリースしました。これにより、今まではあと払い(Pay ID)を利用した翌月に、コンビニに支払いに行く必要がありましたが、金融機関口座を登録することでコンビニに行かずに手数料無料で自動で引き落とされるような支払い方法を選べるようになりました。

https://payid.jp/atobaraiより)

口座振替登録時には以下のような画面を経て、金融機関のページに遷移し、金融機関と連携をする必要があります。

自分は口座振替機能におけるアプリでの口座申込画面の開発をWebViewで行ったのですが、本記事ではそのWebViewに新規でReact + Viteを導入した話を紹介します。

なぜWebViewを利用したのか

なぜ、今回iOSやAndroidのアプリで実装するのではなく、WebViewを利用したのかというと、

  • 「金融機関」というBASEやPay ID管理外の外部のシステムと連携するにあたり、不確実性が高い
  • 金融機関との連携で異常があった場合など、WebViewであれば緊急で修正のリリースが可能
  • iOS/Androidエンジニアの対応が不要で、フロントエンドエンジニアが対応したものがiOS/Androidの両方で動く

などの理由がありました。

WebViewでやるにあたっての技術選定

実は以前からアプリの一部ページでは、WebViewが使われていました。しかし、CakePHP + jQuery or JavaScriptで書かれていました。

今回、WebViewで実装するにあたり、既存の仕組みでやることもできなくはなかったですが、以下の理由で違う方法を試すことにしました

  • 今回の機能要件に入力中のtextからこちらが用意している項目の一致で絞り込みやサジェストを行う機能があったが、jQuery, JavaScriptだとやや実装が大変
  • 保守やレビューなども含めたメンテナンス性(TypeScriptも使われていないので。。)
  • 今後Pay IDアプリの機能拡充で追加のWebViewを作る可能性があるので違う方法を試すチャンス

断念したことにより、フロントエンドライブラリを導入して実装することにしました。今回はReactを導入することにしたのと、サーバーサイドは既存のCakePHPのサーバーを利用するようにしました。

Reactの理由

  • 開発者本人が書き慣れている
  • BASEのフロントエンドでReact化が進行している
  • preact, solid, svelteなど他の軽量めなライブラリ導入も考えたが、学習コストとなにかあった際にほかメンバー対応できない可能性がある
  • TypeScriptで書きやすい

サーバーサイド現状維持の理由

  • 実装する数ページのためにサーバーを立てるのは管理コストなども含めてオーバースペックになる
  • ページに認証が必要で、認証をPHP側で行っているが、サーバーをたてるとそこでも認証を考慮する必要が出てくる
  • 開発着手からリリースまでのスケジュール的な部分であまり大規模な導入を行いたくなかった

また、ReactのビルドツールについてはViteを利用することにしました。

  • webpackよりも高速
  • HMRが標準で使えるので、開発しやすそう
  • GitHubのstar数でwebpackに追いついていきそう(2023年12/13日現在 webpack=63.8k, vite=61.8k)

今回利用する対応量でビルドツールによる大きな差分が出ることはほぼなかったと思いますが、勢いのあるツールだと思ったのでやや勢いで導入しました。

構成的なところ

Viteの導入とSPAの作成については割愛します。

Reactで作ったSPAとバックエンドをどのように統合したかについてを書こうと思います。

Viteではビルド時の設定をvite.config.jsに記述します。

vite.config.js

const react = require('@vitejs/plugin-react');
const { defineConfig } = require('vite');

module.exports = defineConfig({
    ...
  build: {
    outDir: 'path_to_manifest', // manifestファイルの出力ディレクトリ
    manifest: true,
    rollupOptions: {
      input: 'src/index.tsx',
    },
    modulePreload: {
      polyfill: false,
    },
  },
  ...
});

frontendをビルドすると以下のようなファイル郡が作成されます。manifest.jsonにはビルド済みのindex-〇〇.jsやindex-△△.cssファイルのpathが格納されています。(〇〇、△△にはランダムな文字列が入ってます。以下は一例です)

 ├── assets

   ├── index-〇〇.js

   └── index-△△.css

 └── manifest.json

(outputに関しては、フロントエンドや設定の構成などによって、内容が変わることがあります)

PHPの例は以下の通りでサーバー側でmanifestファイルからcssとjsのファイル名を取得し、テンプレートエンジンに渡すパラメータに入れます。

class SampleController {
    ...
    public function v1_hogehoge() {
        // 何かしらの処理...(認証など)
        $this->set_assets();
    }

    private function set_assets() {
        $filePath = 'path_to_manifest'; // manifestファイルへのpath
        $manifest = json_decode(file_get_contents($filePath), true);
        $jsUrl = $manifest['src/index.tsx']['file'];
        $cssUrl = $manifest['src/index.css']['file'];
        $this->set([
            'js_url': $js_url,
            'css_url': $css_url
        ])
  }
}

テンプレートエンジンはtwigを利用しています。

ローカル環境でHMRを利用するために、サーバーから返すファイルtwigファイルは以下のようにしています。

{% if env == 'local' %}
    <script type="module">
       import RefreshRuntime from 'http://localhost:5173/@react-refresh'
       RefreshRuntime.injectIntoGlobalHook(window)
       window.$RefreshReg$ = () => {}
       window.$RefreshSig$ = () => (type) => type
       window.__vite_plugin_react_preamble_installed__ = true
   </script>
    <script type="module" src="http://localhost:5173/@vite/client"></script>
    <script type="module" src="http://localhost:5173/src/index.tsx"></script>
{% else %}
    <link rel="stylesheet" href={{ css_url }} />
    <script src={{ js_url }} defer></script>
{% endif %}

<div id="root"></div>

ローカルのときはローカルの開発環境のサーバーを参照し、デプロイした際にはビルド済みのassetを読み込むようにしています。

クライアント側では、以下のようにreact-routerでルーティングを行い、CSRしています。

src/index.tsx

const router = createBrowserRouter([
    {
        path: '/path_to_page',
        element: <PageComponent />,
    },
    {
        path: '*',
        element: <NotFoundPageComponent />,
    },
]);

const root = ReactDOM.createRoot(
    document.getElementById('root') as HTMLElement
);
root.render(
    <React.StrictMode>
        <RouterProvider router={router} />
    </React.StrictMode>
);

導入しての所管

ページ数やファイル数が多くないので、Viteのスピード面でとても大きいメリットを享受出来たとまでは言えないですが、ローカル環境ではHMRが効いていて更新がすぐに反映するので、細かい調整などとても開発がしやすく感じました。

設定が多くなく、Vite自体の導入して実際に開発できるようになるまでがスムーズに感じました。

ファイル数が増えてくるなど、今後WebViewでやることが増えてきたときによりメリットを享受できるかもしれないなと思いました。

まとめ

今回はPay IDアプリのWebViewにReact+Viteの導入についてお話しました。

明日はeijiさんの記事です。お楽しみに!

参考

https://ja.vitejs.dev/guide/backend-integration.html

https://payid.jp/atobarai