Liquid 模板引擎

Liquid 是 Shopify 开发的安全模板语言,用于动态渲染店铺页面内容

#type / concept #status / growing #tech / dev #resource / liquid #resource / shopify

[!info] related notes

Liquid 模板引擎

一句话定义

Liquid 是 Shopify 于 2006 年开发的安全模板语言,允许在静态 HTML 模板中动态插入数据,同时确保用户无法执行危险代码。

核心机制 / 工作原理

1. 模板语言的核心概念

问题:如何在一个 HTML 模板中显示不同产品的信息?

传统方式(PHP):

<h1><?php echo $product->title; ?></h1>

Liquid 方式

<h1>{{ product.title }}</h1>

Liquid 的本质:将数据(如产品信息)与模板(HTML 结构)分离,在渲染时动态组合。

2. Liquid 的三种语法

(1)输出标签 {{ }}

用于显示数据:

{{ product.title }}           // 输出产品标题
{{ product.price | money }}   // 输出价格,格式化为货币
{{ 'Hello' | upcase }}        // 输出 "HELLO"

(2)逻辑标签 {% %}

用于控制流程:

{% if product.available %}
  <button>Add to Cart</button>
{% else %}
  <p>Sold Out</p>
{% endif %}
{% for product in collection.products %}
  <div>{{ product.title }}</div>
{% endfor %}

(3)注释 {# #}{% comment %}

{# 这是注释,不会在页面显示 #}

{% comment %}
  这也是注释
  可以跨多行
{% endcomment %}

3. 过滤器(Filters)

过滤器用于修改输出的数据:

{{ product.price | money }}                    // 格式化为货币
{{ product.title | upcase }}                   // 转为大写
{{ product.description | strip_html | truncate: 150 }}  // 去除 HTML 并截断
{{ 'image.jpg' | asset_url | img_tag }}        // 生成图片标签

链式调用:从左到右依次执行

{{ product.title | downcase | remove: ' ' | append: '.html' }}
// "Blue T-Shirt" → "bluet-shirt.html"

4. 对象(Objects)

Liquid 通过对象访问 Shopify 数据:

{{ product.title }}          // 产品对象
{{ shop.name }}              // 商店对象
{{ customer.email }}         // 客户对象
{{ cart.item_count }}        // 购物车对象

对象的嵌套属性

{{ product.featured_image.src }}          // 产品的特色图片 URL
{{ product.variants[0].price }}           // 第一个变体的价格
{{ collection.products | size }}          // 集合中的产品数量

5. 为什么 Liquid 是”安全”的

设计目标:允许商家和设计师编辑模板,但不能破坏商店或访问敏感数据

安全机制

  1. 无法执行任意代码

    {% system('rm -rf /') %}  // ❌ 不存在这样的标签
    {{ 1 + 1 }}               // ✅ 只能输出 "2",不能执行复杂逻辑
  2. 有限的逻辑能力

    • 只能用 if/else/elsifforcase/when
    • 没有函数定义、类、模块等编程语言特性
  3. 沙箱环境

    • 无法访问文件系统
    • 无法发起网络请求(除非通过 Shopify API)
    • 无法访问数据库
  4. 权限控制

    • 只能访问 Shopify 提供的对象(product、shop、cart 等)
    • 无法访问其他商家的数据

设计权衡

  • ✅ 安全性高,适合非开发者使用
  • ⚠️ 表达能力有限,复杂逻辑需要在应用层处理

最小例子 / 最小场景

场景:显示产品列表

<div class="product-grid">
  {% for product in collection.products %}
    <div class="product-card">
      <img src="{{ product.featured_image | img_url: 'medium' }}" 
           alt="{{ product.title }}">
      <h3>{{ product.title }}</h3>
      <p class="price">{{ product.price | money }}</p>
      
      {% if product.available %}
        <button>Add to Cart</button>
      {% else %}
        <span class="sold-out">Sold Out</span>
      {% endif %}
    </div>
  {% endfor %}
</div>

渲染结果(假设有 2 个产品):

<div class="product-grid">
  <div class="product-card">
    <img src="https://cdn.shopify.com/.../blue-tshirt.jpg" alt="Blue T-Shirt">
    <h3>Blue T-Shirt</h3>
    <p class="price">$25.00</p>
    <button>Add to Cart</button>
  </div>
  
  <div class="product-card">
    <img src="https://cdn.shopify.com/.../red-hat.jpg" alt="Red Hat">
    <h3>Red Hat</h3>
    <p class="price">$15.00</p>
    <span class="sold-out">Sold Out</span>
  </div>
</div>

场景:条件渲染

{% if customer %}
  <p>Welcome back, {{ customer.first_name }}!</p>
  <a href="/account/logout">Logout</a>
{% else %}
  <a href="/account/login">Login</a>
  <a href="/account/register">Sign Up</a>
{% endif %}

场景:分页

{% paginate collection.products by 12 %}
  {% for product in paginate.collection %}
    <!-- 显示产品 -->
  {% endfor %}
  
  {{ paginate | default_pagination }}
{% endpaginate %}

边界与易混淆点

1. Liquid ≠ 完整编程语言

Liquid 不能做

  • 定义函数或变量(只能用 assign 赋值简单变量)
  • 调用外部 API(需要通过 Shopify App)
  • 操作数据库(数据通过 Shopify 对象提供)
  • 复杂的数学运算(只支持基本算术)

适用场景:前端展示逻辑,不适合复杂业务逻辑

2. Liquid vs JavaScript

维度LiquidJavaScript
执行位置服务器端浏览器端(或 Node.js)
渲染时机页面加载前页面加载后
数据访问Shopify 对象(product、shop 等)需要通过 API 获取
安全性沙箱环境,高安全可执行任意代码
适用场景初始页面渲染用户交互、动态更新

实践建议

  • 用 Liquid 渲染初始 HTML(SEO 友好)
  • 用 JavaScript 处理用户交互(添加购物车、过滤产品)

3. Liquid 变量作用域

{% assign my_var = 'Hello' %}
{{ my_var }}  // 输出 "Hello"

{% for product in collection.products %}
  {% assign my_var = product.title %}
  {{ my_var }}  // 输出当前产品标题
{% endfor %}

{{ my_var }}  // ⚠️ 输出最后一个产品标题(变量在循环外仍可访问)

注意:Liquid 的变量作用域是整个模板文件,不是块级作用域

4. 常见过滤器陷阱

{{ product.price | money }}           // ✅ 正确:$25.00
{{ product.price }}                   // ❌ 输出:2500(单位是分)
{{ product.title | capitalize }}      // ✅ 只首字母大写
{{ product.title | upcase }}          // ✅ 全部大写
{{ product.title | downcase }}        // ✅ 全部小写

5. Liquid vs 其他模板引擎

模板引擎开发语言主要用途
LiquidRubyShopify、Jekyll 静态网站
Jinja2PythonFlask、Django 模板
HandlebarsJavaScript前端模板
ERBRubyRails 视图

Liquid 的特点

  • 语法简洁,易学
  • 安全性高(沙箱环境)
  • 社区成熟(Shopify 生态)

Liquid 在 Shopify 主题中的角色

文件结构

my-theme/
├── layout/
│   └── theme.liquid        // 主布局文件
├── templates/
│   ├── index.liquid        // 首页模板
│   ├── product.liquid      // 产品页模板
│   └── collection.liquid   // 集合页模板
├── sections/
│   ├── header.liquid       // 头部区块
│   └── footer.liquid       // 底部区块
├── snippets/
│   └── product-card.liquid // 可复用的代码片段
└── assets/
    ├── theme.css
    └── theme.js

Liquid 文件的组合

<!-- layout/theme.liquid -->
<!DOCTYPE html>
<html>
<head>
  <title>{{ page_title }}</title>
  {{ 'theme.css' | asset_url | stylesheet_tag }}
</head>
<body>
  {% section 'header' %}
  {{ content_for_layout }}  // 插入具体页面内容
  {% section 'footer' %}
  {{ 'theme.js' | asset_url | script_tag }}
</body>
</html>
<!-- templates/product.liquid -->
<div class="product-page">
  <h1>{{ product.title }}</h1>
  <img src="{{ product.featured_image | img_url: 'large' }}">
  <div>{{ product.description }}</div>
  <p>{{ product.price | money }}</p>
  
  {% render 'product-form', product: product %}
</div>

Liquid 的演进

Shopify 1.0(2006-2021)

  • 基础 Liquid 标签和过滤器
  • 主题结构较简单

Shopify 2.0(2021-至今)

  • JSON 模板:分离数据和展示
  • Sections 和 Blocks:可视化编辑
  • 更强大的对象:Metafield、Metaobject
  • 性能优化:懒加载、图片优化

示例:Shopify 2.0 的 JSON 模板

// templates/product.json
{
  "sections": {
    "main": {
      "type": "product-template",
      "settings": {
        "show_reviews": true
      }
    }
  }
}

对应的 Liquid 文件自动渲染,商家可以在后台可视化编辑

学习资源

创建于 2026/6/15 更新于 2026/6/15