mcpField
T
theme-shopify-javascript-standards

theme-shopify-javascript-standards

github.com/niccos-shopify-workspace开发253 次安装38 周前更新
代码仓库

关于 theme-shopify-javascript-standards

面向 Shopify 主题开发者的 JavaScript 规范 Skill,涵盖文件组织、组件封装和编码约定,适用于编写主题交互逻辑。

主要功能

  1. 01JavaScript 文件独立存放于 assets/
  2. 02脚本使用 defer 加载
  3. 03仅使用 const 和 let
  4. 04通过自定义元素封装逻辑
  5. 05使用 data-* 属性传递数据
  6. 06在 disconnectedCallback 清理事件

适用场景

  1. 01为 Shopify 主题区块编写脚本
  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>

始终使用 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);

说明

  1. 使用独立的 JS 文件 - assets/ 目录中每个分区对应一个文件
  2. 引入脚本时使用 defer
  3. 使用 const 和 let - 绝不使用 var
  4. 使用自定义元素封装逻辑
  5. 通过 data-* 属性传递数据
  6. 避免污染全局作用域
  7. 仅在明确要求时使用观察器
  8. 在 disconnectedCallback 中清理事件监听器

评论

支持 Markdown