Facet 模板语法

Facet 模板语法

Facet 是 FaceFlow 用于动态渲染的模板语言。

将本页作为主要的语法指南。它涵盖了技术用户在编写 Component 模板、Variable 模板、列表项输出以及 FaceFlow 中其他运行时片段时实际需要的模式。

本页故意详尽。它不仅是快速入门指南,而是官方 FaceFlow 模板创作的主要语法文档。

语法类别

Facet 模板通常结合五类语法:

语法作用典型用途
{field}Component 字段输出直接输出 Component 字段
{{ expr }}转义表达式输出页面数据、上下文对象、过滤器
{{{ expr }}}原始输出受信任的富内容和受控标记
{{#block}}...{{/block}}控制流条件、循环、页面查询、延迟输出
[[variable-name]]可复用片段嵌入可选属性的共享模板片段

最常见的混淆来源是将 Component 字段简写与通用 Facet 表达式混用。请明确区分:

  • 当所属 Component 字段合约支持直接字段输出或字段助手时,使用 {field}
  • 在呈现通用上下文感知表达式时使用 {{ ... }}
  • 当正确的解决方案是重用而不是更多模板逻辑时,使用 [[variable-name]]

快速开始

最小的有用 Facet 心智模型是:

<section class="hero">
  <h1>{{ page.title }}</h1>

  {{#if summary}}
    <p>{{ summary }}</p>
  {{/if}}

  [[sales-contact-badge]]
</section>

这个示例已经展示了三个最重要的构建模块:

  • 值输出
  • 条件结构
  • 可复用片段组合

输出语法

Component 字段输出

在 Component 模板中,直接字段输出通常使用单花括号语法:

<h2>{title}</h2>
<p>{summary}</p>

当 Component 字段合约简单且以字段为导向时使用此语法。

对于支持助手的字段,同一族语法扩展为助手语法:

<img src="{heroImage.webp}" alt="{heroImage.description}">
<div data-form-embed="{contactForm}"></div>
<div data-review-embed="{serviceReview}"></div>

不要假设每个字段都支持助手链式调用。请先检查所属字段的参考文档。

转义表达式输出

对于普通的转义输出使用双花括号:

<h1>{{ page.title }}</h1>
<p>{{ summary | default("No summary available.") }}</p>

这是用于纯文本、标签、URL、动态标量值以及所有应当转义 HTML 的情况的默认选择。

原始输出

当来源可信且应作为 HTML 渲染时使用三花括号:

<div class="prose">
  {{{ body }}}
</div>

适用于:

  • 管理的富文本
  • 受控的 HTML 片段
  • 受信任的编辑内容

不要对任意不可信的字符串使用原始输出。

注释

Facet 支持短注释和长注释:

{{! short comment }}

{{!--
long comment
spanning multiple lines
--}}

注释不会渲染到输出中。

表达式语法

表达式是动态模板输出的基础。它们允许您访问嵌套属性、调用类似方法的转换或查找、应用过滤器以及使用条件内联逻辑。

点路径表示法

使用点表示法访问嵌套属性:

{{ page.title }}
{{ page.parent.title }}
{{ page.parent.parent.name }}
{{ user.language.title }}

在作者自然键入对象访问的地方也接受箭头表示法:

{{ page->parent->title }}
{{#if page->parent->hasChildren}}
  ...
{{/if}}
{{#each page->children as="child"}}
  ...
{{/each}}

在当前模板中使用更清晰的样式,但在同一文件内保持一致。

类方法式调用

某些对象暴露类方法式的转换或查找:

{{ page.image.width(800) }}
{{ page.image.size(600, 400) }}
{{ page.image.size(600, 400).webp.url }}

这些常见于:

  • 图像
  • 集合
  • 页面本地化的 URL
  • 页面感知的查询助手

类方法式调用是 Facet 对象模型的一部分,而不是任意的运行时代码执行。

键值参数

有些助手接受命名参数:

{{ page.field name="summary" }}
{{ page.children selector="limit=5" }}
{{ pages.count selector="template=blog-post" }}

当助手合约明确期待命名参数时使用它们。这有助于保持模板可读并避免位置参数过载。

三元表达式

Facet 支持三元输出逻辑:

{{ user.isLoggedin ? "Welcome back!" : "Please sign in." }}
{{ page.summary ? page.summary : "No summary provided." }}

对简短的输出决策使用三元表达式。如果分支变得具有结构性,优先使用块助手例如 {{#if}}

过滤器链

使用管道运算符转换值:

{{ page.title | upper }}
{{ page.body | striptags | truncate(200, "...") }}
{{ published | date("F j, Y") }}
{{ price | currency("$", 2) }}

过滤器从左到右链式调用。

过滤器参数风格

Facet 同时接受括号风格和冒号风格参数:

{{ page.title | truncate(100, "...") }}
{{ page.title | truncate:100:... }}

两种形式都受支持。在官方模板中更倾向使用括号风格,因为它更清晰且便于审阅。

块助手

块助手提供结构,而不仅仅是标量输出。

Facet 支持这些核心块类型:

目的
{{#if}}条件渲染
{{#each}}遍历现有的数组或集合
{{#pages}}查询一组页面
{{#page}}查询单个页面
{{#languages}}迭代可用语言
{{#deferred}}与缓存兼容的延迟渲染

{{#if}}

使用 {{#if}} 进行条件渲染:

{{#if summary}}
  <p>{{ summary }}</p>
{{/if}}

else

{{#if user.isLoggedin}}
  <p>Hello, {{ user.name }}.</p>
{{else}}
  <p>Please sign in.</p>
{{/if}}

elseif

{{#if page.template == "blog-post"}}
  <span class="badge">Blog</span>
{{elseif page.template == "case-study"}}
  <span class="badge">Case Study</span>
{{else}}
  <span class="badge">Page</span>
{{/if}}

也接受 else if 变体:

{{#if page.numChildren > 10}}
  <p>Many children</p>
{{else if page.numChildren > 0}}
  <p>Some children</p>
{{else}}
  <p>No children</p>
{{/if}}

比较运算符

运算符含义示例
==等于page.template == "blog-post"
!=不等于page.status != 1
>大于page.numChildren > 5
<小于page.numChildren < 3
>=大于等于page.sort >= 0
<=小于等于page.sort <= 10

逻辑运算符

运算符含义示例
&&user.isLoggedin && user.isSuperuser
||page.template == "blog-post" || page.template == "news"
!!user.isGuest

真值规则

Facet 的真值遵循一个面向模板的实用模型:

是否为真?
null
false
""
0
"0"
空数组或空集合
其他一切

如果条件逻辑开始承载业务规则而不是简单的展示规则,请回到对象模型。不要用嵌套的 if 块来掩盖薄弱的内容设计。

{{#each}}

当集合已存在于当前上下文中时使用 {{#each}}

{{#each page.children as="child"}}
  <a href="{{ child.url }}">{{ child.title }}</a>
{{/each}}

使用 this 的简写:

{{#each page.images}}
  <img src="{{ this.url }}" alt="{{ this.description }}">
{{/each}}

else

{{#each page.children as="child"}}
  <div>{{ child.title }}</div>
{{else}}
  <p>No child pages found.</p>
{{/each}}

循环上下文变量

在循环块内部,可用这些值:

变量说明
loop.index以 0 为基的索引
loop.index1以 1 为基的索引
loop.first在第一次迭代时为 true
loop.last在最后一次迭代时为 true
loop.length总项数
loop.even在偶数索引时为 true
loop.odd在奇数索引时为 true
@indexloop.index 的简写别名

示例:

{{#each page.children as="child"}}
  <div class="{{#if loop.odd}}row-alt{{/if}}">
    <span>{{ loop.index1 }}.</span>
    {{ child.title }}
  </div>
{{/each}}

this 与命名别名

{{#each}} 内,this 总是指向当前项。

{{#each page.children}}
  <a href="{{ this.url }}">{{ this.title }}</a>
{{/each}}

如果可读性重要,优先使用显式别名:

{{#each page.children as="child"}}
  <a href="{{ child.url }}">{{ child.title }}</a>
{{/each}}

{{#pages}}

当模板需要查询内容而不是渲染当前作用域中已存在的集合时使用 {{#pages}}

{{#pages selector="template=blog-post, sort=-created, limit=5" as="post"}}
  <article>
    <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
    <time>{{ post.created | date("F j, Y") }}</time>
    <p>{{ post.summary | truncate(200) }}</p>
  </article>
{{else}}
  <p>No posts found.</p>
{{/pages}}

典型参数:

参数描述
selector查询选择器字符串
as循环变量名

对以下场景使用 {{#pages}}

  • 归档
  • 列表页面
  • 最近内容小部件
  • 导航集合
  • 基于查询的内容块

当集合已存在时使用 {{#each}}。当模板必须主动获取集合时使用 {{#pages}}

{{#page}}

当只需查询单个页面时使用 {{#page}}

{{#page path="/about/"}}
  <a href="{{ page.url }}">{{ page.title }}</a>
{{/page}}

支持的查找模式包括:

{{#page id="1024"}}...{{/page}}
{{#page path="/about/"}}...{{/page}}
{{#page name="contact"}}...{{/page}}
{{#page selector="template=homepage"}}...{{/page}}

{{#page}} 块内部,page 在该块持续期间引用被查询的页面。

{{#languages}}

使用 {{#languages}} 构建语言感知的 UI,例如语言切换器:

<nav class="lang-switch">
  {{#languages as="lang"}}
    <a href="{{ lang.url }}" {{#if lang.isCurrent}}class="active"{{/if}}>
      {{ lang.title }}
    </a>
  {{/languages}}
</nav>

带空回退:

{{#languages as="lang"}}
  <a href="{{ lang.url }}">{{ lang.title }}</a>
{{else}}
  {{! single-language site }}
{{/languages}}

常用语言属性包括:

  • lang.id
  • lang.name
  • lang.title
  • lang.isDefault
  • lang.isCurrent
  • lang.url
  • lang.httpUrl
  • lang.locale

{{#deferred}}

当页面缓存负担较重但部分内容依赖于按用户或快速变化的运行时数据时使用 {{#deferred}}

{{#deferred}}
  <span>Welcome, {{ user.name }}!</span>
{{/deferred}}

带自定义骨架尺寸:

{{#deferred skeleton-width="12rem" skeleton-height="2em"}}
  <div class="user-panel">
    <span>{{ user.displayName }}</span>
  </div>
{{/deferred}}

用于:

  • 可识别用户的问候
  • 账户面板
  • 与缓存安全的个性化
  • 其他不应被固定为完整页面缓存 HTML 响应的内容

应有意使用 deferred。不要默认将所有动态内容都包裹起来。

上下文对象

Facet 在每个受支持的渲染面中公开一组根级上下文对象。

常见对象包括:

  • page
  • pages
  • user
  • languages / lang
  • site
  • now
  • attr
  • loop
  • this

在 Component 模板内,Component 字段值也在根级暴露:

<h1>{{ title }}</h1>

{{#if showGallery}}
  <div class="gallery">
    {{#each images as="img"}}
      <img src="{{ img.url }}">
    {{/each}}
  </div>
{{/if}}

深度对象引用文档位于:

图像输出边界

图像语法取决于图像对象来自何处。

Component image 字段助手语法

<img src="{heroImage.width(1200).webp}" alt="{heroImage.description}">

页面或查询图像对象语法

{{#each page.images as="img"}}
  <img src="{{ img.width(800).webp.url }}" alt="{{ img.description }}">
{{/each}}

Component images 字段循环语法

{{#each gallery}}
  <img src="{{this.width(1200).webp}}" alt="{{this.description}}">
{{/each}}

不要随意混用这些形式。所属字段或运行时对象决定哪种语法是正确的。

地图助手输出

地图样式字段可以暴露面向助手的输出:

{officeLocation}
{officeLocation.width(600)}
{officeLocation.height(400)}
{officeLocation.size(800,500)}

仅当字段合约明确记录时才使用此类语法。

变量嵌入

变量使用双中括号语法嵌入:

[[site-announcement]]
[[cta-badge label="New" tone="accent"]]

在 Variable 模板内部,传入的值可通过 attr 访问。

变量用于可复用片段,而不是替代适当的对象设计。如果片段扩展成一个区段合约,请回到 Component 设计。

托管嵌入标记

表单和评论通常通过托管嵌入标记渲染。

固定模型嵌入:

<div data-form-embed="enterprise-demo-request"></div>
<div data-review-embed="customer-success-reviews"></div>

在可复用 Component 内的字段支撑嵌入:

<div data-form-embed="{contactForm}"></div>
<div data-review-embed="{serviceReview}"></div>

决策规则:

  • 当模板拥有一个稳定的托管工作流时使用固定嵌入
  • 当作者在创作时应选择托管模型时使用字段支撑嵌入

使用指南

  • 保持模板逻辑可读
  • 偏好强健的字段合约而非巧妙的模板技巧
  • 对于结构性问题使用 {{#if}},不要用它来掩盖薄弱的内容设计
  • 当集合已存在时使用 {{#each}}
  • 当模板必须查询集合时使用 {{#pages}}
  • 对于小的可复用片段使用 Variables
  • 当模板逻辑变得过于复杂时,回到 Component 或 List 设计

常见错误

避免以下模式:

  • 在转义输出更安全时使用原始输出
  • 在不了解差异时混用 Component 字段简写和通用 Facet 表达式
  • 仅通过条件语句解决内容模型问题
  • 构建一个巨大的嵌套模板而不是理清对象边界
  • 在模板间重复相同片段而不提取为 Variable
  • 在不支持的字段上使用字段助手语法

快速决策指南

need to show one value                 -> {field} or {{ expr }}
need trusted HTML output               -> {{{ expr }}}
need to show content conditionally     -> {{#if}}
need to loop existing items            -> {{#each}}
need to query content                  -> {{#pages}} or {{#page}}
need language-aware navigation         -> {{#languages}}
need cache-safe dynamic personalization -> {{#deferred}}
need one reusable fragment             -> [[variable-name]]
need a field-specific helper output    -> {field.helper}
need a full reusable section contract  -> return to Component design

继续阅读