关于 theme-shopify-javascript-standards
面向 Shopify 主题开发者的 JavaScript 规范 Skill,涵盖文件组织、组件封装和编码约定,适用于编写主题交互逻辑。
主要功能
- 01JavaScript 文件独立存放于 assets/
- 02脚本使用 defer 加载
- 03仅使用 const 和 let
- 04通过自定义元素封装逻辑
- 05使用 data-* 属性传递数据
- 06在 disconnectedCallback 清理事件
适用场景
- 01为 Shopify 主题区块编写脚本
- 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>
始终使用 defer,确保脚本在 HTML 解析完成后加载。
文件命名
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);
说明
- 使用独立的 JS 文件 -
assets/目录中每个分区对应一个文件 - 引入脚本时使用
defer - 使用
const和let- 绝不使用var - 使用自定义元素封装逻辑
- 通过
data-*属性传递数据 - 避免污染全局作用域
- 仅在明确要求时使用观察器
- 在
disconnectedCallback中清理事件监听器
评论
支持 Markdown