Liquid 模板引擎
Liquid 是 Shopify 开发的安全模板语言,用于动态渲染店铺页面内容
#type / concept
#status / growing
#tech / dev
#resource / liquid
#resource / shopify
[!info] related notes
- 所属 MOC: Shopify MOC
- 应用场景: Shopify 主题
- 问题排查: Liquid 模板常见错误排查
- 相关技术: Shopify Storefront API
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 是”安全”的
设计目标:允许商家和设计师编辑模板,但不能破坏商店或访问敏感数据
安全机制:
-
无法执行任意代码:
{% system('rm -rf /') %} // ❌ 不存在这样的标签 {{ 1 + 1 }} // ✅ 只能输出 "2",不能执行复杂逻辑 -
有限的逻辑能力:
- 只能用
if/else/elsif、for、case/when - 没有函数定义、类、模块等编程语言特性
- 只能用
-
沙箱环境:
- 无法访问文件系统
- 无法发起网络请求(除非通过 Shopify API)
- 无法访问数据库
-
权限控制:
- 只能访问 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
| 维度 | Liquid | JavaScript |
|---|---|---|
| 执行位置 | 服务器端 | 浏览器端(或 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 其他模板引擎
| 模板引擎 | 开发语言 | 主要用途 |
|---|---|---|
| Liquid | Ruby | Shopify、Jekyll 静态网站 |
| Jinja2 | Python | Flask、Django 模板 |
| Handlebars | JavaScript | 前端模板 |
| ERB | Ruby | Rails 视图 |
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 文件自动渲染,商家可以在后台可视化编辑