mcpField
T
theme-shopify-javascript-standards

theme-shopify-javascript-standards

github.com/niccos-shopify-workspace開発253 回インストール38 週間前に更新
リポジトリ

theme-shopify-javascript-standards について

Shopify テーマの JavaScript を記述する開発者向けのスキルです。ファイル構成、カスタム要素、コンポーネントの動作に関する規約をまとめています。

主な機能

  1. 01JavaScript を assets/ に分離
  2. 02defer によるスクリプト読み込み
  3. 03const と let のみを使用
  4. 04カスタム要素によるロジックのカプセル化
  5. 05data-* 属性によるデータ受け渡し
  6. 06disconnectedCallback でイベントを整理

活用シーン

  1. 01Shopify テーマのセクション用スクリプト作成
  2. 02再利用可能なインタラクティブコンポーネントの構築
  3. 03テーマの JavaScript ファイル構成の整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/niccos-shopify-workspace/shopify-cursor-skills --skill theme-shopify-javascript-standards

インストール後、Claude Code で/theme-shopify-javascript-standardsを入力して読み込まれたか確認します。

詳細

Shopify JavaScript 規約

Shopify テーマ開発における JavaScript ファイル構成、カスタム要素、コーディング規約です。

使用する場面

  • Shopify テーマのセクション用に JavaScript を記述する場合
  • インタラクティブなコンポーネントを作成する場合
  • JavaScript ファイルの構成を設定する場合
  • カスタム HTML 要素を使用する場合

ファイル構成

JavaScript ファイルの分離
  • JavaScript は assets/ ディレクトリ内の別ファイルに記述する必要があります
  • asset_url フィルターを使ってセクションに含めます
セクションへの JavaScript の追加
<script src="{{ 'section-logic.js' | asset_url }}" defer="defer"></script>

スクリプトが HTML の解析後に読み込まれるように、必ず defer を使用してください。

ファイル名

JavaScript ファイル名はセクション名に合わせます。

sections/
  └── product-quick-view.liquid

assets/
  └── product-quick-view.js

JavaScript のルール

変数の宣言
  • const と let のみを使用します
  • var は使用しないでください
  • グローバルスコープを汚染しないようにします
例
// Good
const productId = document.querySelector('[data-product-id]').dataset.productId;
let isOpen = false;

// Bad
var productId = ...; // Never use var
window.myVariable = ...; // Avoid global pollution
スコープ管理

変数のスコープは、その変数を使用する範囲に限定します。

// Good - scoped within function
function initProductCard() {
  const card = document.querySelector('.product-card');
  const button = card.querySelector('.product-card__button');
  // ...
}

// Bad - global variables
const card = document.querySelector('.product-card'); // Global scope

カスタム要素

カスタム HTML 要素を使用する

カスタム HTML 要素を使用して JavaScript のロジックをカプセル化し、再利用可能なコンポーネントを作成します。

カスタム要素の構造
class ProductQuickView extends HTMLElement {
  constructor() {
    super();
    this.init();
  }

  init() {
    this.button = this.querySelector('[data-trigger]');
    this.modal = this.querySelector('[data-modal]');
    this.closeButton = this.querySelector('[data-close]');
    
    this.button?.addEventListener('click', () => this.open());
    this.closeButton?.addEventListener('click', () => this.close());
  }

  open() {
    this.modal?.classList.add('is-open');
  }

  close() {
    this.modal?.classList.remove('is-open');
  }
}

customElements.define('product-quick-view', ProductQuickView);
Liquid でのカスタム要素の使用
<product-quick-view data-product-id="{{ product.id }}">
  <button data-trigger>Quick View</button>
  <div data-modal class="modal">
    <button data-close>Close</button>
    
  </div>
</product-quick-view>
カスタム要素のメリット
  • カプセル化 - ロジックが自己完結しています
  • 再利用性 - テーマ内のどこでも使用できます
  • ライフサイクルフック - connectedCallback、disconnectedCallback
  • データ属性 - data-* 属性を使ってデータを渡します
ライフサイクルフック
class MyComponent extends HTMLElement {
  connectedCallback() {
    // Element added to DOM
    this.init();
  }

  disconnectedCallback() {
    // Element removed from DOM
    this.cleanup();
  }

  init() {
    // Setup logic
  }

  cleanup() {
    // Cleanup logic (remove event listeners, etc.)
  }
}

データ属性

JavaScript へのデータの受け渡し
  • 適切な場合はカスタム HTML タグを使用します
  • data-* 属性を使って動的なデータを渡します
  • dataset プロパティを使ってデータにアクセスします
例
<product-card 
  data-product-id="{{ product.id }}"
  data-product-handle="{{ product.handle }}"
  data-variant-id="{{ product.selected_or_first_available_variant.id }}">
  
</product-card>
class ProductCard extends HTMLElement {
  constructor() {
    super();
    this.productId = this.dataset.productId;
    this.productHandle = this.dataset.productHandle;
    this.variantId = this.dataset.variantId;
  }
}

オブザーバー

明示的に要求された場合のみオブザーバーを使用する

オブザーバー(IntersectionObserver、MutationObserver など)は、ユーザーから明示的に要求された場合のみ使用します。

IntersectionObserver の例
// Only use if explicitly needed
class LazyImage extends HTMLElement {
  constructor() {
    super();
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          this.loadImage();
          this.observer.unobserve(this);
        }
      });
    });
  }

  connectedCallback() {
    this.observer.observe(this);
  }

  loadImage() {
    const img = this.querySelector('img');
    img.src = img.dataset.src;
  }
}

ベストプラクティス

イベント処理
class ProductForm extends HTMLElement {
  constructor() {
    super();
    this.form = this.querySelector('form');
    this.form?.addEventListener('submit', this.handleSubmit.bind(this));
  }

  handleSubmit(event) {
    event.preventDefault();
    // Handle form submission
  }

  disconnectedCallback() {
    // Clean up event listeners
    this.form?.removeEventListener('submit', this.handleSubmit);
  }
}
エラー処理
class ProductCard extends HTMLElement {
  init() {
    try {
      const button = this.querySelector('[data-add-to-cart]');
      if (!button) {
        console.warn('Add to cart button not found');
        return;
      }
      button.addEventListener('click', this.handleAddToCart.bind(this));
    } catch (error) {
      console.error('Error initializing product card:', error);
    }
  }
}

Shopify テーマのドキュメント

以下の Shopify 公式リソースを参照してください。

完全な例

セクションファイル
{{ 'product-card.css' | asset_url | stylesheet_tag }}

<product-card 
  data-product-id="{{ product.id }}"
  data-product-handle="{{ product.handle }}">
  <div class="product-card">
    {{ image | image_tag: widths: '360, 720, 1080', loading: 'lazy' }}
    <h3>{{ product.title }}</h3>
    <button data-add-to-cart>Add to Cart</button>
  </div>
</product-card>

<script src="{{ 'product-card.js' | asset_url }}" defer="defer"></script>
JavaScript ファイル
class ProductCard extends HTMLElement {
  constructor() {
    super();
    this.productId = this.dataset.productId;
    this.init();
  }

  init() {
    const button = this.querySelector('[data-add-to-cart]');
    button?.addEventListener('click', () => this.addToCart());
  }

  async addToCart() {
    try {
      const response = await fetch('/cart/add.js', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: this.productId,
          quantity: 1
        })
      });
      // Handle response
    } catch (error) {
      console.error('Error adding to cart:', error);
    }
  }
}

customElements.define('product-card', ProductCard);

指示

  1. JavaScript ファイルを分離する - assets/ ディレクトリにセクションごとに 1 つのファイルを配置します
  2. スクリプトを含める際は defer を使用する
  3. const と let を使用する - var は使用しないでください
  4. ロジックをカプセル化するためにカスタム要素を使用する
  5. data-* 属性を使ってデータを渡す
  6. グローバルスコープの汚染を避ける
  7. 明示的に要求された場合のみオブザーバーを使用する
  8. disconnectedCallback でイベントリスナーをクリーンアップする

コメント