shopify制作で重要なテーマの構造を知ろう!

shopifyの開発をするのに当たって、テーマの構造を理解する必要が出てきますよね。WordPressなら

こんな感じにファイル見るんですけどね。shopifyもこういう表あるのかな?なさそうなので急ごしらえした物をとりあえず貼っておきます。

今回はdebutテーマを元に作ってあって、customer部分に関しては割愛してある表です。基本の構造を覚えるためならこれで十分でしょう!

んでもって、この解説記事ではこのツリー構造を少しずつ解説していきますね!

この記事を読めばshopifyの階層構造とかがわかるようになりますよ!

まずはLayoutから始まる

さて今回もテンプレートは「debut」を元に解説します。まずはコードを編集からdebutのフォルダ構造を見てみましょう。

フォルダを全部閉じてみると左側に7つのフォルダがある事が確認できるかと思います。今回の解説記事で利用するのはその中でも赤枠で囲ってある

  • Layout
  • Templates
  • Sections

の3つだけになります。snippetについても入れてもよかったのですが、それは別の記事で何となく親子関係を説明しているので、そちらをご覧ください。

sectionが親、snippetが子と言う理解で「最初」はOKです。

見てわかる通りなのですが、フォルダは7個あるけど階層にはなっていないっと言う所がshopifyの独特の構造です。全部横並びにフォルダがあるので、WordPressとかで階層構造があるのに慣れているとやや戸惑います。

しかも一番重要そうな「Layout」フォルダの中身はたった3つ

  • theme.liquid
  • password.liquid
  • gift_acrd.liquid

しか入っていません。このうちpasswordはECサイトがクローズで会員制の場合にアクセスすると表示されるパスワード入力画面用、gift_cardはメールでここにギフトカードあるよ!って送ったときにクリックすると開くギフトカード用のページなので、まぁそんなのもあるんだなぐらいの認識で大丈夫です。

なので実質的には「theme.liquid」ただ1つがshopifyのLayoutと言う事になります。shopifyで作ったECサイトを訪れるときには絶対にこのtehme.liquidが最初に読み込まれている!と言事を覚えて置きまして、この中身の解説に移りましょう。

theme.liquidの中身

さて、このtheme.liquid君がとても重要で、すべてのページにおいて読み込まれる大本となる事を知ったところで中身です。まずは長いですが全部書き出します。

<!doctype html>
<html class="no-js" lang="{{ request.locale.iso_code }}">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="theme-color" content="{{ settings.color_button }}">

  <link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
  <link rel="preconnect" href="https://fonts.shopifycdn.com" crossorigin>
  <link rel="preconnect" href="https://monorail-edge.shopifysvc.com">

  {%- assign header_font = settings.type_header_font -%}
  {%- assign base_font = settings.type_base_font -%}
  {%- assign base_font_bolder = base_font | font_modify: 'weight', 'bolder' -%}
  {%- assign base_font_bold = base_font | font_modify: 'weight', 'bold' -%}
  {%- assign base_font_italic = base_font | font_modify: 'style', 'italic' -%}
  {%- assign base_font_bold_italic = base_font_bold | font_modify: 'style', 'italic' -%}

  <link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style">
  <link rel="preload" as="font" href="{{ header_font | font_url }}" type="font/woff2" crossorigin>
  <link rel="preload" as="font" href="{{ base_font | font_url }}" type="font/woff2" crossorigin>
  <link rel="preload" as="font" href="{{ base_font_bold | font_url }}" type="font/woff2" crossorigin>
  <link rel="preload" href="{{ 'theme.js' | asset_url }}" as="script">
  <link rel="preload" href="{{ 'lazysizes.js' | asset_url }}" as="script">
  {{ 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css' | stylesheet_tag }} 

  {%- if canonical_url != blank  -%}
    <link rel="canonical" href="{{ canonical_url }}">
  {%- endif -%}

  {%- if settings.favicon != blank -%}
    <link rel="shortcut icon" href="{{ settings.favicon | img_url: '32x32' }}" type="image/png">
  {%- endif -%}

  {%- capture seo_title -%}
    {%- if request.page_type == 'search' and search.performed == true -%}
      {{ 'general.search.heading' | t: count: search.results_count }}: {{ 'general.search.results_with_count' | t: terms: search.terms, count: search.results_count }}
    {%- else -%}
      {{ page_title }}
    {%- endif -%}
    {%- if current_tags -%}
      {%- assign meta_tags = current_tags | join: ', ' -%} &ndash; {{ 'general.meta.tags' | t: tags: meta_tags -}}
    {%- endif -%}
    {%- if current_page != 1 -%}
      &ndash; {{ 'general.meta.page' | t: page: current_page }}
    {%- endif -%}
    {%- assign escaped_page_title = page_title | escape -%}
    {%- unless escaped_page_title contains shop.name -%}
      &ndash; {{ shop.name }}
    {%- endunless -%}
  {%- endcapture -%}
  <title>{{ seo_title | strip }}</title>

  {%- if page_description -%}
    <meta name="description" content="{{ page_description | escape }}">
  {%- endif -%}

  {% include 'social-meta-tags' %}
  {% include 'css-variables' %}

  <style>
  長いので省略...
 </style>

  <script>
    window.performance.mark('debut:theme_stylesheet_loaded.start');

    function onLoadStylesheet() {
      performance.mark('debut:theme_stylesheet_loaded.end');
      performance.measure('debut:theme_stylesheet_loaded', 'debut:theme_stylesheet_loaded.start', 'debut:theme_stylesheet_loaded.end');

      var url = "{{ 'theme.css' | asset_url }}";
      var link = document.querySelector('link[href="' + url + '"]');
      link.loaded = true;
      link.dispatchEvent(new Event('load'));
    }
  </script>

  <link rel="stylesheet" href="{{ 'theme.css' | asset_url }}" type="text/css" media="print" onload="this.media='all';onLoadStylesheet()">

  <style>
    {{ header_font | font_face: font_display: 'swap' }}
    {{ base_font | font_face: font_display: 'swap' }}
    {{ base_font_bold | font_face: font_display: 'swap' }}
    {{ base_font_bolder | font_face: font_display: 'swap' }}
    {{ base_font_italic | font_face: font_display: 'swap' }}
    {{ base_font_bold_italic | font_face: font_display: 'swap' }}
  </style>

  <script>

  ここも長いので省略
  </script>

  {%- if request.page_type contains 'customers/' -%}
    <script src="{{ 'shopify_common.js' | shopify_asset_url }}" defer="defer"></script>
  {%- endif -%}

  <script src="{{ 'theme.js' | asset_url }}" defer="defer"></script>
  <script src="{{ 'lazysizes.js' | asset_url }}" async="async"></script>
  {{ 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js' | script_tag }}
 {{ 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js' | script_tag }}
 {{ 'https://rawgit.com/jquery/jquery-ui/master/ui/i18n/datepicker-ja.js' | script_tag }}

  <script type="text/javascript">
    if (window.MSInputMethodContext && document.documentMode) {
      var scripts = document.getElementsByTagName('script')[0];
      var polyfill = document.createElement("script");
      polyfill.defer = true;
      polyfill.src = "{{ 'ie11CustomProperties.min.js' | asset_url }}";

      scripts.parentNode.insertBefore(polyfill, scripts);
    }
  </script>

  {{ content_for_header }}
{% include 'hulkcode_common' %}{% if template contains 'product' or template contains 'cart' %}<script src='https://ha-product-option.nyc3.digitaloceanspaces.com/assets/api/v2/hulkcode.js' defer='defer'></script>{% endif %}</head>

<body class="template-{{ request.page_type | handle }}">

  <a class="in-page-link visually-hidden skip-link" href="#MainContent">{{ 'general.accessibility.skip_to_content' | t }}</a>

  {%- if settings.enable_ajax -%}
    {% include 'cart-popup' %}
  {%- endif -%}

  {% section 'header' %}

  <div class="page-container drawer-page-content" id="PageContainer">

    <main class="main-content js-focus-hidden" id="MainContent" role="main" tabindex="-1">
      {{ content_for_layout }}
    </main>

  {% section 'footer' %}

    <div id="slideshow-info" class="visually-hidden" aria-hidden="true">
      {{- 'sections.slideshow.navigation_instructions' | t -}}
    </div>

  </div>

  <script type="application/json" data-cart-routes>
    {
      "cartUrl": "{{ routes.cart_url }}",
      "cartAddUrl": "{{ routes.cart_add_url }}",
      "cartChangeUrl": "{{ routes.cart_change_url }}"
    }
  </script>

  <ul hidden>
    <li id="a11y-refresh-page-message">{{ 'general.accessibility.refresh_page' | t }}</li>
    <li id="a11y-selection-message">{{ 'general.accessibility.selection_help' | t }}</li>
  </ul>
{% render 'th-subscription-scripts' %} </body>
</html>

一部むき出しだったcssとJavaScriptは長いので消しましたが、書き出すとこんな感じです。

まさに基本のテンプレートらし<html></html> <head></dead> <body></body>と基本構造の開始タグも閉じタグも全部ここに書いてあります。

この辺もバリバリファイル分けしていくwordpressとはちょっと違います。(やればWordPressと同じこと出来ますが)

headタグの中身

上から行くとやはりheadタグの中身ですよね。当然ここでcssファイルなんかを読み込んでいくわけです。普通のhtmlの書き方でも書けるし、liquidで書くことも出来るので、いろいろと混在してるのがわかる思います。

CSSはちょうど比べやすい書き方がないのでJavaScriptのところをちょっと抜き出してみます。

  <script src="{{ 'theme.js' | asset_url }}" defer="defer"></script>
  <script src="{{ 'lazysizes.js' | asset_url }}" async="async"></script>
  {{ 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js' | script_tag }}
 {{ 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js' | script_tag }}
 {{ 'https://rawgit.com/jquery/jquery-ui/master/ui/i18n/datepicker-ja.js' | script_tag }}

こんな風に5行書き出されていますよね。

ここで注目すべきは<script>で始まっている行と、{{}}で囲まれた行がある事ですよね。実際にはおんなじassetフォルダのjsファイルをよい込む設定になってますがが、asyncとかdeferとかscriptを呼び出す方法を書き足したい時はフィルターで簡単に書く方法だと出来ないので

<script>タグを使って、asset_urlでjsファイルを呼び出している。

と言う構造なのですが「ふーん」って思っておけばとりあえずはいいでしょう。こういう構造でheadがかかれているぞってわかっておくことが今回は大事です。

headの詳しい話はまたいつか書くとして、今回はファイル構造を簡単に学ぶのが目的なので、次に行きます。

bodyタグの中身

headと同列だけど見出し的には下層にしなかったのにはもちろんわけがあります。今回の学習の最大のテーマはこのbodyタグの中身です!

  {% section 'header' %}

  <div class="page-container drawer-page-content" id="PageContainer">

    <main class="main-content js-focus-hidden" id="MainContent" role="main" tabindex="-1">
      {{ content_for_layout }}
    </main>

  {% section 'footer' %}

中でもこの部分だけ見れば、とりあえずいいでしょう。

{% section ‘header’ %}

これが一番最初に書いてありますね。見ての通りsectionsフォルダ内のheader.liquidを呼び出す記述です。これによってページのヘッダーが読み込まれているわけですが、theme.liquid上にこれを書くことによって、全ページ共通のヘッダーを実現しているわけです。

下を見たら書いてあるフッターも同じです。

{% section ‘footer’ %}

これだけ書いてありますね。これでフッターも完了。あぁぁ簡単だぁぁぁっぁ!!

と終われば簡単なんですが肝心の中身っぽい物がたいして見当たりませんね。まぁ、もう残すところあと1つみたいなliquidが書いてあるのでわかると思いますが

{{ content_for_layout }}

と言いう所がページの中身を読み込んでします。要するにここにコンテンツを書きなさいよ!って言う事だけ書いてあって、あとはアクセスしたページによって自動的にtemplateファイルのどれかを読み込みに行くわけです。

さて、場面は少し変わりますが、カスタマイズ画面に移動しましょう。一番上のホームページのところをクリックすると、こんなリストが開きますよね?

(一度に表示しきらないので一部合成しています)

で、これの意味がこれから分かってきます。これなのですが意味合いとしては「どのテンプレートを編集したい?」って言う意味なんですよね。

つまりはこんな感じです。

設定を変更したい、liquidファイルにアクセスして設定を変更できるのがカスタマイズ

と言う意味だと思って頂いて大丈夫です!

ここでカスタマイズ的には大事なポイントなのですが、テンプレートは増やせるんです。

この記事でも解説していますがtemplateフォルダ内に「product.product1.liquid」を使った場合にはproduct.liquidとは区別されます。商品管理から別のテンプレートを選択する事が可能になるので、見た目の表示も変えられるのは上の記事でもわかるかと思います。

面白いのは、たとえばproduct.liquidproduct.product1.liquidがほぼほぼ中身が一緒で、中に書いてある呼び出しsectionが

product-template.liquid
product-template1.liquid

と別のセクションを呼び出していたとしましょう。とはいえ上記2つのsectionファイルの中身のコードは一緒です。ただファイルとしては別ファイルにしてあるだけです。

これが重要です。例えば「くまちゃん」にはproduct.liquidを適用。「くまちゃん2号」にはproduct.product1.liquidを適用していたとしましょう。

そうすると、別々のliquidファイルにアクセス、そして別々のsectionにアクセスする事になるので、カスタマイズ画面から、同じ商品ページでも別々のカスタマイズを加えることが出来ます!

こっちの「くまちゃん」では動的チェックアウトボタンを表示する。つまり「今すぐ購入」を表示する設定にしていますが

「くまちゃん2号」では非表示にしています。そしてこれはページを行き来しても維持されるのが大きなポイント!

テンプレートを、そしてその下のセクションを分けていない場合には商品ページをカスタマイズ画面で編集しても、商品個別に編集されずに同じものが適用されてしまうので、見た目を変えたくても変えられません!

が、テンプレートとその下のセクションを書き換えておけば別々の設定をしても保持されるのです。詳しい事はセクションについて書いたこちらの記事をご覧ください。

これを覚えると便利なのがコレクションページです!コレクションページはcollection.liquidが大本になっているのは上で指示した通りです。

コレクションページも別のテンプレートを当てる事が出来るので、例えばよくあるのが

このコレクションは横4列のレイアウトで商品一覧を作りたい。でもこっちのコレクションは3列がいいんだよなぁぁぁっぁああああ!!!

って時ですね。別にしちゃいえばいいんですよえ。テンプレートを。

テンプレートの階層構造を何となくでも把握しておくと、こういう所の応用が利くようになってきます。

index.liquidだけは超特殊

さて、普通のテンプレートに関しては何となくわかる感じかと思いますが、トップページ用のindex.liquidだけは全然特殊です。

まずもう1度この表を見てみましょう。

index.liquidだけsectionを呼び出していないんですよね。ファイルの中身を見ても

{{ content_for_index }}

これだけ。これで何も端折ってないですからね!たった1行で出来ているウルトラファイルです!だからこそ特殊な動きが出来て(たぶんJavaScriptで全部制御してる)TOPページだけは、sectionその物をガンガン追加する事が出来るんですよね。

ほかのテンプレートではこの便利な便利な「セクションを追加する」が使えない理由がわかったかと思います。

僕は最初にこの構造に気が付いた時にproduct.liquidにこれ貼り付けたら、商品ページも自由にコントロールできるんじゃ?!と思って

 {{ content_for_index }}

これをproduct.liquidにコピペしてみたんですよね・・・そしたらindexページとおんなじ物が商品ページに書き出されたのは懐かしい思い出です。

なので、他のページではこのセクションを追加する方法は使えないと言う事で、あきらめ・・・完全にあきらめる事はありません!いずれ解説しますが、何と疑似的にはトップページと同じようにセクションを追加可能なプログラムの書き方は存在します。いつか書きます。

まとめ

まとめると

全てのページはtheme.liquid経由で呼び出される

カスタマイズ画面はテンプレートファイルを呼び出している

index.liquidだけはめっちゃ特殊

と言った辺りを理解しましょう!っていう話です。テーマの構造を知ると今後のカスタマイズをするときに

  • ここはliquidを編集しなくちゃ辛いな
  • ここはカスタマイズ画面からで足りるな
  • ここはテンプレート1つ増やすだけだな

の3通りぐらいからカスタマイズする方法がすぐにわかるようになります。まずはファイルの構造を知る所から初めて、いろいろ書き込んだりしてみて、触りまくりましょう!