Hugo 短代码 .Inner:在开闭标签之间提取与渲染内容
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
导读
.Inner是 Hugo 短代码(shortcode)模板中的核心变量之一,它返回短代码开标签与闭标签之间的原始内容,适用于任何带闭标签的短代码调用。本文以 docs/content/en/methods/shortcode/Inner.md 为骨架,结合 hugolib/shortcode.go 等源码,系统讲解.Inner的数据结构、取用姿势、空白字符处理,以及通过RenderString、markdownify或将整个短代码按 Markdown 渲染({{% %}}写法)来把 Inner 内容从纯文本升级为真正的 HTML,并给出可直接复制的完整示例。
一、.Inner 是什么
在 Hugo 中,短代码有两种常见调用形态:自闭合(无内容)与成对(有内容)。当你在 Markdown 源文件中写成对短代码时,比如:
{{</* card title="Product Design" */>}} We design the **best** widgets in the world. {{</* /card */>}}开标签{{</* card ... */>}}与闭标签{{</* /card */>}}之间的所有内容——We design the **best** widgets in the world.——就是该短代码的Inner 内容。在短代码模板中,通过.Inner即可取用这段内容。
从源码看,.Inner的类型是template.HTML,由 hugolib/shortcode.go 中ShortcodeWithPage结构体定义:
// ShortcodeWithPage is the "." context in a shortcode template. type ShortcodeWithPage struct { Params any Inner template.HTML Page page.Page Parent *ShortcodeWithPage Name string IsNamedParams bool // ... }ShortcodeWithPage就是短代码模板执行时的.上下文,因此模板里可以同时访问:
| 成员 | 含义 |
|---|---|
.Inner | 开闭标签之间的原始内容(template.HTML类型) |
.Get "title" | 按键取短代码参数(hugolib/shortcode.go 中的Get方法) |
.Params | 全部参数 |
.Page | 当前页面对象 |
.Parent | 父级短代码上下文(支持嵌套短代码) |
.Ordinal | 该短代码在页面中的零基序号 |
.Store | 短代码作用域的临时存储(替代已废弃的.Scratch) |
需要留意:
.Inner只有在对短代码的调用包含闭标签时才有效。如果调用是自闭合的(没有闭标签、没有内容),.Inner为空。源码 hugolib/shortcode.go 甚至会抛出错误提示:"shortcode %q does not evaluate .Inner or .InnerDeindent, yet a closing tag was provided",即模板中使用了.Inner却未提供闭标签时会收到明确的构建错误,方便你定位问题。
1.1 相关的 InnerDeindent
在 hugolib/shortcode.go 中还有一个InnerDeindent()方法:当短代码在源文件中带有缩进时,它返回去除缩进后的 Inner 内容。它按行遍历,把以scp.indentation(开标签前的缩进)开头的行去掉该前缀。若没有缩进则直接返回.Inner。如果你的短代码内容会被嵌入到缩进敏感的场景(如 Markdown 代码块),可以考虑使用InnerDeindent而非Inner。
二、基础用法:把 Inner 作为纯文本输出
最直接的用法是在短代码模板中直接输出.Inner。例如定义一个卡片短代码 layouts/_shortcodes/card.html(对应前面content/services.md中的调用):
<div class="card"> {{ with .Get "title" }} <div class="card-title">{{ . }}</div> {{ end }} <div class="card-content"> {{ .Inner | strings.TrimSpace }} </div> </div>渲染结果:
<div class="card"> <div class="card-title">Product Design</div> <div class="card-content"> We design the **best** widgets in the world. </div> </div>这里有两个要点:
- 用
strings.TrimSpace处理换行。开闭标签之间的内容在 Markdown 中常带有前导/尾随换行(取决于书写位置),直接输出会破坏布局。strings.TrimSpace会移除首尾的空白(包括回车与换行),使 Inner 内容紧贴card-content。对应的模板函数定义可参考 tpl/strings/strings.go 的 TrimSpace 实现。 - 默认情况下 Inner 中的 Markdown 不会被渲染。示例里
**best**是 Markdown 加粗语法,但输出仍是字面**best**——因为.Inner只是原始内容(template.HTML),Hugo 不会自动对它做 Markdown 转换。
2.1 源码视角:Inner 内容的收集
在 hugolib/shortcode.go 的渲染逻辑中,Hugo 会遍历解析出的 inner 数据段:普通字符串直接拼接,遇到嵌套短代码则递归渲染后拼接,最终写入data.Inner。同时,对于以 Markdown 渲染模式({{% %}},见下文)调用、且内容不含换行的场景,源码还会用正则\A<p>(.*)</p>\n\z剥离包裹的<p>标签(hugolib/shortcode.go),避免单行内容被包成段落块。这解释了为什么“按 Markdown 渲染”时单行与多行 Inner 的输出形态会不同。
三、用 RenderString 把 Inner 渲染成 HTML
要让 Inner 中的 Markdown 真正变成 HTML,最简单的方式是把它交给Page.RenderString方法处理。修改上面的模板:
<div class="card"> {{ with .Get "title" }} <div class="card-title">{{ . }}</div> {{ end }} <div class="card-content"> {{ .Inner | strings.TrimSpace | .Page.RenderString }} </div> </div>渲染结果:
<div class="card"> <div class="card-title">Product design</div> <div class="card-content"> We produce the <strong>best</strong> widgets in the world. </div> </div>RenderString把 Inner 中经过 TrimSpace 处理后的 Markdown 文本渲染为 HTML(**best**→<strong>best</strong>)。关于RenderString的完整说明可参考 docs/content/en/methods/page/RenderString.md。
注意:示例标题从 "Product Design" 变为 "Product design",这只是示例文案本身的差异,并非
RenderString的副作用——RenderString只负责把 Markdown 渲染为 HTML,不会改写标题文本。
3.1 markdownify 与 RenderString 的取舍
原文档明确指出:你也可以用markdownify函数替代RenderString方法,但后者更灵活。从 tpl/transform/transform.go 的实现可以看到:
// Markdownify renders s from Markdown to HTML. func (ns *Namespace) Markdownify(ctx context.Context, s any) (template.HTML, error) { home := ns.deps.Site.Home() if home == nil { panic("home must not be nil") } ss, err := home.RenderString(ctx, s) if err != nil { return "", err } // Strip if this is a short inline type of text. bb := ns.deps.ContentSpec.TrimShortHTML([]byte(ss), "markdown") return helpers.BytesToHTML(bb), nil }从源码看,markdownify内部本质上是基于 Home 页面调用RenderString,并额外通过TrimShortHTML对短小行内文本做<p>剥离处理。而RenderString是Page对象的方法,允许指定渲染器与上下文(如使用当前页面的配置),因此在需要精确控制渲染行为、处理块级内容时更具灵活性。简单行内文本可用markdownify,复杂块级内容建议用Page.RenderString。
四、替代写法:用 {{% %}} 按 Markdown 渲染整个短代码
除了在模板内用RenderString二次加工,Hugo 还提供第二种思路:把整个短代码调用声明为 Markdown 渲染模式。将内容中的{{</* */>}}改为{{%/* */%}}记号(docs/content/en/content-management/shortcodes 中有完整记号说明):
{{%/* card title="Product Design" */%}} We design the **best** widgets in the world. {{%/* /card */%}}此时 Hugo 会把整个短代码(包括输出结果)作为 Markdown 渲染,因此需要做两处配合修改。
4.1 第一步:允许 raw HTML
由于短代码模板输出的是 HTML,而整体又要走 Markdown 渲染管线,必须先在配置中放行 raw HTML:
{{< code-toggle file=hugo >}} [markup.goldmark.renderer] unsafe = true {{< /code-toggle >}}
对应hugo.toml的写法:
[markup.goldmark.renderer] unsafe = true安全说明:此配置之所以"unsafe"是因为它允许 Markdown 内容中嵌入未经转义的原始 HTML;如果你完全掌控内容源(例如个人站点或可信作者团队),风险可控。Hugo 的安全模型可参考 docs/content/en/about/security/_index.md。
4.2 第二步:遵循 CommonMark 缩进与 HTML 块规则
因为整个短代码被当作 Markdown 渲染,模板输出必须符合 CommonMark 规范 中关于缩进代码块与原始 HTML 块的规则:
<div class="card"> {{ with .Get "title" }} <div class="card-title">{{ . }}</div> {{ end }} <div class="card-content"> {{ .Inner | strings.TrimSpace }} </div> </div>与基础版模板对比,差异是微妙但必须的(见下面对照):
--- layouts/_shortcodes/a.html +++ layouts/_shortcodes/b.html @@ -1,8 +1,9 @@ <div class="card"> {{ with .Get "title" }} - <div class="card-title">{{ . }}</div> + <div class="card-title">{{ . }}</div> {{ end }} <div class="card-content"> - {{ .Inner | strings.TrimSpace | .Page.RenderString }} + + {{ .Inner | strings.TrimSpace }} </div> </div>具体变化有三处:
- 调整缩进:
.card-title从 4 空格缩进改为 2 空格,避免被 CommonMark 识别为缩进代码块(4 空格及以上会被当作代码块处理)。 - 添加空行:在
.Inner前增加空行,确保 Inner 内容作为独立的 HTML 块处理,而不是与相邻标签粘连。 - 移除
RenderString:在{{% %}}记号下,不要再对 Inner 做RenderString或markdownify处理——因为整个短代码已经被 Hugo 作为 Markdown 渲染,Inner 的 Markdown 会在这一轮渲染中自然转换。若再手动调用RenderString会造成双重渲染。
[!NOTE] 使用 Markdown 记号(
{{% %}})调用短代码时,不要用RenderString或markdownify处理 Inner 值。
五、两种方案的对比与选择
| 维度 | 方案 A:.Inner | .Page.RenderString | 方案 B:{{% %}}+ unsafe |
|---|---|---|
| 调用记号 | {{</* */>}} | {{%/* */%}} |
是否需配置unsafe = true | 否 | 是 |
| 是否需遵循 CommonMark 缩进/HTML 块规则 | 否(模板输出即最终 HTML) | 是 |
| Inner 中 Markdown 渲染方式 | 在模板内显式调用RenderString/markdownify | 由整段短代码的 Markdown 渲染自动完成 |
| 灵活性 | 高:可精确控制渲染时机、上下文与输出结构 | 低:输出受 Markdown 渲染管线约束 |
| 典型场景 | 内容块、卡片、引用等需要精细控制的组件 | 希望在短代码内外统一走 Markdown 处理流程 |
- 想要精确控制、不引入全局配置,选方案 A(
RenderString)。 - 希望 Markdown 与短代码输出统一处理、且内容源可信,选方案 B(
{{% %}}+unsafe = true)。 - 无论哪种方案,都建议对
.Inner先做strings.TrimSpace,避免开闭标签旁的多余换行影响布局。
六、易错点小结
- 忘记闭标签:模板里用到
.Inner而调用处没有闭标签,Hugo 会报错:"shortcode does not evaluate .Inner ... yet a closing tag was provided"(hugolib/shortcode.go)。 - Inner 默认不渲染 Markdown:直接输出
.Inner得到的是字面文本(含**等 Markdown 语法),需要RenderString或markdownify(或在{{% %}}模式下自动处理)。 - 未处理首尾换行:开闭标签之间的换行会原样进入 Inner,记得
strings.TrimSpace。 {{% %}}模式下重复渲染:已整体按 Markdown 渲染时,再对 Inner 调用RenderString/markdownify会双重转换。- CommonMark 缩进陷阱:
{{% %}}模式下模板输出的 4 空格缩进可能被当作代码块,务必保持 2 空格缩进并正确使用空行分隔 HTML 块。
参考资料与源码索引
- 本文核心依据:docs/content/en/methods/shortcode/Inner.md
.Inner字段定义与InnerDeindent实现:hugolib/shortcode.go- Inner 内容收集与渲染(含
<p>剥离逻辑):hugolib/shortcode.go、hugolib/shortcode.go markdownify实现(内部调用RenderString):tpl/transform/transform.goPage.RenderString方法:docs/content/en/methods/page/RenderString.md- 短代码记号说明:docs/content/en/content-management/shortcodes
- Hugo 安全模型:docs/content/en/about/security/_index.md
- 短代码模板目录约定:
layouts/_shortcodes/
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考