ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Zola 模板系统完全指南:Tera 模板引擎、内置过滤器与函数详解

2026/9/13 23:41:29 拓冰建站 浏览量
Zola 模板系统完全指南:Tera 模板引擎、内置过滤器与函数详解 Zola 模板系统完全指南Tera 模板引擎、内置过滤器与函数详解【免费下载链接】zolaA fast static site generator in a single binary with everything built-in. https://www.getzola.org项目地址: https://gitcode.com/GitHub_Trending/zo/zola本文是 Zola 静态站点生成器模板系统的完整技术指南面向需要使用templates目录编写页面、为内容定制输出 HTML 的开发者。Zola 以 Tera 模板引擎为核心与 Jinja2、Liquid、Twig 语法高度相似通过全局变量、内置过滤器与函数为模板开发提供了开箱即用的能力。读完本文你将掌握模板目录结构、页面/区块/首页三种标准模板的使用、自定义模板与内置模板覆盖机制以及get_page、get_url、load_data、get_hash等全部内置函数与过滤器的参数细节和底层实现原理。模板引擎与目录约定Zola 使用 Tera 模板引擎其语法与 Jinja2、Liquid 和 Twig 非常相似。当前 Zola 版本0.22 及之前使用 Tera v1本文仅介绍模板在 Zola 中如何工作Tera 自身的模板语法变量插值{{ }}、标签{% %}、注释{# #}、宏、继承等需要另行参阅 Tera 文档。所有模板都存放在站点的templates目录下。从源码看模板加载逻辑位于 components/templates/src/lib.rs 的load_tera函数Zola 会按主题模板 → 站点模板的优先级顺序加载站点templates目录中的同名模板会覆盖主题模板。如果你不确定某个模板中到底有哪些变量可用可以在模板中放置{% raw %}{{ __tera_context }}{% endraw %}Zola 会打印出当前模板的完整上下文所有可用变量及其值这是调试模板最直接的手段。全局模板变量除 Feedsfeed 模板和 sitemap 之外所有模板都可以使用以下全局变量变量含义config语言感知的 站点配置即zola.toml中的内容current_path当前页面的路径不含base_url的完整 URL始终以/开头current_url当前页面的完整 URLlang当前页面的语言配置变量通过config.variable访问例如在 HTML 中写{% raw %}{{ config.base_url }}{% endraw %}。需要注意两点404 模板是例外由于请求的页面并不存在404 模板拿不到current_path和current_url这两个变量的值无法确定。config.mode除上述config属性外模板还额外获得config.mode用于标识当前 Zola 的运行模式取值是build、serve或check之一。模板可以根据运行模式做差异化渲染例如仅在serve模式下输出调试信息。标准模板index.html / section.html / page.html默认情况下Zola 会寻找三个标准模板模板作用index.html应用于站点首页section.html应用于所有 section在content目录中创建任意目录生成的 HTML 页面page.html应用于所有 page在content目录中创建.md文件生成的 HTML 页面关键点首页始终是一个 section无论它是否包含其他页面。因此index.html和section.html都能访问 section 变量而page.html只能访问 page 变量。page 与 section 变量的详细说明见 Pages Sections 文档。从仓库的测试站点 test_site/templates 可以看到一套实际的模板布局index.html首页、section.html、page.html以及用于分页场景的section_paginated.html、index_paginated.html还有page_template.html、page_template_override.html等用于验证页面自定义模板机制的文件——这些恰好对应下文的自定义模板能力。内置模板与覆盖机制Zola 自带四个内置模板atom.xml与rss.xml详见 Feedssitemap.xml详见 Sitemaprobots.txt详见 Robots.txt从 components/templates/src/lib.rs 的BUILTIN_TEMPLATES常量可以看到完整的注册列表除上述四个外还有split_sitemap_index.xmlsitemap 拆分索引、anchor-link.html标题锚点、summary-cutoff.html摘要截断标记以及一个用户不可覆盖的内部模板internal/alias.html用于 URL 别名重定向。这些模板通过include_str!直接编译进二进制源码位于 components/templates/src/builtins。覆盖机制非常简单在正确的路径下创建同名模板即可覆盖内置模板或主题模板。例如创建templates/atom.xml文件即可覆盖内置的 Atom 模板。主题也可以自带模板在站点未提供同名模板时生效——这正是load_tera中 fallback 前缀theme __zola_builtins的实现逻辑见 components/templates/src/lib.rs。自定义模板除三个标准模板外你还可以在templates目录下创建任意.html文件作为自定义模板。自定义模板默认不会被使用只有两种方式会触发它在页面 front matter 中通过template变量显式指定在其他被应用的模板中通过{% include %}引入它。例如为站点 About 页面创建about.html自定义模板后在about.md的 front matter 中这样引用 title About Us template about.html 自定义模板不必放在templates目录根部子目录同样有效例如product_pages/with_pictures.html就是一个合法的模板路径。仓库中 test_site/content/applying_page_template 目录正是这套机制的测试用例override.md、from-section-config.md以及another_section、yet_another_section子目录配合 test_site/templates/page_template.html、page_template_child.html、page_template_override.html分别验证了页面级templatefront matter、section 级page_template配置以及覆盖优先级。内置过滤器Zola 在 Tera 自带的过滤器之外额外提供了一批过滤器。它们统一在 components/templates/src/lib.rs 中注册其中base64_encode、base64_decode、regex_replace等来自 tera-contribnum_format与markdown是 Zola 自研实现。markdown将给定变量用 Markdown 转换为 HTML。默认会把所有文本包裹在p段落中如果想去掉这个行为传入inlinetrue{% raw -%} {{ some_text | markdown(inlinetrue) }} {%- endraw %}你不需要对page.content或section.content使用该过滤器——页面与区块的内容在渲染时已经被处理过了。从实现看components/templates/src/filters/markdown.rsMarkdownFilter会从 Tera 状态中提取当前的 page/section 上下文相对路径、permalink、语言然后调用markdown::render_content走与正文完全相同的渲染管线因此内部链接、colocated 资源等 Markdown 特性在该过滤器中同样生效。inlinetrue的实现是去掉首尾的p与/p\n标记。仓库中的单元测试验证了标题渲染、行内代码、表格、内部链接等场景见同一文件 测试段。base64_encode将变量编码为 base64。base64_decode将变量从 base64 解码。regex_replace通过正则表达式替换文本{% raw -%} {{ World Hello | regex_replace(pattern(?Psubject\w), (?Pgreeting\w), rep$greeting $subject) }} !-- Hello World -- {%- endraw %}该过滤器基于 tera-contrib 的RegexReplace实现支持命名捕获组。num_format将数字格式化为字符串表示{% raw -%} {{ 1000000 | num_format }} !-- 1,000,000 -- {%- endraw %}默认使用zola.toml中config.default_language设置的 locale 进行格式化。若要为特定 locale 格式化使用locale参数传入目标 locale 名称{% raw -%} {{ 1000000 | num_format(localeen-IN) }} !-- 10,00,000 -- {%- endraw %}从源码看components/templates/src/filters/num_format.rs该过滤器基于num-formatcratelocale参数优先于构造时的default_language且无效的 locale 名会直接报错。其单元测试覆盖了en、en-IN、fr三种 locale 的分组规则差异例如法语环境下百万会显示为1 000 000窄不换行空格分隔。文件搜索逻辑File Searching Logic对于除get_page和get_section之外所有需要在磁盘上搜索文件的函数如load_data、get_hash、get_image_metadata、get_url的静态文件路径等遵循以下搜索逻辑基准目录是 Zola 根目录zola.toml所在的目录若路径以/开头将其替换为content/并去掉开头的/按以下顺序搜索返回第一个存在的文件$base_directory$path$base_directorystatic/$path$base_directorycontent/$path$base_directory$output_path$path$base_directorythemes$themestatic/$path仅在使用主题时实际上这意味着/some/image.jpg、/content/some/image.jpg和content/some/image.jpg指向的是同一个文件。如果路径越出了 Zola 目录会直接报错。底层实现位于 components/templates/src/helpers.rs 的search_for_file函数先构造static、content、输出目录以及主题static四个候选目录将/前缀替换为content/并把开头的/去掉后依次探测文件是否存在同时通过is_path_in_directory做越界检查确保无法读取 Zola 目录之外的文件load_data的单元测试cannot_load_outside_base_dir验证了这一点。内置函数Zola 在 Tera 内置函数之外为开发复杂站点注册了一批函数。它们全部注册在 components/templates/src/lib.rs。get_page接收一个.md文件的路径返回关联的 page 对象。基准路径是content目录{% raw -%} {% set page get_page(pathblog/page2.md) %} {%- endraw %}如果页面有特定语言版本可以传入lang参数指定语言代码{% raw -%} {% set page get_page(pathblog/page2.md, langfr) %} {# 如果 fr 是默认语言上面等价于 #} {% set page get_page(pathblog/page2.md) %} {%- endraw %}如果希望忽略不存在的页面而不是抛错例如你想自行处理指向草稿页面的链接传入allow_missingtrue{% raw -%} {% set linked_page get_page(pathblog/path-to-draft.md, allow_missingtrue) %} {% if linked_page %} a href{{get_url(path/blog/page-in-draft.md)}} {{linked_page.title}} /a {% else %} span/span {% endif %} {%- endraw %}从源码看components/templates/src/functions/content.rsGetPage从渲染缓存RenderCache中查找页面先按路径找到 canonical 页面再按语言解析具体翻译版本语言解析优先级是lang参数 模板上下文的lang变量 默认语言allow_missingtrue时找不到会返回None而不是报错。其单元测试完整覆盖了语言上下文、显式lang覆盖、默认语言、路径不存在、语言翻译缺失等场景测试段。get_section接收一个_index.md文件的路径返回关联的 section 对象。基准路径是content目录{% raw -%} {% set section get_section(pathblog/_index.md) %} {%- endraw %}如果只需要 section 的元数据传入metadata_onlytrue{% raw -%} {% set section get_section(pathblog/_index.md, metadata_onlytrue) %} {%- endraw %}与get_page一样支持lang参数{% raw -%} {% set section get_section(pathblog/_index.md, langfr) %} {# 如果 fr 是默认语言上面等价于 #} {% set section get_section(pathblog/_index.md) %} {%- endraw %}同样支持allow_missingtrue忽略不存在的 section{% raw -%} {% set linked_section get_section(pathblog/path-to-draft.md, allow_missingtrue) %} {% if linked_section %} a href{{get_url(path/blog/section-in-draft.md)}} {{linked_section.title}} /a {% else %} span/span {% endif %} {%- endraw %}其实现components/templates/src/functions/content.rs与GetPage完全对称从RenderCache.sections缓存中按路径与语言解析。get_taxonomy_url获取指定分类法taxonomy条目的 permalink{% raw -%} {% set url get_taxonomy_url(kindcategories, termpage.taxonomies.category, langpage.lang) %} {%- endraw %}term通常来自变量手动传入时值应与 front matter 中的原始值一致而不是 slug 化后的版本lang可选默认取zola.toml中的config.default_languagerequired可选如果分类法已定义但没有内容使用它则抛错默认true从实现看components/templates/src/functions/taxonomy.rsterm会经过与分类法一致的 slug 化策略处理后去缓存中匹配 permalink旧参数名name仍然可用但会打印弃用警告。get_taxonomy获取某个 kind 的整个分类法{% raw -%} {% set categories get_taxonomy(kindcategories) %} {%- endraw %}输出类型为kind: TaxonomyConfig; items: ArrayTaxonomyTerm; lang: String; permalink: String;lang可选默认取config.default_languagerequired可选如果分类法已定义但没有内容使用它则抛错默认true这些类型的完整说明见 Taxonomies 文档。get_taxonomy_term获取某个 kind 分类法中的单个条目{% raw -%} {% set categories get_taxonomy_term(kindcategories, termterm_name) %} {%- endraw %}输出类型为单个TaxonomyTerm对象。lang可选默认取config.default_languageinclude_pages可选默认true设为false时TaxonomyTerm中的pages项将为空无论该条目实际有哪些页面但page_count在两种情况下都会正确反映条目下的页面数required可选分类法或条目未找到时的行为控制get_url获取给定路径的 permalink。如果路径以/开头它会被当作 内部链接 处理——即指向content根目录下的一个 Markdown 文件或 colocated 资源并会经过校验{% raw -%} {% set url get_url(path/blog/_index.md) %} {% set asset get_url(path/blog/my-article/graph.png, langfr) %} {%- endraw %}它接受可选的lang参数用于在多语言站点中计算语言感知的 URL。假设config.base_url为http://example.com下面的代码片段会如果config.default_language是en返回http://example.com/blog/如果config.default_language不是en且en出现在config.languages中返回http://example.com/en/blog/否则失败错误信息为en is not an authorized language (check config.languages).{% raw -%} {% set url get_url(path/blog/_index.md, langen) %} {%- endraw %}get_url也可以获取静态文件的 permalink例如链接到static/css/app.css{% raw -%} {{ get_url(pathcss/app.css) }} {%- endraw %}默认链接不带尾部斜杠传入trailing_slashtrue可以强制加上{% raw -%} {{ get_url(pathcss/app.css, trailing_slashtrue) }} {%- endraw %}对于非内部链接还可以通过cachebusttrue在 URL 末尾追加?hsha256格式的缓存破坏参数此时路径必须能解析到真实文件详见上文的 文件搜索逻辑。从实现看components/templates/src/functions/files.rs内部链接通过resolve_internal_link在 permalink 表中解析colocated 资源会先定位其所属页面/区块的语言版本再拼接资源的相对路径单元测试can_get_colocated_asset_url展示了法语/英语 slug 不同的情况非内部链接则直接基于base_url构造。cachebust的实现是读取文件内容计算 SHA-256 并截取前 20 个十六进制字符约 2^-80 的碰撞概率见 files.rs。get_hash返回文件或字符串字面量的哈希摘要SHA-256、SHA-384 或 SHA-512参数如下path必填之一文件路径遵循 文件搜索逻辑或literal必填之一要哈希的字符串值sha_type可选256、384或512之一默认384base64可选true或false默认true是否将哈希编码为 base64path与literal必须二选一给出{% raw -%} {{ get_hash(literalHello World, sha_type256) }} {{ get_hash(pathstatic/js/app.js, sha_type256) }} {%- endraw %}将base64设为true时函数输出 base64 编码的哈希值可用于实现子资源完整性Subresource Integrity, SRI{% raw -%} script src{{ get_url(pathstatic/js/app.js) }} integritysha384-{{ get_hash(pathstatic/js/app.js, sha_type384, base64true) | safe }}/script {%- endraw %}需要注意SRI 通常用于外部脚本而get_hash不支持外部 URL只能处理本地文件或字面量。源码实现见 components/templates/src/functions/files.rs三种 SHA 系列与 base64/hex 输出组合均有测试覆盖。get_image_metadata获取图片的元数据支持 JPEG、PNG、WebP、BMP、GIF 以及 SVG 等常见格式。参数path必填遵循 文件搜索逻辑allow_missing可选true或false默认false缺失文件是否抛错返回一个包含width、height、format、mime、description和created的 map。format返回的是该文件格式最常见的扩展名可能与图片实际使用的扩展名不一致created是图片的创建时间对于照片而言即拍摄时间{% raw -%} {% set meta get_image_metadata(path...) %} Our image (.{{meta.format}}) has format is {{ meta.width }}x{{ meta.height }} {%- endraw %}load_data从文件、URL 或字符串字面量加载数据。支持toml、json、csv、bibtex、yaml/yml和xml文件类型仅支持 UTF-8 编码其他文件类型一律按纯文本加载。path参数指定本地数据文件路径遵循 文件搜索逻辑{% raw -%} {% set data load_data(pathcontent/blog/story/data.toml) %} {%- endraw %}也可以使用url参数指定远程 URL{% raw -%} {% set data load_data(urlhttps://en.wikipedia.org/wiki/Commune_of_Paris) %} {%- endraw %}还可以使用literal参数指定字符串字面量。注意如果不指定format参数字面量会被当作纯文本{% raw -%} {% set data load_data(literal{name: bob}, formatjson) %} {{ data[name] }} {%- endraw %}注意required参数与literal参数组合使用时无效。可选的required布尔参数设为false时数据缺失HTTP 错误或本地文件不存在不会报错而是返回 null 值。但本地文件的权限问题、以及数据无法按请求的格式解析即使requiredfalse仍然会报错。下面的片段输出 Wikipedia 页面的 HTML若页面不可达或未返回成功 HTTP 状态码则输出 No data found{% raw -%} {% set data load_data(urlhttps://en.wikipedia.org/wiki/Commune_of_Paris, requiredfalse) %} {% if data %}{{ data | safe }}{% else %}No data found{% endif %} {%- endraw %}可选的format参数允许指定或覆盖文件/URL 中的数据格式合法取值是toml、json、csv、bibtex、yaml、xml或plain。若未指定format则使用路径扩展名推断对字面量而言未指定时默认plain{% raw -%} {% set data load_data(pathcontent/blog/story/data.txt, formatjson) %} {%- endraw %}当文件扩展名受支持但你想按纯文本加载时使用plain格式。对toml、json、yaml和xml数据被加载为与原始文件匹配的结构csv没有原生结构概念因此被拆分为包含headers和records的数据结构。以下示例展示其工作方式。模板中{% raw -%} {% set data load_data(pathcontent/blog/story/data.csv) %} {%- endraw %}content/blog/story/data.csv文件内容Number, Title 1,Gutenberg 2,Printing解析后存入模板data变量中的等价 json 值{ headers: [Number, Title], records: [ [1, Gutenberg], [2, Printing] ], }bibtex格式加载为与 nom-bibtex crate 结构一致的数据。以下是一个 bibtex 格式数据示例preamble{A bibtex preamble # this is.} Comment{ Here is a comment. } Another comment! string(name Vincent Prouillet) string(github https://github.com/getzola/zola) misc {my_citation_key, author name, title Zola, note github: # github } }其产生的 bibtex 数据结构的 json 等价格式如下{ preambles: [A bibtex preamble this is.], comments: [Here is a comment., Another comment!], variables: { name: Vincent Prouillet, github: https://github.com/getzola/zola }, bibliographies: [ { entry_type: misc, citation_key: my_citation_key, tags: { author: Vincent Prouillet, title: Zola, note: github: https://github.com/getzola/zola } } ] }最后可以在模板中这样访问 bibtex 数据{% raw -%} {% set tags data.bibliographies[0].tags %} This was generated using {{ tags.title }}, authored by {{ tags.author }}. {%- endraw %}从源码看components/templates/src/functions/load_data.rsLoadData支持path/url/literal三选一同时给出多个会报错format解析支持上述全部格式各格式的解析函数load_json、load_yaml、load_toml、load_csv、load_bibtex、load_xml在 同一文件 中实现TOML 日期还会经过fix_toml_dates规范化。远程内容除本地文件外可通过url参数而非path加载远程数据{% raw -%} {% set response load_data(urlhttps://api.github.com/repos/getzola/zola) %} {{ response }} {%- endraw %}默认响应体不做解析直接返回。通过format参数可改变这一行为{% raw -%} {% set response load_data(urlhttps://api.github.com/repos/getzola/zola, formatjson) %} {{ response }} {%- endraw %}未指定其他参数时URL 始终以 HTTP GET 请求获取。自 0.14.0 起可以使用method参数选择以 POST 请求获取使用methodPOST时还可以使用body与content_type参数body是 POST 请求发送的实际内容content_type是 body 的 mimetype。下面这个示例向 kroki 服务发送 POST 请求生成 SVG{% raw -%} {% set postdata load_data(urlhttps://kroki.io/blockdiag/svg, formatplain, methodPOST ,content_typetext/plain, bodyblockdiag { Doing POST - using load_data using load_data - can generate - block diagrams; using load_data - is - very easy!; Doing POST [color greenyellow]; block diagrams [color pink]; very easy! [color orange]; })%} {{postdata|safe}} {%- endraw %}如果需要对 HTTP 头做额外处理可以使用headers参数。当资源需要认证或需要通过特殊头传递额外参数时可能需要该参数。注意这些头会被追加到 Zola 自身设置的默认头之后而不是替换它们。下面这个示例向 GitHub Markdown 渲染服务发送 POST 请求{% raw -%} {% set postdata load_data(urlhttps://api.github.com/markdown, formatplain, methodPOST, content_typeapplication/json, headers[acceptapplication/vnd.github.v3json], body{text:headers support added in #1710, commit before it: b3918f124d13ec1bedad4860c15a060dd3751368,context:getzola/zola,mode:gfm})%} {{postdata|safe}} {%- endraw %}下面的示例演示向 GitHub 发送 GraphQL 查询需要认证。要亲自运行该示例需要提供 GitHub PAT个人访问令牌并将获取的令牌设置到GITHUB_TOKEN环境变量中{% raw -%} {% set token get_env(nameGITHUB_TOKEN) %} {% set postdata load_data(urlhttps://api.github.com/graphql, formatjson, methodPOST ,content_typeapplication/json, headers[acceptapplication/vnd.github.v4.idl, authorizationBearer ~ token], body{query:query { viewer { login }}})%} {{postdata|safe}} {%- endraw %}如果需要指定多个同名头可以这样写headers[acceptapplication/json,text/html]这等价于两个Accept头application/json与text/html。如果这样不行也可以多次指定相同的头名达到类似效果headers[acceptapplication/json, accepttext/html]从实现看远程请求通过reqwest阻塞客户端发出load_data.rsmethod仅接受POST或GET其他值直接报错有单元测试覆盖headers参数按keyvalue拆分并追加到请求头同时 Zola 会按format自动设置Accept头User-Agent 固定为zola/版本号。数据缓存构建期间数据文件加载与远程请求都会在内存中缓存因此不会对同一端点发起多次请求。URL 基于 URL 本身缓存数据文件基于文件修改时间缓存。缓存时也会把格式纳入考量——同一个源以两种不同格式加载时会发起两次请求。底层实现见 load_data.rs 与 load_data.rs缓存键由数据源URL 本身 / 路径修改时间 / 字面量、格式、方法、body、content_type 与 headers 共同哈希得到result_cache以HashMapu64, Value形式存储相关测试覆盖了不同文件名、不同格式、不同 headers 产生不同缓存键的行为。text_direction获取default_language、指定lang或当前活动语言的水平文本方向{% raw -%} {{ text_direction() }} {{ text_direction(langfr) }} {{ text_direction(langlang) }} {%- endraw %}返回字符串字面量ltr或rtl可直接传给 HTML 的dir全局属性。trans获取给定key的翻译作用于default_language、指定lang或当前活动语言{% raw -%} {{ trans(keytitle) }} {{ trans(keytitle, langfr) }} {{ trans(keytitle, langlang) }} {%- endraw %}resize_image调整图片文件的尺寸。完整文档参见Content / Image Processing其底层处理逻辑位于 components/imageproc/src/processor.rs包括缩放、裁剪、格式转换与生成响应式图片集等能力。实战建议与常见陷阱调试优先用__tera_context不确定模板里有什么变量时先输出{% raw %}{{ __tera_context }}{% endraw %}比反复猜测字段名高效得多。区分 page 与 section 变量index.html和section.html拥有 section 变量page.html拥有 page 变量跨类型访问会导致渲染错误。内部链接统一使用/前缀get_url(path/...)与load_data、get_hash等函数的/写法都遵循同一套解析规则且会被校验是否存在相比手写/content/...更可靠。allow_missing/requiredfalse的边界它们只吞掉文件/页面/URL 不存在类错误权限问题、解析失败等仍然会报错这是刻意设计便于在 CI 中尽早暴露问题。远程数据会被缓存同一个 URL 在一次构建内只请求一次不同format会触发不同缓存条目构建期的load_data请求发生在zola build时zola serve模式下文件修改触发重建时会按文件修改时间重新加载本地数据。小结Zola 的模板系统以 Tera 为引擎在保留 Jinja2 风格语法的基础上通过全局变量、内置过滤器、内置函数三大类能力把页面/区块/首页渲染、多语言 URL、静态资源链接、数据加载、哈希与 SRI、图片处理等高频需求全部纳入模板层。理解本文介绍的目录约定、三类标准模板、覆盖机制以及 components/templates 模块中每个过滤器/函数的参数语义你就能为任意 Zola 站点编写出结构清晰、可维护的模板代码。各内置模板的源码Atom/RSS/sitemap/robots与测试站点 test_site/templates 中的实际用例都是继续深入学习的最佳参考。【免费下载链接】zolaA fast static site generator in a single binary with everything built-in. https://www.getzola.org项目地址: https://gitcode.com/GitHub_Trending/zo/zola创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考