shopify-javascript-standards
1
总安装量
1
周安装量
#44242
全站排名
安装命令
npx skills add https://github.com/skomskiy/shopify-cursor-skills --skill shopify-javascript-standards
Agent 安装分布
amp
1
opencode
1
kimi-cli
1
github-copilot
1
gemini-cli
1
Skill 文档
Shopify JavaScript Standards
JavaScript file structure, custom elements, and coding standards for Shopify theme development.
When to Use
- Writing JavaScript for Shopify theme sections
- Creating interactive components
- Setting up JavaScript file structure
- Using custom HTML elements
File Structure
Separate JavaScript Files
- JavaScript must live in a separate file in the
assets/directory - Include it in the section using the
asset_urlfilter
Including JavaScript in Sections
<script src="{{ 'section-logic.js' | asset_url }}" defer="defer"></script>
Always use defer to ensure scripts load after HTML parsing.
File Naming
Match JavaScript file names to section names:
sections/
âââ product-quick-view.liquid
assets/
âââ product-quick-view.js
JavaScript Rules
Variable Declarations
- Use only
constandlet - Never use
var - Avoid global scope pollution
Example
// Good
const productId = document.querySelector('[data-product-id]').dataset.productId;
let isOpen = false;
// Bad
var productId = ...; // Never use var
window.myVariable = ...; // Avoid global pollution
Scope Management
Keep variables scoped to their usage:
// 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
Custom Elements
Use Custom HTML Elements
Use custom HTML elements to encapsulate JavaScript logic and create reusable components.
Custom Element Structure
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);
Using Custom Elements in 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>
<!-- Modal content -->
</div>
</product-quick-view>
Custom Element Benefits
- Encapsulation – logic is self-contained
- Reusability – use anywhere in the theme
- Lifecycle hooks –
connectedCallback,disconnectedCallback - Data attributes – pass data via
data-*attributes
Lifecycle Hooks
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.)
}
}
Data Attributes
Passing Data to JavaScript
- Use custom HTML tags when appropriate
- Pass dynamic data via
data-*attributes - Access data via
datasetproperty
Example
<product-card
data-product-id="{{ product.id }}"
data-product-handle="{{ product.handle }}"
data-variant-id="{{ product.selected_or_first_available_variant.id }}">
<!-- Content -->
</product-card>
class ProductCard extends HTMLElement {
constructor() {
super();
this.productId = this.dataset.productId;
this.productHandle = this.dataset.productHandle;
this.variantId = this.dataset.variantId;
}
}
Observers
Use Observers Only When Explicitly Requested
Use observers (IntersectionObserver, MutationObserver, etc.) only if explicitly requested by the user.
IntersectionObserver Example
// 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;
}
}
Best Practices
Event Handling
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);
}
}
Error Handling
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 Theme Documentation
Reference these official Shopify resources:
Complete Example
Section File
{{ '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 File
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);
Instructions
- Separate JS files – one file per section in
assets/directory - Use
deferwhen including scripts - Use
constandlet– nevervar - Use custom elements to encapsulate logic
- Pass data via
data-*attributes - Avoid global scope pollution
- Use observers only when explicitly requested
- Clean up event listeners in
disconnectedCallback