Stitch Shopify
Stitchで生成したモックzipをShopify Online Store 2.0のテーマディレクトリに変換する。
snippets階層をAtomic Design(atoms / molecules / organisms)で構造化し、テキストはsection.settings、画像はimage_pickerに差し替えたカスタマイズ可能なテーマを生成する。
引数
/stitch-shopify [zipファイルパス] [出力先ディレクトリ(省略時: zipと同階層の shopify-theme/)]
前提
- 入力: Stitchの生zip(
stitch/p_XXX/code.html形式、/stitch-deployと同じ構造) - 出力:
shopify theme dev/shopify theme pushでそのまま動作するテーマディレクトリ - CSS: Tailwind CLIで
assets/base.cssを事前コンパイル(CDNは使わない、Shopify公式推奨のasset_url配信) code.htmlのHTML中身は、Liquid化に必要な箇所以外は極力変更しない- ユーザー確認なしに既存ファイルを上書きしない
出力ディレクトリ構造
shopify-theme/
├── assets/
│ ├── base.css # Tailwind CLIで事前コンパイル(CDN不使用)
│ ├── global.js # 共通JS(必要なら)
│ └── mock-*.{png,jpg,svg,webp} # Stitchの画像をimage_pickerのデフォルトとして同梱
├── blocks/ # OS 2.0 theme blocks(section横断で再利用)
│ └── *.liquid
├── config/
│ ├── settings_schema.json
│ └── settings_data.json
├── layout/
│ └── theme.liquid # asset_urlでbase.css読込 + 共通head
├── sections/
│ ├── header.liquid
│ ├── footer.liquid
│ └── main-*.liquid # ページごと(main-index, main-product 等)
├── snippets/
│ ├── atoms/*.liquid # button, heading, input, icon 等
│ ├── molecules/*.liquid # form-field, product-card, search-bar 等
│ └── organisms/*.liquid # hero, product-grid, article-list 等
├── templates/
│ ├── index.json
│ ├── product.json
│ ├── collection.json
│ ├── cart.json
│ ├── page.json
│ ├── blog.json
│ ├── article.json
│ ├── 404.json
│ └── search.json
└── locales/
└── en.default.json
snippetsのサブディレクトリは {% render 'atoms/button' %} のようにパス付きで参照する(Shopify CLI 3系以降が対応)。
ワークフロー
Phase 1: 準備
- 引数からzipパスを取得。未指定なら作業ディレクトリ内の
stitch*.zipを探す - zipを一時ディレクトリに展開
- 出力先ディレクトリの候補を決定(引数 >
zipと同階層/shopify-theme/)
Phase 2: ページ解析とテンプレートマッピング
各 p_XXX/code.html について:
- ページ名推定(
/stitch-deployと同ロジック):<title>→<h1>→<h2>→ ディレクトリサフィックスの順 - Shopify標準テンプレートに自動マッピング(キーワード照合):
- ホーム/トップ/Home →
index - 商品一覧/コレクション/Collection →
collection - 商品詳細/Product →
product - カート/Cart →
cart - 404/見つかりません →
404 - 検索/Search →
search - ブログ/Blog →
blog - 記事/Article →
article - その他(About、問い合わせ等)→
page.{slug}代替テンプレート
- ホーム/トップ/Home →
マッピング表をユーザーに提示し承認を得る:
番号 | Stitch | 推定ページ名 | Shopifyテンプレート
-----|----------|-------------|---------------------
1 | p_001 | ホーム | index
2 | p_002 | 商品一覧 | collection
3 | p_003 | 商品詳細 | product
4 | p_004 | About | page.about
...
ユーザーが修正指示を出したら反映。承認されるまで次に進まない。
欠落テンプレートの確認
Shopify標準9種(index, product, collection, cart, page, blog, article, 404, search)のうちモックに無いものを列挙し、AskUserQuestionで各テンプレートごとに対応を選ばせる:
- 自動雛形生成: 最小構成のsection(タイトル+本文のsettingsのみ)を生成
- Stitchで再生成: 処理を一旦中断し、ユーザーがStitchで追加モックを生成後に再実行
- 今回はスキップ: テンプレートを作らない
「Stitchで再生成」が1つでも選ばれたら、その時点で処理を終え「追加分を含めた新しいzipで再実行してください」と案内する。
Phase 3: Atomic Design分解
全ページのHTMLを横断的にスキャンし、再利用可能な要素を識別する。
判定基準とTailwindクラス署名の早見表は references/atomic-patterns.md を参照(分類に迷ったら必ず読む)。
分類基準:
- atoms — 単一要素、最小単位(button, heading, input, textarea, select, icon, badge, label, image)
- molecules — atomsを2〜3個束ねた機能単位(form-field, search-bar, product-card, nav-item, breadcrumb)
- organisms — moleculesとatomsを組み合わせた独立セクション(site-header, site-footer, hero, product-grid, article-list, cta-section)
抽出手順:
- 全ページを走査し、Tailwindクラスの類似度で同種要素をグループ化
- 2回以上出現するパターンを候補に挙げる(1回きりはsection内にインライン)
- 候補リストをユーザーに提示:
■ atoms 候補
- button-primary (出現: 23回) - bg-blue-600 text-white rounded
- button-secondary (出現: 8回) - border text-blue-600
- heading-section (出現: 12回) - text-2xl font-bold
- input-text (出現: 15回) - border rounded px-3 py-2
...
■ molecules 候補
- product-card (出現: 15回) - 画像+商品名+価格+ボタン
- form-field (出現: 9回) - label+input+error
...
■ organisms 候補
- site-header (全ページ)
- site-footer (全ページ)
- hero (index, collection)
- product-grid (index, collection)
...
ユーザーが粒度を調整(分ける/まとめる/命名変更)。承認されるまで次に進まない。
Phase 4: Shopifyテーマ生成
出力先ディレクトリが存在する場合は上書き確認を取ってから実行。
4-1. layout/theme.liquid
Shopify Dawnを参考にした標準構造を生成:
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="">
<link rel="canonical" href="{{ canonical_url }}">
<link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
{%- if settings.favicon != blank -%}
<link rel="icon" type="image/png" href="{{ settings.favicon | image_url: width: 32, height: 32 }}">
{%- endif -%}
<title>{{ page_title }}{% if current_tags %} – tagged "{{ current_tags | join: ', ' }}"{% endif %}{% if current_page != 1 %} – Page {{ current_page }}{% endif %}{% unless page_title contains shop.name %} – {{ shop.name }}{% endunless %}</title>
{%- if page_description -%}
<meta name="description" content="{{ page_description | escape }}">
{%- endif -%}
{{ 'base.css' | asset_url | stylesheet_tag }}
{{ content_for_header }}
</head>
<body>
{% section 'header' %}
<main id="MainContent" role="main">{{ content_for_layout }}</main>
{% section 'footer' %}
<script src="{{ 'global.js' | asset_url }}" defer></script>
</body>
</html>
{{ content_for_header }}は必ず<head>内に置く(App埋め込み・analytics用)- Stitchの共通
<head>(font等のlink)はpreconnectを残しつつ移植 - 元のCSSファイルリンク(
<link rel="stylesheet">)は削除 - インラインスクリプト・
<script src>は section 内の{% javascript %}またはassets/*.js+script_tagに移す
4-2. snippets/ の生成
承認済みのatoms/molecules/organismsを個別Liquidファイルに:
atoms: propsのみから描画、ロジック無し
{% comment %} atoms/button.liquid params: label (string), variant (primary|secondary), href (string, optional) {% endcomment %} {% assign variant = variant | default: 'primary' %} {% if variant == 'primary' %} {% assign classes = 'bg-blue-600 text-white rounded px-4 py-2' %} {% else %} {% assign classes = 'border text-blue-600 rounded px-4 py-2' %} {% endif %} {% if href %} <a href="{{ href }}" class="{{ classes }}">{{ label }}</a> {% else %} <button class="{{ classes }}">{{ label }}</button> {% endif %}molecules: atomsを
{% render %}で組み立てるorganisms: 通常sectionから呼ばれる想定。sectionのsettings/blocksをプロパティで受け取る
4-3. sections/ の生成
header.liquid,footer.liquid: organisms/site-header, site-footer を呼ぶ薄いラッパー。{% schema %}付き- ページごとに
main-{template}.liquid(main-index.liquid, main-product.liquid 等)
setting type / block / presets の構文は references/shopify-settings.md を参照(schema生成時は必ず確認)。
各sectionに{% schema %}を付与し、以下を変換:
| 元の要素 | 変換後 | setting type |
|---|---|---|
| プレーンテキスト(見出し、段落) | {{ section.settings.xxx }} |
text / textarea |
| リッチテキスト(リンク含む段落) | {{ section.settings.xxx }} |
richtext |
| 画像 | 下記「画像変換テンプレート」参照 | image_picker |
| CTAボタン(テキスト+URL) | setting 2つ(cta_label, cta_url) |
text, url |
| リスト状の繰り返し(特徴、実績、FAQ等) | {% for block in section.blocks %} |
blocks配列で定義 |
schemaのpresetsも1つ入れ、テーマエディタからsection追加できるようにする。
画像変換テンプレート(Stitch画像をデフォルトに)
Stitchの<img src="...">(picsum.photos / placehold.co / 実アセット)は以下のパターンに置換:
{%- assign img = section.settings.hero_image -%}
{%- if img != blank -%}
{{ img | image_url: width: 1600 | image_tag:
loading: 'lazy',
widths: '400, 600, 800, 1200, 1600',
sizes: '(min-width: 750px) 50vw, 100vw',
alt: section.settings.hero_image_alt | default: img.alt }}
{%- else -%}
<img src="{{ 'mock-hero.jpg' | asset_url }}"
alt="{{ section.settings.hero_image_alt | default: '' }}"
loading="lazy" width="1600" height="900">
{%- endif -%}
image_pickerはdefaultを持てない仕様なので、Stitch側の画像(picsum.photos / placehold.co / ダウンロード済アセット)をassets/mock-{section}-{slug}.{ext}として同梱し、{% if settings.image %}...{% else %}{{ 'mock-*.ext' | asset_url }}{% endif %}のフォールバックで表示する- LCP対象(1枚目のhero等)は
loading: 'lazy'を外し、fetchpriority: 'high'とpreloadを検討 width/height属性は元HTMLから取得(CLS対策。picsum.photosの場合はURLのwxhから推定)altは別settingで編集可能にする(画像とペアでimage_altsetting を置く)
section-scoped アセット(Shopify標準)
section固有の動的スタイル・スクリプトは {% stylesheet %} / {% javascript %} で section 内に埋める(DawnのイディオムでTheme Checkも推奨):
{% schema %}{...}{% endschema %}
{% stylesheet %}
/* Tailwindで表現できないCSS変数・動的背景等のみここに書く */
.hero--section-{{ section.id }} { --hero-overlay: {{ section.settings.overlay_color }}; }
{% endstylesheet %}
{% javascript %}
/* section 初期化ロジック。Shopifyが自動でdeferして結合する */
customElements.define('hero-section', class extends HTMLElement { ... });
{% endjavascript %}
- Tailwindユーティリティで表現できるものは
{% stylesheet %}に書かない(重複になる) {% javascript %}はassets/global.jsより優先してsection単位で使う(Shopifyが結合&最適化)
4-4. 商品・コレクションsectionのLiquid化
main-product.liquid / main-collection.liquid のダミーデータ部分は以下に置換:
- 商品リスト →
{% for product in collection.products %} - 商品タイトル →
{{ product.title }} - 商品価格 →
{{ product.price | money }} - 商品画像 →
{{ product.featured_image | image_url: width: 1200 | image_tag: loading: 'lazy', widths: '300, 600, 900, 1200', sizes: '(min-width: 750px) 33vw, 100vw' }} - 商品リンク →
{{ product.url }} - 商品説明 →
{{ product.description }} - 商品フォーム →
{% form 'product', product %}...{% endform %}
メイン商品/コレクション領域以外(お客様の声、特徴紹介等)はsettings/blocks化。
4-5. templates/
Shopify OS 2.0 JSON形式。該当する main-{template} section を参照:
{
"sections": {
"main": { "type": "main-index", "settings": {} }
},
"order": ["main"]
}
Phase 2で決めた代替テンプレート(例: page.about.json)も同様に生成。
4-6. blocks/(theme blocks, OS 2.0)
複数section間で再利用したいorganismは theme block として切り出す(OS 2.0の新機能、Dawn 15+が採用):
blocks/product-card.liquidのようにtop-levelblocks/ディレクトリに配置- section schemaの
blocks配列に{"type": "@theme"}or{"type": "@app"}を含めるとテーマエディタから任意のtheme blockを追加可能 - section内
blocksでしか使わないものは theme block にしない(section local blockとして残す)
{%- comment -%} blocks/product-card.liquid {%- endcomment -%}
<div {{ block.shopify_attributes }}>
{% render 'molecules/product-card', product: block.settings.product %}
</div>
{% schema %}
{
"name": "Product card",
"settings": [
{ "type": "product", "id": "product", "label": "Product" }
]
}
{% endschema %}
Phase 3のorganism選定時、ユーザーに「このorganismをtheme blockにするか」を確認する。
4-7. config/, locales/, assets/
config/settings_schema.json:theme_infoと favicon / color scheme 等のグローバル設定config/settings_data.json:{"current": {}}locales/en.default.json: 画面文言のキーを格納。section内のハードコード文字列は{{ 'general.xxx' | t }}に置換可能な箇所を抽出して収録locales/ja.jsonなど他言語は空の雛形を作成assets/base.css: Phase 4-8でTailwind CLIが生成assets/global.js: Stitchの共通<script>由来。無ければ空ファイルまたは省略assets/mock-*.{ext}: Stitchの画像(screen.png/ picsum.photos / placehold.coのキャプチャ含む)をmock-{p_XXX}-{元ファイル名}でコピー。image_pickerフォールバック用
4-8. Tailwind CSS コンパイル
出力ディレクトリに対して Tailwind CLI を実行し assets/base.css を生成:
# 事前に一時ファイル作成
cat > /tmp/base-input.css <<'CSS'
@tailwind base;
@tailwind components;
@tailwind utilities;
CSS
# コンパイル実行
npx tailwindcss@latest \
-i /tmp/base-input.css \
-o <出力先>/assets/base.css \
--content '<出力先>/**/*.liquid' \
--minify
--contentで生成済Liquidファイルをスキャンし、使用中のクラスだけをバンドル(JIT動作、通常30-80KB)npxが使えない環境では bun / pnpm dlx で代替。利用可能なパッケージマネージャをユーザーに確認- Stitch独自のTailwind設定があれば
tailwind.config.jsを出力先に生成(元HTMLの<script>tailwind.config = {...}</script>から抽出) - コンパイル失敗時はエラーをそのまま表示し、手動で修正する案内を出す
Phase 5: 自己チェック
以下の整合性チェックを行い結果を報告:
- 全sectionに
{% schema %}があるか templates/*.jsonのtypeが実在するsectionを指しているか{% render 'xxx/yyy' %}の参照先snippetが全て存在するか- snippets間の引数名が渡し側と受け側で一致しているか
assets/,config/,layout/,sections/,snippets/,templates/,locales/が全て存在するかasset_urlが参照するファイルがassets/に実在するかassets/base.cssが生成されており空でないか(Tailwind CLIの結果)- モック画像フォールバックを使うsectionで、該当
mock-*ファイルがassets/に存在するか theme.liquidの<head>に{{ content_for_header }}があるか- sectionの
{% stylesheet %}内に Tailwindユーティリティが書かれていないか(CSS重複検出) <script src="https://cdn...">などの外部CDN参照が残っていないかshopify theme checkを実行(インストールされていれば)。ERROR レベルがあればその場で修正
異常があれば修正してから次フェーズへ。
Phase 6: Git commit & push(自動)
出力ディレクトリを含むgitリポジトリを検出して自動でcommit/pushする。
リポジトリ判定: 出力先から
git rev-parse --show-toplevelを実行- gitリポジトリ内の場合: そのリポジトリで commit/push
- gitリポジトリ外の場合: AskUserQuestionで以下を選ばせる
git initして初期コミットを作る(remoteは後で設定)- gitステップをスキップ
ステージング: 出力先ディレクトリのみを対象にする
git add <出力先ディレクトリ>他のワーキングツリーの変更は巻き込まない。
コミットメッセージ:
CLAUDE.mdの規約に従い<type>: <説明>形式- 初回生成:
feat: Stitchモックから生成したShopifyテーマを追加 - 既存上書き:
update: Stitchモックから生成したShopifyテーマを更新 - type は英語、説明は日本語
--no-verify禁止。pre-commit/pre-pushが失敗したら原因を調査・修正してから再実行
- 初回生成:
プッシュ前の確認: 以下を1画面で提示してユーザーに最終確認:
ブランチ: <current-branch> リモート: <remote-url> コミット: <commit-message> 変更ファイル数: <N>承認後に
git pushを実行。リモートが未設定なら push はスキップし案内のみ。現在ブランチがmain/masterの場合は警告: 新規ブランチ作成を推奨し、AskUserQuestionで
- 新規ブランチ
stitch-shopify/<日付>を切って commit/push - そのままmain/masterに commit/push を選ばせる。
- 新規ブランチ
Phase 7: 完了案内
- 出力ディレクトリの
treeを表示 - Phase 5 の自己チェック結果と Phase 6 の git 操作結果をサマリ表示
- 次アクションを案内:
cd <出力先> shopify theme check # 静的検査 shopify theme dev # ローカルプレビュー shopify theme push -u # 未公開テーマとしてアップロード
Liquid 作法(Shopify標準)
Phase 4の全generation phaseで以下を適用:
- whitespace制御: Liquidタグが余分な空白を残さないよう、制御フロー系は
{%- ... -%}を使う{%- if section.settings.image != blank -%} ... {%- endif -%} - マルチライン
{% liquid %}: 3行以上のassign/ifが続くなら{% liquid %}でまとめる{%- liquid assign variant = block.settings.variant | default: 'primary' assign href = block.settings.href if variant == 'primary' assign classes = 'bg-blue-600 text-white rounded px-4 py-2' else assign classes = 'border text-blue-600 rounded px-4 py-2' endif -%} blank/empty判定: 未定義値は!= blankで判定(!= nilではない)defaultフィルター: fallbackは| default:を優先({% if x %}{{ x }}{% else %}...{% endif %}より簡潔)- 翻訳: ハードコード文字列は避け、
{{ 'section.hero.cta_label' | t }}とlocales/en.default.jsonで管理。ただしユーザー編集前提のテキストはsection.settingsを優先(固定ラベルだけをlocalesへ) - schemaの
t:プレフィックス:{% schema %}内のlabel/infoも翻訳可能:"label": "t:sections.hero.settings.title.label" block.shopify_attributes: 各blockのルート要素に必ず{{ block.shopify_attributes }}を出力(テーマエディタのインスペクタ用)- section id: 複数配置される可能性があるので CSS class にも
section.idを含める(hero--{{ section.id }}等) - App blocks対応: 各sectionのblocksに
{"type": "@app"}を入れ、アプリブロック配置を許容 - アクセシビリティ: Stitch出力の
alt/aria-label/roleを保持。画像未設定時はalt=""(装飾画像扱い)
ルール
- ユーザー確認を取る箇所(必須):
- ページ→テンプレートのマッピング表
- 欠落テンプレートの対応(AskUserQuestion)
- Atomic Design分解の粒度
- 出力先ディレクトリが既存の場合の上書き
- git push 直前の最終確認(ブランチ/リモート/コミット)
- main/masterブランチへの直pushを避けるかの確認
- テキストは必ずsettings化する(ハードコードしない)。固定ラベルは
locales/*.json経由 - 画像は必ず
image_picker化する(URLハードコードしない)。Stitchの画像はassets/mock-*.{ext}へコピーし{% if settings.image %}...{% else %}asset_url{% endif %}で参照 - CSSは Tailwind CLI で
assets/base.cssにコンパイル。CDN参照禁止 - section固有の動的CSS/JSは
{% stylesheet %}/{% javascript %}を優先 - atoms同士は相互参照しない(molecules→atoms、organisms→molecules/atomsの一方向のみ)
- 一時展開ディレクトリは処理完了後に削除する
- Stitchで再生成の選択肢が出たら処理を中断し、追加zipで再実行させる
- git操作では
--no-verifyを使わない。pre-commit/pre-pushフックは必ず通す git addは出力先ディレクトリに限定し、他の変更を巻き込まない- リモート未設定なら push はスキップ(エラー停止しない)