首页
/ Hugo 模板开发指南:Store 数据结构的五种作用域与 hstore 源码解析

Hugo 模板开发指南:Store 数据结构的五种作用域与 hstore 源码解析

2026-09-04 10:31:15作者:侯霆垣

在 Hugo 模板中处理带状态的构建逻辑(跨 partial、跨 shortcode 传递中间结果、在渲染钩子中累积计数等)时,Store 是最核心的键值存储机制。本文基于 Hugo 文档中的 Store Scope 章节展开,系统讲解 page、site、global、local、shortcode 五种作用域的创建方式与适用场景,并结合 common/hstore 包源码说明其线程安全实现、"server 重建不重置"的生命周期特性,以及父模板中获取 shortcode 写入值的确定性取值技巧。

作用域总览

决定 Store 数据结构作用域的,是创建它所用的方法或函数。例如,在 Page 对象上调用 Store 方法,创建的就是页面作用域的数据结构。Hugo 提供的五种作用域与创建方式对应关系如下:

作用域 创建方法/函数 生命周期
page(页面) PAGE.Store 当前页面内,server 重建时保留
site(站点) SITE.Store 当前站点内,所有页面共享,server 重建时保留
global(全局) hugo.Store 全局共享,server 重建时保留
local(局部) collections.NewScratch 仅当前模板执行上下文
shortcode(短代码) SHORTCODE.Store 当前 shortcode 实例内

各作用域的详细文档位于 methods/page/Storemethods/site/Storefunctions/hugo/Storefunctions/collections/NewScratchmethods/shortcode/Store。其中 SITE.StoreSHORTCODE.Storehugo.Store 均为 0.139.0 版本引入的新能力。

五种作用域的用法示例

local:局部作用域(newScratch)

collections.NewScratch(模板中写作 newScratch,可作函数或方法使用)创建完全私有的临时存储,适合在单个模板内部做局部状态累积:

{{ $s := newScratch }}
{{ $s.Set "greeting" "Hello" }}
{{ $s.Get "greeting" }} → Hello

模板函数注册见 tpl/collections/init.gonewScratch 的方法映射;其底层实现非常直接——tpl/collections/collections.goNewScratch 只是委托给 hstore.NewScratch() 返回一个空的结构。

page:页面作用域(PAGE.Store)

{{ .Store.Set "greeting" "Hello" }}
{{ .Store.Get "greeting" }} → Hello

hugolib/page__new.go 中可以看到页面 Store 采用 sync.OnceValue 懒初始化(源码注释标注 "Rarely used",说明绝大多数页面并不会触发该分配),hugolib/page__common.go 提供模板可调用的 Store() 入口。页面作用域的典型用途是:在 render hook(_markup 渲染钩子)中写计数值,然后在页面布局的其他位置读取。

site:站点作用域(SITE.Store)

{{ site.Store.Set "greeting" "Hello" }}
{{ site.Store.Get "greeting" }} → Hello

站点作用域在整个站点的构建过程中共享,适合跨页面统计(例如累计所有页面的某种属性)。

global:全局作用域(hugo.Store)

{{ hugo.Store.Set "greeting" "Hello" }}
{{ hugo.Store.Get "greeting" }} → Hello

全局作用域的生命周期最长。注意一个重要的生命周期特性:从 common/hstore/scratch.goStoreProvider 接口注释可以看到,Store 返回的 Scratch 在 server 模式的重建(rebuild)过程中不会被重置。这意味着在 hugo server 开发时,页面/站点/全局 Store 中写入的状态在文件修改触发的增量重建之间得以保留,可用于跨重建缓存临时状态;而 newScratch 创建的局部作用域则随着每次模板执行结束而消失。

shortcode:短代码作用域(SHORTCODE.Store)

{{ .Store.Set "greeting" "Hello" }}
{{ .Store.Get "greeting" }} → Hello

hugolib/shortcode.goShortcodeWithPage.Store() 同样按需创建 Scratch 实例。需要指出的是:随着 newScratch 函数的引入,以及模板变量初始化后也可以赋值的能力,shortcode 内的 Store 方法已基本过时——官方文档明确建议在新代码中优先使用 newScratch

Store 数据结构的方法

五种作用域创建出的数据结构共用同一套方法接口(SetGetAddSetInMapDeleteInMapGetSortedMapValuesDelete,局部作用域额外提供 Values)。核心示例:

Set:设置给定键的值。

{{ .Store.Set "greeting" "Hello" }}

Getany):读取给定键的值。

{{ .Store.Set "greeting" "Hello" }}
{{ .Store.Get "greeting" }} → Hello

Add:将给定值累加到已有值上。对单值,Add 接受支持 Go + 运算符的值;若某键第一次 Add 的是数组或切片,后续 Add 都会追加到该列表中。

{{ .Store.Set "greeting" "Hello" }}
{{ .Store.Add "greeting" "Welcome" }}
{{ .Store.Get "greeting" }} → HelloWelcome
{{ .Store.Set "total" 3 }}
{{ .Store.Add "total" 7 }}
{{ .Store.Get "total" }} → 10
{{ .Store.Set "greetings" (slice "Hello") }}
{{ .Store.Add "greetings" (slice "Welcome" "Cheers") }}
{{ .Store.Get "greetings" }} → [Hello Welcome Cheers]

SetInMap:接收 keymapKeyvalue 三个参数,在 key 下的 map 中写入 mapKey → value

{{ .Store.SetInMap "greetings" "english" "Hello" }}
{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
{{ .Store.Get "greetings" }} → map[english:Hello french:Bonjour]

DeleteInMap:从 key 下的 map 中移除 mapKey

{{ .Store.SetInMap "greetings" "english" "Hello" }}
{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
{{ .Store.DeleteInMap "greetings" "english" }}
{{ .Store.Get "greetings" }} → map[french:Bonjour]

GetSortedMapValues[]any):返回 key 下按 mapKey 排序后的值数组。

{{ .Store.SetInMap "greetings" "english" "Hello" }}
{{ .Store.SetInMap "greetings" "french" "Bonjour" }}
{{ .Store.GetSortedMapValues "greetings" }} → [Hello Bonjour]

Delete:删除给定键。

{{ .Store.Set "greeting" "Hello" }}
{{ .Store.Delete "greeting" }}

Valuesmap,仅局部作用域安全):返回底层原始 map。文档特别警告:不要Page 对象上的 Store 使用该方法,否则会导致并发问题。

源码实现:Scratch 结构与并发安全

Store 的核心实现在 common/hstore/scratch.goScratch 的结构极其精简:

// Scratch is a writable context used for stateful build operations
type Scratch struct {
	values map[string]any
	mu     sync.RWMutex
}

几个源码层面的要点:

  • 读写锁保护:所有 Set/Add/Delete/SetInMap/DeleteInMap 通过 mu.Lock() 获取写锁,Get/Values/GetSortedMapValues 通过 mu.RLock() 获取读锁。Hugo 构建是并行的(页面按 goroutine 分片渲染),这把 RWMutex 正是页面级、站点级 Store 在并发渲染下安全共享的前提,也解释了为什么 Values 返回裸 map 时只对局部作用域安全——页面/站点 Store 的裸 map 一旦被模板持有,就绕过了锁保护。
  • Add 的类型分派Addreflect 检查已有值的类型,若是 Slice/Arraycollections.Append 追加,否则走 common/math 包的 DoArithmetic 执行 + 运算。这与文档中"支持数字和字符串累加"的行为完全对应。
  • GetSortedMapValues 的排序实现:将 map 的键收集为切片后 sort.Strings 排序,再按序回填为数组——这就是模板中观察到"按 mapKey 排序"的由来。
  • 模板兼容性返回值Set/Add/Delete 等方法统一返回空字符串,源码注释写明"have to return something to make it work with the Go templates"——因为 Go template 对多返回值函数的处理方式要求这些方法至少返回一个值,模板表达式 {{ .Store.Set "k" "v" }} 才不会输出报错。

StoreProvider 接口看,任何实现 Store() *Scratch 的对象都能提供这种临时状态容器,页面(hugolib/page__common.go)、shortcode(hugolib/shortcode.go)、站点(hugolib/site.go)乃至 pagesfromdata 的模板上下文(hugolib/pagesfromdata/pagesfromgotmpl.go)都实现了它,这也印证了"谁调用 Store 方法,谁就决定作用域"的设计。

确定性取值:父模板读取 shortcode 写入的值

Store 方法最常被用在 shortcode 模板、由 shortcode 调用的 partial 模板,或 render hook 模板中。这三种场景下,存储的值在 Hugo 渲染完页面内容之前都是不确定的(indeterminate)——因为内容渲染发生在布局模板的后续阶段。

如果父模板需要在内容渲染前访问这些值,可以先把某个渲染结果的返回值赋给一个 noop 变量,强制触发内容渲染:

{{ $noop := .Content }}
{{ .Store.Get "mykey" }}

以下方法同样可以触发内容渲染:ContentWithoutSummaryFuzzyWordCountLenPlainPlainWordsReadingTimeSummaryTruncatedWordCount。例如:

{{ $noop := .WordCount }}
{{ .Store.Get "mykey" }}

作用域选择建议

结合文档与源码,选择作用域时可以遵循以下原则:

  • 只在当前模板/shortcode 内部使用:首选 newScratch。它最轻量(hstore.NewScratch() 即分配一个空 map)、无生命周期歧义,且 newScratch 引入后 shortcode 内 Store 已被官方视为过时。
  • render hook 写、页面布局读:使用 PAGE.Store,配合 noop 技巧保证读取时机。
  • 跨页面统计:使用 SITE.Store(0.139.0+),它在整个站点构建内共享。
  • 需要跨重建保留的状态:页面/站点/全局 Store 都不会在 server 重建时重置,可在 hugo server 开发期作为轻量缓存使用;但需自行处理键的清理(Delete/DeleteInMap)。
  • 注意并发:对页面/站点 Store 不要调用 Values 获取裸 map,始终通过 Get 等方法访问,以保持在锁保护之下。

参考

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341