您是一位精通 Shopify 主题开发的专家,熟悉 Liquid、HTML、CSS、JavaScript 以及最新的 Shopify 在线商店 2.0 特性。
—
描述:使用 Liquid、JavaScript 和 CSS 进行 Shopify 主题开发的最佳实践
文件类型:**/*.liquid, assets/*.js, assets/*.css, sections/*.liquid, snippets/*.liquid, templates/**/*.liquid, blocks/*.liquid
始终应用:true
—
# Liquid 开发指南
## Liquid 规则
### 有效的过滤器
* **购物车**
* `item_count_for_variant`: `cart | item_count_for_variant: {variant_id}`
* `line_items_for`: `cart | line_items_for: object`
* **HTML**
* `class_list`: `settings.layout | class_list`
* `time_tag`: `string | time_tag: string`
* `inline_asset_content`: `asset_name | inline_asset_content`
* `highlight`: `string | highlight: string`
* `link_to`: `string | link_to: string`
* `placeholder_svg_tag`: `string | placeholder_svg_tag`
* `preload_tag`: `string | preload_tag: as: string`
* `script_tag`: `string | script_tag`
* `stylesheet_tag`: `string | stylesheet_tag`
* **集合**
* `link_to_type`: `string | link_to_type`
* `link_to_vendor`: `string | link_to_vendor`
* `sort_by`: `string | sort_by: string`
* `url_for_type`: `string | url_for_type`
* `url_for_vendor`: `string | url_for_vendor`
* `within`: `string | within: collection`
* `highlight_active_tag`: `string | highlight_active_tag`
* **颜色**
* `brightness_difference`: `string | brightness_difference: string`
* `color_brightness`: `string | color_brightness`
* `color_contrast`: `string | color_contrast: string`
* `color_darken`: `string | color_darken: number`
* `color_desaturate`: `string | color_desaturate: number`
* `color_difference`: `string | color_difference: string`
* `color_extract`: `string | color_extract: string`
* `color_lighten`: `string | color_lighten: number`
* `color_mix`: `string | color_mix: string, number`
* `color_modify`: `string | color_modify: string, number`
* `color_saturate`: `string | color_saturate: number`
* `color_to_hex`: `string | color_to_hex`
* `color_to_hsl`: `string | color_to_hsl`
* `color_to_rgb`: `string | color_to_rgb`
* `hex_to_rgba`: `string | hex_to_rgba`
* **字符串**
* `hmac_sha1`: `string | hmac_sha1: string`
* `hmac_sha256`: `string | hmac_sha256: string`
* `md5`: `string | md5`
* `sha1`: `string | sha1: string`
* `sha256`: `string | sha256: string`
* `append`: `string | append: string`
* `base64_decode`: `string | base64_decode`
* `base64_encode`: `string | base64_encode`
* `base64_url_safe_decode`: `string | base64_url_safe_decode`
* `base64_url_safe_encode`: `string | base64_url_safe_encode`
* `capitalize`: `string | capitalize`
* `downcase`: `string | downcase`
* `escape`: `string | escape`
* `escape_once`: `string | escape_once`
* `lstrip`: `string | lstrip`
* `newline_to_br`: `string | newline_to_br`
* `prepend`: `string | prepend: string`
* `remove`: `string | remove: string`
* `remove_first`: `string | remove_first: string`
* `remove_last`: `string | remove_last: string`
* `replace`: `string | replace: string, string`
* `replace_first`: `string | replace_first: string, string`
* `replace_last`: `string | replace_last: string, string`
* `rstrip`: `string | rstrip`
* `slice`: `string | slice`
* `split`: `string | split: string`
* `strip`: `string | strip`
* `strip_html`: `string | strip_html`
* `strip_newlines`: `string | strip_newlines`
* `truncate`: `string | truncate: number`
* `truncatewords`: `string | truncatewords: number`
* `upcase`: `string | upcase`
* `url_decode`: `string | url_decode`
* `url_encode`: `string | url_encode`
* `camelize`: `string | camelize`
* `handleize`: `string | handleize`
* `url_escape`: `string | url_escape`
* `url_param_escape`: `string | url_param_escape`
* `pluralize`: `number | pluralize: string, string`
* **本地化**
* `currency_selector`: `form | currency_selector`
* `translate`: `string | t`
* `format_address`: `address | format_address`
* **客户**
* `customer_login_link`: `string | customer_login_link`
* `customer_logout_link`: `string | customer_logout_link`
* `customer_register_link`: `string | customer_register_link`
* `avatar`: `customer | avatar`
* `login_button`: `shop | login_button`
* **格式**
* `date`: `string | date: string`
* `json`: `variable | json`
* `structured_data`: `variable | structured_data`
* `weight_with_unit`: `number | weight_with_unit`
* **字体**
* `font_face`: `font | font_face`
* `font_modify`: `font | font_modify: string, string`
* `font_url`: `font | font_url`
* **默认**
* `default_errors`: `string | default_errors`
* `default`: `variable | default: variable`
* `default_pagination`: `paginate | default_pagination`
* **支付**
* `payment_button`: `form | payment_button`
* `payment_terms`: `form | payment_terms`
* `payment_type_img_url`: `string | payment_type_img_url`
* `payment_type_svg_tag`: `string | payment_type_svg_tag`
* **数学**
* `abs`: `number | abs`
* `at_least`: `number | at_least`
* `at_most`: `number | at_most`
* `ceil`: `number | ceil`
* `divided_by`: `number | divided_by: number`
* `floor`: `number | floor`
* `minus`: `number | minus: number`
* `modulo`: `number | modulo: number`
* `plus`: `number | plus: number`
* `round`: `number | round`
* `times`: `number | times: number`
* **数组**
* `compact`: `array | compact`
* `concat`: `array | concat: array`
* `find`: `array | find: string, string`
* `find_index`: `array | find_index: string, string`
* `first`: `array | first`
* `has`: `array | has: string, string`
* `join`: `array | join`
* `last`: `array | last`
* `map`: `array | map: string`
* `reject`: `array | reject: string, string`
* `reverse`: `array | reverse`
* `size`: `variable | size`
* `sort`: `array | sort`
* `sort_natural`: `array | sort_natural`
* `sum`: `array | sum`
* `uniq`: `array | uniq`
* `where`: `array | where: string, string`
* **媒体**
* `external_video_tag`: `variable | external_video_tag`
* `external_video_url`: `media | external_video_url: attribute: string`
* `image_tag`: `string | image_tag`
* `media_tag`: `media | media_tag`
* `model_viewer_tag`: `media | model_viewer_tag`
* `video_tag`: `media | video_tag`
* `article_img_url`: `variable | article_img_url`
* `collection_img_url`: `variable | collection_img_url`
* `image_url`: `variable | image_url: width: number, height: number`
* `img_tag`: `string | img_tag`
* `img_url`: `variable | img_url`
* `product_img_url`: `variable | product_img_url`
* **自定义字段**
* `metafield_tag`: `metafield | metafield_tag`
* `metafield_text`: `metafield | metafield_text`
* **货币**
* `money`: `number | money`
* `money_with_currency`: `number | money_with_currency`
* `money_without_currency`: `number | money_without_currency`
* `money_without_trailing_zeros`: `number | money_without_trailing_zeros`
* **标签**
* `link_to_add_tag`: `string | link_to_add_tag`
* `link_to_remove_tag`: `string | link_to_remove_tag`
* `link_to_tag`: `string | link_to_tag`
* **托管文件**
* `asset_img_url`: `string | asset_img_url`
* `asset_url`: `string | asset_url`
* `file_img_url`: `string | file_img_url`
* `file_url`: `string | file_url`
* `global_asset_url`: `string | global_asset_url`
* `shopify_asset_url`: `string | shopify_asset_url`
### 有效的标签
* **主题**
* `content_for`
* `layout`
* `include`
* `render`
* `javascript`
* `section`
* `stylesheet`
* `sections`
* **HTML**
* `form`
* `style`
* **变量**
* `assign`
* `capture`
* `decrement`
* `increment`
* **迭代**
* `break`
* `continue`
* `cycle`
* `for`
* `tablerow`
* `paginate`
* `else`
* **条件**
* `case`
* `if`
* `unless`
* `else`
* **语法**
* `comment`
* `echo`
* `raw`
* `liquid`
### 有效的对象
* `collections`
* `pages`
* `all_products`
* `articles`
* `blogs`
* `cart`
* `closest`
* `content_for_header`
* `customer`
* `images`
* `linklists`
* `localization`
* `metaobjects`
* `request`
* `routes`
* `shop`
* `theme`
* `settings`
* `template`
* `additional_checkout_buttons`
* `all_country_option_tags`
* `canonical_url`
* `content_for_additional_checkout_buttons`
* `content_for_index`
* `content_for_layout`
* `country_option_tags`
* `current_page`
* `handle`
* `page_description`
* `page_image`
* `page_title`
* `powered_by_link`
* `scripts`
### 验证规则
* **语法**
* 使用 `{% liquid %}` 处理多行代码。
* 使用 `{% # comments %}` 处理内联评论。
* 不要自行发明新的过滤器、标签或对象。
* 遵循正确的标签关闭顺序。
* 使用正确的对象点符号。
* 尊重对象范围和可用性。
* **主题结构**
* 将文件放置在适当的目录中。
* 遵循命名约定。
* 尊重模板层级。
* 维护适当的节/块结构。
* 使用适当的模式设置。
## 主题架构
### 文件夹结构
* `sections`: 定义页面可定制部分的 Liquid 文件,包括通过模式定义的块和设置,允许商家在主题编辑器中修改。
* `blocks`: 在节内可配置的元素,可以添加、移除或重新排序。通过模式标签定义,供商家在主题编辑器中自定义。
* `layout`: 定义重复内容的结构,如页眉和页脚,包裹其他模板文件。它是将页面结合在一起的框架,但不是内容。
* `snippets`: 包含可以在模板、节和布局中通过 render 标签引入的可重用代码片段。适用于需要重复使用但不需要直接在主题编辑器中编辑的逻辑。
* `config`: 存储主题自定义选项(如排版和颜色)的设置数据和模式,通过管理员主题编辑器访问。
* `assets`: 包含 CSS、JavaScript 和图像等静态文件。这些资产可以通过 `asset_url` 过滤器在 Liquid 文件中引用。
* `locales`: 存储用于本地化主题编辑器和商店内容的翻译文件。
* `templates`: JSON 文件,指定每种页面类型(如产品、集合、博客)显示哪些节。它们通过布局文件包裹,以确保一致的标题/页脚内容。模板也可以是 Liquid 文件,但以 JSON 为良好实践。
* `templates/customers`: 用于客户相关页面的模板,如登录和帐户概览。
* `templates/metaobject`: 用于呈现定义为自定义内容类型的元对象的模板。
## 用户体验原则
### 翻译
* 确保主题中的每个文本都翻译。
* 用合理的键和值更新语言文件。
* 仅添加英文文本,其他语言由翻译人员处理。
### 设置
#### 一般指导
* 保持简单、明确且不重复。
* 设置类型可以提供上下文,而不需要设置标签提供。例如:“列数”可以简单以“列”表示,如果输入表明这是数字值。
* 假设所有设置都是设备无关的,在断点之间优雅缩放。仅在需要唯一设置时提及移动或桌面。
* 在合适的场合使用通用缩写。例如:最大/最小表示最大和最小。但要确保这些值被正确翻译/本地化。
* 帮助文本:尽量减少使用。如确实需要,简短并避免标点,除非超过1句话(但也不应该!)。
#### 信息架构
* **排序**
* 按照它们所控制的元素的预览顺序列出设置。从上到下,从左到右,从背景到前景。
* 如果需要,首先列出资源选择器,然后是自定义设置。专注于商家需要采取行动以使节/块正常工作的内容。例如:一个推荐集合块需要商家选择一个集合,然后再决定每行的产品数量。
* 按视觉影响的顺序列出设置,例如:每行产品数量应先于产品卡设置。
* **分组**
* 如果有超过1个相关设置,考虑在标题下分组设置。将未分组的设置放在节/块的顶部。
* 常见分组:
* 布局
* 排版
* 颜色
* 内边距
* **命名**
* 删除标题和嵌套标签中的单词重复。当一个词出现在标题中(例如,“颜色”),它不应在嵌套设置标签或帮助文本中重复。信息的层级提供了足够的上下文。
* **条件**
* 当它:
* 通过逐步披露简化商家的决策过程时,使用条件设置
* 避免重复设置
* 避免视觉混乱并减少认知负担
* 条件设置应出现在信息架构中它们最相关的位置。可能在触发设置直接下方,或可能是一个单独的设置组,出现在其他适合商家的地方。
* 条件设置的权衡和考虑:
* 它们隐藏了帮助商家决定如何美化其网站的功能/选项,因此在将哪些概念结合起来时要谨慎。例如,不要将产品卡的“样本显示”设置与“快速购买”设置条件化。虽然它们都与变体选择相关,但它们具有不同的目的。
* 限制条件深度为2级,以避免复杂逻辑(待讨论!)。
* 即使未显示,条件设置的值也会在 Liquid 代码中被评估。保护性编程,永远不要假定主题设置值为零。
* **输入类型**
* **复选框**:将复选框视为开/关开关。避免使用基于动词的标签,例如:使用“语言选择器”,而不是“启用语言选择器”。动词的存在可能会无意间暗示切换的方向。
* **选择**:保持选择选项标签尽可能简短,以便它们可以动态显示为分段控制。
### 服务器端渲染
* 商店前端应优先采用 Liquid 进行服务器端渲染,而非客户端 JavaScript。
* 在使用 JavaScript 渲染页面部分时,应尽可能从服务器获取新 HTML。
#### 乐观用户界面
* 这是服务器端渲染规则的例外。
* “乐观用户界面”指的是在服务器响应到达之前更新用户界面的部分,以提高**感知性能**。
* **标准**
* 决定是否使用乐观用户界面的关键因素:
1. 在服务器响应到达之前,您正在使用 JavaScript 在客户端更新用户界面的**小**部分。
2. API 请求成功的可能性很高。
* 适合使用的案例示例:
* 当过滤集合页面时,可以在用户选择过滤条件时在客户端更新已应用过滤器的列表,例如,“颜色:红色”或“尺寸:中等”。然而,我们并不知道符合过滤条件的产品数量,所以无法更新产品网格或产品计数。
* 当买家尝试将商品添加到购物车时,可以在客户端更新购物车项目数量。假设我们的产品表单的“添加到购物车”按钮已经在检查商品的可用性,我们可以 reasonably 确信商品将被添加到购物车(API 请求成功)。但是,我们并不知道新的购物车总额将是多少,也无法知道行项目的外观,因此在没有等待服务器响应的情况下不能更新购物车抽屉。
## HTML
* 使用语义化 HTML。
* 在适当的情况下使用现代 HTML 特性,例如,使用 `BITO API Error (403): {“status”:1,”response”:”Unauthorized Access”,”created”:”2025-12-21T13:39:46.124429947Z”}