Skip to content

text/template: Template tree produced by template.Clone is not in a clean state #56409

@kidlj

Description

@kidlj

What version of Go are you using (go version)?

$ go version
go version go1.19.2 linux/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/lijian/.cache/go-build"
GOENV="/home/lijian/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS="-tags=dev"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/lijian/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/lijian/go"
GOPRIVATE=""
GOPROXY="https://goproxy.cn,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.19.2"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/lijian/Code/blog/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3532686189=/tmp/go-build -gno-record-gcc-switches"

What did you do?

To make Go html/template inherit base template properly, every time executing an end template I make a copy via template.Clone of the full template tree, hoping it in a clean state, then add the desired end template to the cloned template tree and execute it. It almost works well, except that when a {{block "FOO" .}}{{end}} is defined in the base template, but the corresponding named block is not {{define}}-ed in the end executing template, the execution result may contain the content of block FOO defined in other templates that should not be executed.

The clone and execution process is as follows:

package templates

import (
	"embed"
	"fmt"
	"html/template"
	"io"

	"github.com/labstack/echo/v4"
)

//go:embed views/*.html
var templateFS embed.FS

type Template struct {
	templates *template.Template
}

func NewTemplate() *Template {
	templates := template.Must(template.New("").Funcs(funcMap()).ParseFS(templateFS, "views/*.html"))
	return &Template{
		templates: templates,
	}
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	tmpl := template.Must(t.templates.Clone())
	tmpl = template.Must(tmpl.ParseFS(templateFS, fmt.Sprintf("views/%s", name)))
	return tmpl.ExecuteTemplate(w, name, data)
}

func funcMap() template.FuncMap {
	return template.FuncMap{
		"dateTime": dateTime,
		"noEscape": noEscape,
	}
}

And the base layout template is as follows:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width" />
	<title>{{block "title" .}}{{end}}</title>
	<link rel="stylesheet" href="/static/bin/style.css">
	{{block "feed" .}}{{end}}
</head>

<body>
	<div class="container">
		{{block "content" .}}{{end}}
	</div>
</body>

</html>

When the blog.html template that inherits layout.html and defines block feed is executed, it works fine(that produces feed block contents):

{{template "layout.html" .}}

{{define "title"}}{{.Blog.Title}}{{end}}

{{define "feed"}}
<link rel="alternate" type="application/atom+xml" title="Blog Feed" href="{{.Feed}}">
{{end}}

{{ define "content" }}
<article>
	{{with .Blog}}
	<div class="content">
		<div class="header">
			<h2>{{.Title}}</h2>
			<span>{{.Date | dateTime}}</span>
		</div>

		{{.Content | noEscape}}
	</div>
	{{end}}
</article>
{{end}}

But when the index.html template that also inherits layout.html but does not define block feed is executed, the execution result also contains the content of block feed, which seems like come from blog.html.

{{template "layout.html" .}}

{{define "title"}}Home{{end}}

{{ define "content" }}
<article>
	<div class="content">
		<h1 class="header">Home</h1>
		<p>
			<a href="/blog/">Go to Blog →</a>
		</p>
	</div>
</article>
{{end}}

The execution result:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width" />
	<title>Home</title>
	<link rel="stylesheet" href="/static/bin/style.css">
	
<!-- this line should not be produced here -->
<link rel="alternate" type="application/atom+xml" title="Blog Feed" href="">

</head>

In short, in the static blog project, I want blogs.html and blog.html to have the feed link produced, but not having it on the index.html page.

The full project code can be checked here: https://github.com/kidlj/blog

What did you expect to see?

Template tree produced by template.Clone is in a clean state that doesn't generate contents from blocks that have not been defined.

What did you see instead?

Template tree produced by template.Clone generates block contents that are defined elsewhere not being executed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions