theme-shopify-javascript-standards について
Shopify テーマの JavaScript を記述する開発者向けのスキルです。ファイル構成、カスタム要素、コンポーネントの動作に関する規約をまとめています。
主な機能
- 01JavaScript を assets/ に分離
- 02defer によるスクリプト読み込み
- 03const と let のみを使用
- 04カスタム要素によるロジックのカプセル化
- 05data-* 属性によるデータ受け渡し
- 06disconnectedCallback でイベントを整理
活用シーン
- 01Shopify テーマのセクション用スクリプト作成
- 02再利用可能なインタラクティブコンポーネントの構築
- 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);
指示
- JavaScript ファイルを分離する -
assets/ディレクトリにセクションごとに 1 つのファイルを配置します - スクリプトを含める際は
deferを使用する constとletを使用する -varは使用しないでください- ロジックをカプセル化するためにカスタム要素を使用する
data-*属性を使ってデータを渡す- グローバルスコープの汚染を避ける
- 明示的に要求された場合のみオブザーバーを使用する
disconnectedCallbackでイベントリスナーをクリーンアップする
コメント
Markdown 対応