🛍️

React + Shopify のテーマをOnline Store 2.0に移行する(観点洗い出し編)

2023/12/11に公開

自己紹介

こんにちは!今年もこの季節がやってきましたね🎄
株式会社DELTAでフロントエンドエンジニアをしている、るなこです。
昨年の目論み通り、フロントエンドからバックエンド開発にも関わるようになってきました💪(今回の記事はフロントエンドですが)

こちらの記事は、はじめてのアドベントカレンダー Advent Calendar 2023 シリーズ10の11日目の記事です。本当は初めてではないのですが、ShopifyもLiquidもアドベントカレンダーがなく。。。クリスマスまでに、あと2記事書く予定なのでお楽しみに!

はじめに

今回は、React と Shopifyテーマで構築しているサイトを Online Store 2.0 に移行するための観点を調査しました。バージョンアップが必要になった理由は、ECサイトで使用しているアプリのある機能が Online Store 2.0 にしか適応していないためです。バージョンアップに伴う影響範囲、作業工程を洗い出すところまでを記事にしています。

結論としては、影響範囲はほぼ全体だけど、作業としては地道な書き換えになります。

つらいのか、いや救いなのか…。

そもそもOnline Store 2.0とは

2021年8月にリリースされたアップデートです。今までトップページにしか表示できなかった「セクション」がサイト内のどこにでも表示できるようになったほか、テンプレートでも、管理画面からでもセクションを自由に追加できるようになりました。

これにより、ユーザー側でカスタマイズできるページがより広がることになりました。わざわざエンジニアに依頼しなくてもちょっとした値の変更・テキストの変更ができるようになるのは、時間的にも双方の手間においても非常に良い機能ですね!

開発についての変更点は、以下のようなものがあります。

  • templatesがliquidファイルからjsonファイルに変更
  • sectionの中でsectionの呼び出しはできない
    • templateの中ではsectionを、sectionの中ではsnippetsを呼び出す規則に

Online Store 2.0 については、以前まとめた記事もありますので良ければご参照ください。
https://zenn.dev/team_delta/articles/52bb283248b27b

サイトの構成

現在のECサイトは、React と Shopifyテーマで構築しています。商品情報は、Liquidコード側でproduct-context.liquidというsnippetファイルでscriptタグを出力し、これをheaderタグ内で読み込むことでグローバル変数として渡しています。

├── app     ←  React, Storybook のコード類
└── theme   ←  Shopifyのliquidコード類
    ├── assets
    ├── config
    ├── layout
    ├── locales
    ├── sections
    ├── snippets
    └── templates 

(React内での処理は、本題からそれるため割愛させていただきます)

product-context.liquid
<script>
   (()=>{
   const d = window.Delta || {};
   const products = [];
    {% for collection in collections %}
      {% if collection.title != "お弁当メニュー(マイページ用)" %}
        {% for product in collection.products %}
          {% increment count_ %}
          const product{{ count_ }} = {
            id: "{{ product.id }}",
            title: "{{ product.title }}",
            ... 中略 ...
          };
          {% for variant in product.variants %}
            {% increment innerCount_ %}
            const variant{{ innerCount_ }} = {
              id: "{{ variant.id }}",
              title: "{{ variant.title }}",
              ... 中略 ...
            };
            product{{ count_ }}.variants.push(variant{{ innerCount_ }});
          {% endfor %}
          products.push(product{{ count_ }});
        {% endfor %}
      {% endif %}
    {% endfor %}
      window.Delta =  {
        ...d,
        products
      } 
   })()
</script>

Online Store 2.0 移行作業

影響範囲

ほぼ全体です。sectionタグが記述されているファイル、templatesフォルダにあるliquidファイルを修正するため、ほぼ全てのテンプレートの表示確認が必要になります。
(ただし、部分的に移行作業をすることで一部のみOnline Store 2.0への移行することもできそうではあります。(表示未検証)(例:productページのみなど))

作業工程概要

Online Store 2.0 への移行方法はShopifyの開発ドキュメントにまとまっています。
https://shopify.dev/docs/themes/os20/migration

ざっくりまとめると、

  • テーマのバックアップを作成
  • 各ファイル内のsectionタグを特定して、削除する
    • templateファイルからsectionファイルへコードを移動する
  • templateファイルを削除して、JSON形式のtemplateファイルを作成する

という流れの繰り返しになります。

作業工程の詳細

自分用の備忘として、移行作業の章を詳しめに書いています。公式ドキュメントの意訳なので、さらっと読み流していただいてもかまいません。

テーマのバックアップ

管理画面から、オンラインストア → テーマ で、現在のテーマを複製しておきます。

各ファイル内のsectionタグを特定して、削除する

  • 各ファイル内のsectionタグを特定する

プロジェクト内でsectionタグを検索し、どのファイルが何のsecitionファイルを呼び出しているか確認します。あとでJSON形式のテンプレートファイルに書き換えできるよう、お互いの呼び出し関係をメモしておきます。

  • 各ファイル内の section タグを削除し、残りのコードを切り出す
templates/product.liquid

// -----------ここから削除
{% section 'product-template' %}
{% section 'product-recommendations' %}
// -----------ここまで削除


// -----------ここから新しいセクションファイルとして切り出し
<div id="backToCollection"></div>

<script>
  // Override default values of shop.strings for each template.
  // Alternate product templates can change values of
  // add to cart button, sold out, and unavailable states here.
  theme.productStrings = {
    addToCart: {{ 'products.product.add_to_cart' | t | json }},
    soldOut: {{ 'products.product.sold_out' | t | json }},
    unavailable: {{ 'products.product.unavailable' | t | json }}
  };

  // -- 省略 --
</script>

{% assign current_variant = product.selected_or_first_available_variant %}

<script type="application/ld+json">
{
  "@context": "http://schema.org/",
  "@type": "Product",
  "name": {{ product.title | json }},
  "url": {{ shop.url | append: product.url | json }},

  // -- 省略 --
}
</script>
// -----------ここまで新しいセクションファイルとして切り出し

サンプルとしてproduct.liquidを見てみると、sectionタグを削除してもまだコードが残っていることがわかります。新たにproduct-script.liquidというsectionファイルを作り、残りのscriptタグの部分を移します。

section/product-script.liquid
// -----------ここからproduct.liquidから切り出してペースト
<div id="backToCollection"></div>

<script>
  // Override default values of shop.strings for each template.
  // Alternate product templates can change values of
  // add to cart button, sold out, and unavailable states here.
  theme.productStrings = {
    addToCart: {{ 'products.product.add_to_cart' | t | json }},
    soldOut: {{ 'products.product.sold_out' | t | json }},
    unavailable: {{ 'products.product.unavailable' | t | json }}
  };

  // -- 省略 --
</script>

{% assign current_variant = product.selected_or_first_available_variant %}

<script type="application/ld+json">
{
  "@context": "http://schema.org/",
  "@type": "Product",
  "name": {{ product.title | json }},
  "url": {{ shop.url | append: product.url | json }},

  // -- 省略 --
}
</script>
// -----------ここまでproduct.liquidから切り出してペースト

また、sectionファイルには必ずschemaと呼ばれる記述を必ず記述します。

section/product-script.liquid
 // -- 省略 --
{% schema %}
{
  "name": "product-script",
  "settings": [
    //ここに管理画面から設定できる各種設定を追加
  ],
  "presets": [
    {
      "name": "ここに管理画面でセクションを表示するときのラベル(日本語:商品用scriptタグ など)"
    }
  ]
}
{% endschema %}

templateファイルを削除して、JSON形式のtemplateファイルを作成する

templates フォルダ内の xxx.liquid ファイルを削除し、xxx.json に置き換えます。先ほどのproduct.liquidを例にすると、product.liquidを削除して、templatesフォルダにproduct.jsonを作成し、もともとのliquidテンプレートファイルで呼び出していたsectionを記述していきます。

テンプレートでsectionを呼び出すオブジェクトは以下のように記述します。sectionファイルに名前を付けて呼び出す部分・呼び出したsectionファイルの順番を指定する部分に分かれています。

product.json
{
  "sections": {
    // ここでsectionファイルに名前を付けて呼び出し
    "main": {
      "type": "product-template"
    },
    "recommendations": {
      "type": "product-recommendations"
    }
  },
  "order": [
    // ここで呼び出したsectionの表示順序を指定
    "main",
    "recommendations"
  ]
}

テンプレートを保存したら、プレビューして確認します。
上記を繰り返して、templatesフォルダのliquidファイルをJSON形式のファイルに置き換えます。

懸念点

上記の繰り返しであれば、地道に書き換えていけばバージョンアップできそうです。ただし、書き換えに伴って2点ほど時間がかかりそうな部分がありました。

さらに細かく分割する必要があるファイル

これはOnline Store 2.0に起因する懸念点です。section ファイル内でさらに section ファイルを呼び出すことはできないため、必要に応じて小さい snippets ファイルに分割する作業が必要になりそうです。

例としてarticle.liquidでは、sectionファイルがHTMLの子要素として呼び出されています。この場合、単にsectionタグを削除するのではなく親要素自体をsectionファイル内に含めるか、親要素をsectionタグとして切り出し、sectionタグで呼び出されているファイルをsnippetフォルダに移動する必要があります。snippetファイルはJSON形式のテンプレートファイルで直接呼び出せないため、sectionファイルの中で呼び出さなければいけません。

article-template.liquid

// -- 省略 --

<article class="page-width" aria-labelledby="title-0">
  <div class="grid">
    <div class="grid__item medium-up--five-sixths medium-up--push-one-twelfth">
      {% section 'article-template' %}
    </div>
  </div>
</article>

// -- 省略 --

article-template.liquidをsnippetフォルダに移し、親要素のsectionファイルを作成したのち、sectionタグをincludeタグに書き換えます。(この場合、もともとarticle-template.liquidに書かれていたschemaを親要素のsectionファイルに移すことになると思うのですが、現時点で未検証です。)

article-template-parent.liquid

<article class="page-width" aria-labelledby="title-0">
  <div class="grid">
    <div class="grid__item medium-up--five-sixths medium-up--push-one-twelfth">
      {% include 'article-template' %}
    </div>
  </div>
</article>

これでarticle.liquidarticle.jsonに書き換えることができます。

article.json
{
  "sections": {
    "xxx": {
      "type": "article-xxx"
    },
    "main": {
      "type": "article-template-parent"
    },
    "yyy": {
      "type": "article-yyy"
    }
  },
  "order": [
    "xxx"
    "main",
    "yyy",
  ]
}

ECサイトで読み込む情報の切替

これはECサイト起因の懸念点です。ECサイトでは、<div id="app"></div>という要素をベースにしてReactを描画させています。このため、常にすべてのページで<div id="app"></div>が読み込まれるようにテンプレートへ入れる必要があります。

加えて、各ページでどのReactファイルを呼び出すかは、使用しているテーマ名(local/dev/stg/prod)によって切り替えている箇所があり、この判定を崩さないまま移行する必要があります。

また、React側への商品情報の引き渡しはproduct-context.liquidで行っており、ページによってはaccount-context.liquidmenu-context.liquidを読み込む場合があります。各テンプレートでこれらのsnippetファイルを二重に呼び出すと、グローバル変数に余計な値が入り込んだり、上書きしてしまう危険性もあります。プロジェクト内のファイル全体を見て、各ファイルの切替を一か所で行えるようまとめると今後のメンテナンスが楽そうです。

まとめ

今回は移行観点の洗い出し編なので、移行編も出す予定です。(アドベントカレンダー後かも?)テストで引っかかった点、つまずいた点を記録します。乞うご期待!

We're Hiring!

最後までお読みいただきありがとうございます。
弊社では一緒に働いてくれる仲間を募集中です!!!!
まずはカジュアル面談からお話しましょう!

Pitta:弊社CTOの丹がベットしたい技術を語りたがってます!
Google Form:もちろんこちらからでもOK!

来てねー!

DELTAテックブログ

Discussion