ShopifyテーマにReactのコンポーネントを描画する

アソビューAdvent Calendar 2022の12日目のブログです。
本日の裏面はこちらになります。
tech.asoview.co.jp

こんにちは!
アソビューでフロントエンドエンジニアをしている野口です。

今回は、Shopifyのテーマ上でReactのコンポーネントを描画する調査をしたのでその内容を紹介していきます。

はじめに

弊社では、アソビュー!に掲載されている遊びの中から厳選した体験を、家族や友人にプレゼントできるアソビュー!ギフトというサービスも展開しています。
そのアソビュー!ギフトはShopifyを利用してECサイトを構築しており、テーマはDawnを使っています。

アソビュー!ギフトのトップページ

今回、一部機能においてアソビューで管理している別のアプリケーションから情報を取得したいという要望が出てきました。
UIもテーマのスタイルを利用するのではなく独自でstyleを当てる必要があったためjQueryではなくReactで開発できないかと思い調査した内容になります。

Shopifyのテーマファイルをローカルにpullする

まずはShopify cliを利用して、Shopifyのテーマファイルをローカルにダウンロードします。
今回はすでにストア上でテーマを利用している前提で説明していきます。

brew tap shopify/shopify
brew install shopify-cli

インストールが完了したら、ストアに接続しテーマをダウンロードします。

// ストアに接続
shopify login --store {ストアのURL}

// テーマのダウンロード
shopify theme pull -i {テーマのID}

これで、ローカルにストアのテーマをダウンロードできました。

Reactの開発環境を整える

次に、Reactの開発環境を整えていきます。

フォルダ構造

まずは開発するコードをどこで管理するか検討します。

Shopifyのテーマはフォルダ構造が決められており、ストアに反映する際にこのフォルダ構造に則っていないフォルダは無視されます。(参考

今回はこの制約を利用して、開発用コードをsrcフォルダに作成し、バンドルしたコードをassetsフォルダに配置する方法にしました。

バンドルしたコードについてはS3に配置することも考えたのですが、コード量・開発頻度も多くないためそこまでする必要性を感じなかったのと、修正を反映するたびに最新のjsファイルを取得してくれブラウザキャッシュも考慮せず良さそうだったのでこのやり方にしています。

.
├── assets
├── config
├── layout
├── locales
├── sections
├── snippets
├── templates
└── src  // <- 追加

ちなみに、ビルドツールを通した開発をする場合、Shopifyではビルド前のコードとビルド後のコードでブランチを分けて管理することを推奨しています。

この場合、git subtreeコマンドを使い、開発用のブランチとストア連携用のブランチを分ける運用になると思いますが、弊社の運用を考えたときに本番とステージングで開発用ブランチ、連携用ブランチを分けて管理するとなるとそれぞれを適切に同期していく必要がありかなり大変そうな印象を受け見送りました。

ビルドツールの設定

バンドラーにはwebpackを利用します。
特に複雑なことはせず、最小限のセットアップです。

  • ライブラリのインストール
yarn add react react-dom
yarn add -D typescript babel-loader webpack webpack-cli @types/react @types/react-dom @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
  • buildの設定
// babel.config.js
module.exports = {
  presets: [
    '@babel/preset-env',
    '@babel/preset-react',
    '@babel/preset-typescript'
  ]
}
// webpack.config.js
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './src/index.tsx',
  output: {
        filename: 'bundle.min.js',
        path: path.resolve(__dirname,'assets')
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],
    modules: [path.resolve(__dirname, "node_modules")]
  },
  module: {
    rules: [
      {
        test: [/\.ts$/, /\.tsx$/],
        exclude: /node_modules/,
        loader: 'babel-loader'
      }
    ]
  },
  optimization: {
    minimizer: [new TerserPlugin({
      extractComments: false,
    })],
  },
};
  • npmスクリプト設定
{
  ...
  "scripts": {
    "build": "webpack"
  },
  ..
}

これで yarn build を実行するとassetsディレクトリ配下に bundle.min.js が作成されるようになります。

  • theme.liquidでbundleされたjsファイルを読み込む

layout/theme.liquidファイルでバンドルしたjsファイルを読み込むようにします。

// theme.liquid
<script src="{{ 'bundle.min.js' | asset_url }}" defer="defer"></script>

実装

設定が完了したのでReactのコンポーネントを作成していきます。

  • マウント対象の要素を追加

今回はtheme.liquidに追加しています。

// theme.liquid
<div id="root"></div>
  • マウントするReactコンポーネントを作成
// src/App.tsx
import React from 'react'

const App = () => {
  return <div>Hello App!</div>
}

export default App
// src/index.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App'

const el = document.getElementById('root')
const root = el && createRoot(el);

root && root.render(<App />);

この状態で yarn buildし画面を確認するとReactコンポーネントの表示が確認できます。
画面の確認は shopify theme serveすることでローカルで簡単に確認可能です。

hot reloadの設定

描画はできたのですが、
このままではコンポーネントの修正をするたびにyarn buildをする必要があります。
修正を検知して再ビルドの必要なく確認できるようにしたいので調整します。

以下のようにwebpackのwatchオプションでの起動をnpmスクリプトに追加します。

// package.json
{
  ...
  "scripts": {
    "build": "webpack",
    "dev": "webpack --watch"
  },
  ..
}

この状態で、yarn dev & shopify theme dev を実行すればhot reloadで開発ができるようになります。

最後に

今回はShopifyのテーマ上でReactコンポーネントを描画する方法を紹介しました。
部分的に利用する前提で紹介していますが、もしReactのコードが増えるようであればHydrogenを利用するなどしてフロント部分は独自で開発するほうが良いかと思います。

アソビューでは一緒に働くメンバーを大募集しています! カジュアル面談もありますので、少しでも興味があればお気軽にご応募いただければと思います!

www.asoview.com