如何用text/template渲染「模板的模板」,仅渲染指定动作无需逐个转义
Go text/template 渲染「模板的模板」:仅渲染指定动作,其余保留字面量
问题场景
给定如下模板,需要仅渲染{{.Foo}},其余所有{{...}}语法均作为字面量保留:
I want to render {{.Foo}}. but I don't want to render anything on this line, like {{.Bar}} or this template: [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }} Render {{.Foo}} again.
期望输出:
I want to render foo. but I don't want to render anything on this line, like {{.Bar}} or this template: [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }} Render foo again.
实现方案
方案1:自定义模板函数简化转义
手动逐个编写{{ "{{" }}转义过于繁琐,可以自定义一个模板函数自动生成原始模板语法:
package main import ( "bytes" "text/template" ) // raw 将传入的字符串包装为{{...}}字面量输出 func raw(s string) string { return "{{" + s + "}}" } func main() { // 注册自定义函数 funcMap := template.FuncMap{"raw": raw} // 修改模板,将不需要渲染的片段用raw函数包裹 tmplStr := `I want to render {{.Foo}}. but I don't want to render anything on this line, like {{raw ".Bar"}} or this template: [{{raw ".Status | toUpper"}}{{raw "if eq .Status \"firing\""}}:{{raw ".Alerts.Firing | len"}}{{raw "end"}}] {{raw ".CommonLabels.alertname"}} for {{raw ".CommonLabels.job"}} Render {{.Foo}} again.` tmpl, err := template.New("t").Funcs(funcMap).Parse(tmplStr) if err != nil { panic(err) } data := map[string]interface{}{"Foo": "foo"} var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { panic(err) } print(buf.String()) }
这种方式只需将无需渲染的模板内容传入raw函数,即可自动保留为{{...}}字面量。
方案2:自定义外层模板分隔符(推荐)
如果场景允许,建议给外层需要渲染的内容换用自定义分隔符(比如<<...>>),彻底避免语法冲突:
package main import ( "bytes" "text/template" ) func main() { // 使用<<>>作为外层模板的分隔符 tmplStr := `I want to render <<.Foo>>. but I don't want to render anything on this line, like {{.Bar}} or this template: [{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }} Render <<.Foo>> again.` // 解析模板时指定自定义分隔符 tmpl, err := template.New("t").Delims("<<", ">>").Parse(tmplStr) if err != nil { panic(err) } data := map[string]interface{}{"Foo": "foo"} var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { panic(err) } print(buf.String()) }
这种方式无需任何转义操作,外层标记被正常渲染,内层{{...}}完全保留为字面量,逻辑更清晰。
方案3:嵌套模板的局限性说明
你提到的{{template "outer" .Foo}}方式不适用于当前场景:Go模板会解析所有{{...}}标记,未转义的非目标片段(比如{{.Bar}})会被尝试渲染,若数据中无对应字段会报错或输出空值,无法直接保留为字面量。
关于代码异味的讨论
这种「模板的模板」场景本身不一定是代码异味,需根据需求判断:
- 若仅需简单变量替换,使用字符串替换(如
strings.ReplaceAll)更轻量,无需引入模板引擎; - 若涉及复杂逻辑(条件判断、循环、函数调用等),使用模板引擎更合适;
- 若频繁生成模板类内容,推荐使用自定义分隔符方案,既能利用模板能力,又能避免语法混淆,可读性和可维护性更好。
内容的提问来源于stack exchange,提问作者modulitos
相关产品推荐
相关产品推荐

