尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Hugo 模板函数 templates.Inner 详解:用 partial decorator 实现内容块注入与组合式布局

Hugo 模板函数 templates.Inner 详解:用 partial decorator 实现内容块注入与组合式布局 Hugo 模板函数 templates.Inner 详解用 partial decorator 实现内容块注入与组合式布局【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugotemplates.Inner是 Hugo 0.154.0 引入的模板函数也是partial decorator局部装饰器机制的核心注入点调用方模板把一段代码块交给with partial捕获装饰器_partial 模板则通过templates.Inner决定这段内容注入到哪里、渲染几次、以什么数据为上下文。本文以 Inner.md 为主体结合 templates.go 实现与 decorator_integration_test.go 测试用例系统讲解其用法、上下文绑定、重复执行与底层原理。读完你将掌握用templates.Inner构建可嵌套卡片、列表、网格等装饰器组件的完整实战方案。一、templates.Inner 是什么一次“执行反转”的注入点从 模板函数文档 的定义看templates.Inner在partial模板中扮演占位符placeholder角色。当调用方模板以装饰器decorator方式调用partial时Hugo 不是立即渲染传入的内容块而是先将其捕获再由templates.Inner精确指定捕获内容的注入位置。这带来一种“执行反转”reversal of execution被调用方callee变成实际执行者调用方caller反而提供内容。partial模板负责管理外层结构如div容器、CSS 类、循环而调用方模板始终掌控内层内容的标记与数据展示方式。在源码层面Inner方法定义于 tpl/templates/templates.go#L85-L102// Inner executes the inner content of a partial decorator. // Note that there is only one inner block per partial decorator, but inner may be called multiple times with, typically, different data. func (ns *Namespace) Inner(ctx context.Context, data any) (any, error) { stack : tpl.Context.PartialDecoratorIDStack.Get(ctx) id, ok : stack.Peek() if !ok { panic(no partial decorator ID on stack) } // Signal that inner exists. id.Bool true partialName : fmt.Sprintf(%s%s, tplimpl.PartialDecoratorPrefix, id.Str) v, err : ns.partialsNs.Include(ctx, partialName, data) return v, err }从这段实现可以读出三个关键事实每个 partial decorator 只有一个 inner 块但templates.Inner可以被调用多次通常配合不同 data这正是“重复执行”能力的基础调用templates.Inner时会在上下文中压栈的装饰器 ID上做标记id.Bool true并向内部模板发起一次Include调用内部模板名以tplimpl.PartialDecoratorPrefix即_internal/decorator_定义见 templatetransform.go#L239-L240为前缀拼装如果装饰器从未调用templates.Inner_PopPartialDecorator会返回空内容而非报错见 templates.go#L111-L130避免渲染出残缺页面。二、基础用法with partialtemplates.Inner2.1 调用方模板必须使用 block 风格 with使用该函数的前提是调用方模板必须采用块block风格语法并用with语句发起调用——这一约束允许装饰器进行深层嵌套。原文档给出的最简示例{{ with partial components/card.html . }} pThis content is passed to the partial./p {{ end }}注意with语句会创建新的作用域调用方模板中在with块外定义的变量不会自动出现在被捕获的内容块内。若要使用外部数据必须保证它包含在调用partial时传入的上下文里。2.2 装饰器模板用 templates.Inner 渲染捕获块在partial模板内部调用templates.Inner即可渲染被捕获的内容块div classcard-frame {{ templates.Inner . }} /divtemplates.Inner在模板中的别名是inner注册见 tpl/templates/init.go#L64-L67因此上面的写法等价于div classcard-frame {{ inner . }} /div测试用例 TestDecoratorInlinePartial 同时验证了inner与templates.Inner两种写法都能工作且partial、partialCached、partials.Include、partials.IncludeCached四种调用方式均兼容装饰器见 TestDecoratorInAllPartialFuncNames。2.3 编译期校验禁止在装饰器的 with 块内使用 inner值得警惕的是inner/templates.Inner不能出现在包裹 partial decorator 的那个with块内部否则会形成递归循环。模板转换器会通过正则templatesInnerRe{{\s*(templates\.Inner\b|inner\b)见 templatetransform.go#L242识别并直接报错inner cannot be used inside a with block that wraps a partial decorator测试 TestDecoratorFailOnInnerInWith 验证了包括带缩进/换行写法在内的各种形式都会被拦截。三、参数与上下文绑定templates.Inner接受一个可选参数上下文context它决定捕获块渲染时点号.的值。传参如{{ templates.Inner .SomeData }}捕获块内的点号会被重新绑定到该数据不传参捕获块沿用调用方首次发起partial调用时的上下文。这一点在多层包装嵌套、循环内使用装饰器时至关重要。参数最终会被透传给内部Include调用见上文Inner源码中的ns.partialsNs.Include(ctx, partialName, data)即templates.Inner的参数就是被注入内容的渲染数据源。结合 partial-decorators 文档 的示例来看“层层传参”的组合场景。调用方模板构造一个 dict 上下文并将多个装饰器逐层嵌套{{ $ctx : dict page . label Recent Posts pageCollection ((site.GetPage /posts).RegularPages) }} {{ with partial components/section.html $ctx }} div classgrid-wrapper {{ range .pageCollection }} {{ with partial components/column.html (dict page . class col-half) }} {{ with partial components/card.html (dict page .page url .page.RelPermalink title .page.LinkTitle) }} p {{ .page.Content | plainify | strings.Truncate 240 }} /p {{ end }} {{ end }} {{ end }} /div {{ end }}section 组件提供语义化容器与可选标题section classcontent-section {{ with .label }} h2 classsection-label{{ . }}/h2 {{ end }} div classsection-content {{ templates.Inner . }} /div /sectioncolumn 组件通过 CSS 类控制布局宽度div class{{ .class | default column-default }} {{ templates.Inner . }} /divcard 组件定义内容的视觉边界div classcard {{ with .title }} h2 classcard-title {{ if $.url }} a href{{ $.url }}{{ . }}/a {{ else }} {{ . }} {{ end }} /h2 {{ end }} div classcard-body {{ templates.Inner . }} /div {{ with .url }} div classcard-footer a href{{ . }}Read more/a /div {{ end }} /div这里每个templates.Inner .都把当前装饰器拿到的 dict 继续下传内层内容因此能访问page、label、class、url、title等字段——即使嵌套了三层包装也依然数据完整。四、重复执行一次装饰多次注入装饰器可以把捕获的内容块执行零次或多次。当外层结构需要对一组条目重复套用同一装饰时列表、网格等这个能力非常实用。原文档示例ul classstyled-list {{ range .items }} li {{ templates.Inner . }} /li {{ end }} /ul调用方提供的代码会针对.items集合中的每一项渲染一次且每次迭代点号.都会更新为当前条目。测试用例 TestDecoratorNested2 展示了这一模式的完整链路ul.html对页面集合循环调用inner .bold.html内部用inner $引用根上下文最终每个列表项都被ul与b双层包裹ul {{- range . }} li{{ inner . }}/li {{- end }} /ulb{{ inner $ }}/b输出为ul lia href/p1/bspanPage 1/span/b/a/li lia href/p2/bspanPage 2/span/b/a/li /ul另一个值得关注的用例是 TestDecoratorReturninner的返回值即被捕获块最终渲染出的内容可以参与算术运算——装饰器把inner 1、inner 2、inner 3的结果累加进$sum后return调用方再对返回值做mul . 2最终输出13。这说明templates.Inner不只是“注入点”其调用结果本身也是一个可编程使用的值。五、内部机制模板转换器如何把装饰器拆成内部模板理解templates.Inner的底层实现有助于你预判各种边界行为。在 templatetransform.go 中handleWithPartialL279-L362会在模板解析阶段对with partial ...结构做如下处理识别isWithPartialL195-L226判断with的第一个参数是否为partial/partialCached/partials.Include/partials.IncludeCached生成内部模板把with块的内容复制出来以模板名 块内容的 XXHash 作为唯一 ID注册为名为_partials/_internal/decorator_hash的内部模板L290-L318改写 with在with表达式中插入_pushPartialDecorator压入装饰器 ID与_popPartialDecorator弹出 ID若inner未被调用则输出空并将块内容替换为内部模板调用L320-L361。这样运行时templates.Inner查栈拿到 ID、拼出内部模板名并执行见第一节源码就完成了“内容捕获—按需注入”的闭环。栈式设计PartialDecoratorIDStack正是装饰器可以无限嵌套的原因。六、边界行为与限制结合测试文件 decorator_integration_test.go 与实现以下行为值得在实际项目中留意不调用 inner 的装饰器是合法的TestDecoratorInnerNeverCalledL25-L56证明with partial一个从不调用inner的partial时with块内的代码正常渲染、inner不产生输出不会报错一个装饰器可被多次调用TestDecoratorDuplicateInnerL248-L266验证同一个装饰器模板在页面上重复使用时各自独立输出多个 inner 调用、甚至嵌套装饰器TestDecoratorMultipleL162-L196验证一个装饰器内多次调用inner、以及装饰器再嵌套装饰器的复杂组合均能按序输出模板类型覆盖广TestDecoratorInAllTemplateTypesL268-L299显示装饰器不仅可用于布局模板还能用于 render-link、shortcode 等模板类型在with块内使用inner会编译报错见 2.3 节with块内出现break/continue的特殊结构会被守卫逻辑放行见 templatetransform.go#L286-L289 及 issue #14333 相关测试 TestPartialWithBreakOutsideRange14333。七、实战用装饰器生成带 SRI 哈希的脚本标签TestDecoratorFingerprintScript 提供了一个颇具工程价值的组合用法把templates.Inner的返回值作为资源内容计算 SRI 哈希并生成 Content-Security-Policy{{ $s : (templates.Inner .) }} {{ $r : resources.FromString ($s | xxhash) $s | fingerprint }} {{ $hash : $s | crypto.SHA256 | encoding.HexDecode | encoding.Base64Encode }} {{ $sri : (printf sha256-%s $hash) }} {{ if ne $sri $r.Data.Integrity }} {{ errorf SRI hash mismatch: %s ! %s $sri $r.Data.Integrity }} {{ end }} {{ hugo.Store.SetInMap srihashes $sri true }} script{{ $s | safeJS }}/script调用方在with块中提供脚本内容{{ with partial script.html . }} console.log(Hello world); {{ end }}最终页面输出script标签同时在_headers文件中汇总所有脚本的 sha256 哈希实现Content-Security-Policy: script-src self sha256-...。这里templates.Inner捕获的是一整段 JavaScript 源码字符串充分体现了“装饰器分离容器逻辑与内容逻辑”的设计价值。八、总结templates.Inner别名inner是 Hugo 0.154.0 起 partial decorator 机制的枢纽一个注入点在partial模板中标定捕获内容块的渲染位置一次执行反转partial管理外层结构调用方掌控内层内容可选上下文参数重绑定点号.保证深层嵌套与循环内数据正确可重复执行配合range可对集合逐项装饰返回值还可参与模板运算。使用要点可归纳为调用方必须用with partial块风格语法、块外变量需显式放入传入的上下文、inner不能出现在包裹该with的块内部。相关术语可参考 partial decorator 词汇表完整机制见 partial decorators 指南。【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表