关于 twig-templating
面向 Drupal 主题开发者的 Twig 模板参考,涵盖模板命名、建议机制、过滤器和预处理函数。指南强调将逻辑放在预处理函数中,并遵循资源附加、转义和 BEM 约定。
主要功能
- 01Twig 模板命名与结构
- 02常用过滤器和 Drupal 函数
- 03模板建议与预处理函数
- 04BEM 组件样式约定
- 05资源附加与安全实践
适用场景
- 01创建 Drupal 内容类型模板
- 02为主题组件组织 CSS 和 JS
- 03将模板逻辑移至预处理函数
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gkastanis/ drupal-workflow --skill twig-templating安装后在 Claude Code 中输入/twig-templating确认已加载。
详细介绍
Twig 模板
始终使用 {{ attach_library() }} 加载资源——绝不内联 CSS/JS。自定义类始终使用 BEM 命名。避免在模板中编写复杂逻辑——优先使用预处理函数。绝不要对用户输入使用 |raw。
模板命名约定
Drupal 使用基于建议的模板发现机制:
node.html.twig # Base node template.
node--article.html.twig # Article content type.
node--article--teaser.html.twig # Article teaser view mode.
node--42.html.twig # Specific node by ID.
block.html.twig # Base block template.
block--system-branding-block.html.twig # Specific block.
field.html.twig # Base field template.
field--field-image.html.twig # Specific field.
field--node--field-image--article.html.twig # Field + bundle.
page.html.twig # Base page template.
page--front.html.twig # Front page.
page--node--42.html.twig # Specific node page.
模板结构
{# node--article.html.twig #}
{% set classes = [
'node',
'node--' ~ node.bundle,
node.isPromoted() ? 'node--promoted',
node.isSticky() ? 'node--sticky',
view_mode ? 'node--' ~ view_mode,
] %}
<article{{ attributes.addClass(classes) }}>
{% if label %}
<h2{{ title_attributes }}>
<a href="{{ url }}" rel="bookmark">{{ label }}</a>
</h2>
{% endif %}
<div{{ content_attributes.addClass('node__content') }}>
{{ content }}
</div>
</article>
常用过滤器
| 过滤器 | 用途 | 示例 |
|---|---|---|
| ` | t` | 翻译字符串 |
| ` | raw` | 跳过自动转义(注意) |
| ` | escape` | 显式转义 |
| ` | clean_class` | 转换为 CSS 安全类名 |
| ` | clean_id` | 转换为 HTML 安全 ID |
| ` | date` | 格式化日期 |
| ` | length` | 数组/字符串长度 |
| ` | without` | 不渲染指定字段 |
| ` | safe_join` | 使用分隔符连接数组 |
Drupal 专用函数
{# Attach library assets. #}
{{ attach_library('my_theme/component') }}
{# Link to a route. #}
{{ path('entity.node.canonical', {'node': nid}) }}
{# Create a link. #}
{{ link('Click here', url) }}
{# Translate with context. #}
{{ 'Submit'|t({}, {'context': 'my_module'}) }}
{# Access Drupal URL. #}
{{ url('<front>') }}
{# File URL from URI. #}
{{ file_url(node.field_image.entity.fileuri) }}
预处理函数
/**
* Implements hook_preprocess_HOOK() for node templates.
*/
function my_theme_preprocess_node(array &$variables): void {
$node = $variables['node'];
// Add custom variables.
$variables['reading_time'] = ceil(str_word_count(strip_tags($node->body->value)) / 200);
// Add conditional classes.
if ($node->isPublished()) {
$variables['attributes']['class'][] = 'node--published';
}
}
模板建议
/**
* Implements hook_theme_suggestions_HOOK_alter() for node templates.
*/
function my_theme_theme_suggestions_node_alter(array &$suggestions, array $variables): void {
$node = $variables['elements']['#node'];
// Add suggestion based on custom field value.
if ($node->hasField('field_layout') && !$node->get('field_layout')->isEmpty()) {
$layout = $node->get('field_layout')->value;
$suggestions[] = 'node__' . $node->bundle() . '__' . $layout;
}
}
BEM 组件模式
{# components/card.html.twig #}
{% set card_classes = [
'card',
variant ? 'card--' ~ variant,
size ? 'card--' ~ size,
] %}
<div{{ attributes.addClass(card_classes) }}>
{% if image %}
<div class="card__image">
{{ image }}
</div>
{% endif %}
<div class="card__content">
{% if title %}
<h3 class="card__title">{{ title }}</h3>
{% endif %}
{% if body %}
<div class="card__body">{{ body }}</div>
{% endif %}
</div>
{% if actions %}
<div class="card__actions">{{ actions }}</div>
{% endif %}
</div>
模板最佳实践
- 尽量减少 Twig 中的逻辑——复杂逻辑应放在预处理函数中。
- 使用 BEM 命名约定(
block__element--modifier)。 - 始终使用
{{ attach_library() }}加载组件资源。 - 所有可翻译字符串都使用
|t过滤器。 - 模板只接收数据——不要在 Twig 中获取数据。
- 在生产环境配置中禁用 Twig 调试。
- 使用
{{ content|without('field_name') }}排除特定字段。
评论
支持 Markdown