theme-shopify-javascript-standards
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseShopify JavaScript Standards
Shopify JavaScript 规范
JavaScript file structure, custom elements, and coding standards for Shopify theme development.
Shopify主题开发中的JavaScript文件结构、自定义元素及编码规范。
When to Use
适用场景
- Writing JavaScript for Shopify theme sections
- Creating interactive components
- Setting up JavaScript file structure
- Using custom HTML elements
- 编写Shopify主题区块的JavaScript代码时
- 创建交互式组件时
- 搭建JavaScript文件结构时
- 使用自定义HTML元素时
File Structure
文件结构
Separate JavaScript Files
独立JavaScript文件
- JavaScript must live in a separate file in the directory
assets/ - Include it in the section using the filter
asset_url
- JavaScript文件必须放在目录下的独立文件中
assets/ - 使用过滤器将其引入区块中
asset_url
Including JavaScript in Sections
在区块中引入JavaScript
liquid
<script src="{{ 'section-logic.js' | asset_url }}" defer="defer"></script>Always use to ensure scripts load after HTML parsing.
deferliquid
<script src="{{ 'section-logic.js' | asset_url }}" defer="defer"></script>务必使用,确保脚本在HTML解析完成后加载。
deferFile Naming
文件命名
Match JavaScript file names to section names:
sections/
└── product-quick-view.liquid
assets/
└── product-quick-view.jsJavaScript文件名需与区块名称匹配:
sections/
└── product-quick-view.liquid
assets/
└── product-quick-view.jsJavaScript Rules
JavaScript规则
Variable Declarations
变量声明
- Use only and
constlet - Never use
var - Avoid global scope pollution
- 仅使用和
constlet - 禁止使用
var - 避免全局作用域污染
Example
示例
javascript
// Good
const productId = document.querySelector('[data-product-id]').dataset.productId;
let isOpen = false;
// Bad
var productId = ...; // Never use var
window.myVariable = ...; // Avoid global pollutionjavascript
// Good
const productId = document.querySelector('[data-product-id]').dataset.productId;
let isOpen = false;
// Bad
var productId = ...; // Never use var
window.myVariable = ...; // Avoid global pollutionScope Management
作用域管理
Keep variables scoped to their usage:
javascript
// 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将变量限定在其使用的作用域内:
javascript
// 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 scopeCustom Elements
自定义元素
Use Custom HTML Elements
使用自定义HTML元素
Use custom HTML elements to encapsulate JavaScript logic and create reusable components.
使用自定义HTML元素封装JavaScript逻辑,创建可复用组件。
Custom Element Structure
自定义元素结构
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);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);Using Custom Elements in Liquid
在Liquid中使用自定义元素
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>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 - ,
connectedCallbackdisconnectedCallback - Data attributes - pass data via attributes
data-*
- 封装性——逻辑独立
- 可复用性——可在主题中任意位置使用
- 生命周期钩子——、
connectedCallbackdisconnectedCallback - 数据属性——通过属性传递数据
data-*
Lifecycle Hooks
生命周期钩子
javascript
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
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
向JavaScript传递数据
- Use custom HTML tags when appropriate
- Pass dynamic data via attributes
data-* - Access data via property
dataset
- 合适时使用自定义HTML标签
- 通过属性传递动态数据
data-* - 通过属性访问数据
dataset
Example
示例
liquid
<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>javascript
class ProductCard extends HTMLElement {
constructor() {
super();
this.productId = this.dataset.productId;
this.productHandle = this.dataset.productHandle;
this.variantId = this.dataset.variantId;
}
}liquid
<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>javascript
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、MutationObserver等)。
IntersectionObserver Example
IntersectionObserver示例
javascript
// 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;
}
}javascript
// 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
事件处理
javascript
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);
}
}javascript
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
错误处理
javascript
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);
}
}
}javascript
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
Shopify主题文档
Reference these official Shopify resources:
参考Shopify官方资源:
Complete Example
完整示例
Section File
区块文件
liquid
{{ '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>liquid
{{ '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
JavaScript文件
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
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 directory
assets/ - Use when including scripts
defer - Use and
const- neverletvar - Use custom elements to encapsulate logic
- Pass data via attributes
data-* - Avoid global scope pollution
- Use observers only when explicitly requested
- Clean up event listeners in
disconnectedCallback
- 独立JS文件——每个区块对应目录下的一个文件
assets/ - **使用**引入脚本
defer - 使用和
const——禁止使用letvar - 使用自定义元素封装逻辑
- 通过属性传递数据
data-* - 避免全局作用域污染
- 仅在明确需要时使用观察者
- 在中清理事件监听器
disconnectedCallback