如何使用golang html/template的基本模板文件?

Ser*_*tin 4 go

有gin-gonic网络应用程序.

有3个文件:

1)base.html - 基本布局文件

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

header...

{{template "content" .}}

footer...

</body>
</html>
Run Code Online (Sandbox Code Playgroud)

2)page1.html,用于/ page1

{{define "content"}}
<div>
    <h1>Page1</h1>
</div>
{{end}}
{{template "base.html"}}
Run Code Online (Sandbox Code Playgroud)

3)page2.html,用于/ page2

{{define "content"}}
<div>
    <h1>Page2</h1>
</div>
{{end}}
{{template "base.html"}}
Run Code Online (Sandbox Code Playgroud)

问题是/ page1和/ page2使用一个模板 - page2.html.我想,我有这样的结构的误区:{{define "content"}},{{template "base.html"}}.

请问,您能举例说明如何在golang中使用基本布局吗?

小智 24

Go 1.16引入了embed包,它将非.go文件打包成二进制文件,极大地方便了Go程序的部署。该ParseFS函数也被添加到标准库 html/template 中,它将 embed.FS 中包含的所有模板文件编译成模板树。

// templates.go
package templates

import (
    "embed"
    "html/template"
)

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

type Template struct {
    templates *template.Template
}

func New() *Template {
    funcMap := template.FuncMap{
        "inc": inc,
    }

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


// main.go
t := templates.New()
Run Code Online (Sandbox Code Playgroud)

t.templates是一个全局模板,包含所有匹配的views/*.html模板,所有模板都是相关的,可以互相引用,模板的名称就是文件的名称,例如article.html.

此外,我们Render为该*Template类型定义了一个方法,该方法实现了RendererEcho Web 框架的接口。

// templates.go
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return t.templates.ExecuteTemplate(w, name, data)
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以指定 Echo 的渲染器,以便于在每个处理程序中生成 HTML 响应,只需将模板的名称传递给函数即可c.Render

// main.go
func main() {
    t := templates.New()

    e := echo.New()
    e.Renderer = t
}


// handler.go
func (h *Handler) articlePage(c echo.Context) error {
    id := c.Param("id")
    article, err := h.service.GetArticle(c.Request().Context(), id)
    ...
    return c.Render(http.StatusOK, "article.html", article)
}
Run Code Online (Sandbox Code Playgroud)

由于t.templates模板包含了所有已解析的模板,因此每个模板名称都可以直接使用。

为了组装 HTML,我们需要使用模板继承。例如,为基本HTML框架和<head>元素定义一个layout.html,并设置{{block "title"}}{{block "content"}},其他模板继承layout.html,并用自己定义的块填充或覆盖布局模板的同名块。

以下是layout.html模板的内容。

<!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, initial-scale=1.0">
    <title>{{block "title" .}}{{end}}</title>
    <script src="/static/main.js"></script>
</head>

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

</html>
Run Code Online (Sandbox Code Playgroud)

对于其他模板,您可以参考(继承)layout.html,并在layout.html模板中定义块。

例如,login.html 的内容如下。

{{template "layout.html" .}}

{{define "title"}}Login{{end}}

{{define "content"}}
<form class="account-form" method="post" action="/account/login" data-controller="login">
    <div div="account-form-title">Login</div>
    <input type="phone" name="phone" maxlength="13" class="account-form-input" placeholder="Phone" tabindex="1">
    <div class="account-form-field-submit ">
        <button type="submit" class="btn btn-phone">Login</button>
    </div>
</form>
{{end}}
Run Code Online (Sandbox Code Playgroud)

Article.html 还引用了layout.html:

{{template "layout.html" .}}

{{define "title"}}<h1>{{.Title}}</h1>{{end}}

{{define "content"}}
<p>{{.URL}}</p>
<article>{{.Content}}</article>
{{end}}
Run Code Online (Sandbox Code Playgroud)

我们希望在渲染和渲染article.html 模板时,login.html 模板中定义的块会覆盖layout.html 中的块。但事实并非如此,这取决于 Go 文本/模板的实现。在我们的实现中ParseFS(tmplFS, "views/*.html"),假设先解析article.html,并将其content块解析为模板名称,那么当稍后解析login.html模板并content在其中发现块时,text/template将覆盖该模板的模板与后面解析的内容同名,所以当所有模板都解析完毕后,content我们的模板树中实际上只有一个命名的模板,它是content在最后解析的模板文件中定义的。

因此,当我们执行article.html模板时,有可能该content模板不是这个模板中定义的内容,而是content其他模板中定义的内容。

社区针对这个问题提出了一些解决方案。例如,不使用全局模板,而是每次渲染时都会创建一个新模板,仅包含layout.html和子模板的内容。但这确实很乏味。事实上,当 Go 1.6 引入了用于文本/模板的指令 [1] 时,我们只需对上面的代码进行一些更改,就block可以使用该方法执行我们想要的操作。Clone

// templates.go
package templates

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

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

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

type Template struct {
    templates *template.Template
}

func New() *Template {
    funcMap := template.FuncMap{
        "inc": inc,
    }

    templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tmplFS, "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(tmplFS, "views/"+name))
    return tmpl.ExecuteTemplate(w, name, data)
}
Run Code Online (Sandbox Code Playgroud)

可以看到Render这里只修改了函数。我们不执行全局模板,而是将其克隆到一个新模板中,而content这个新模板中的块可能不是我们想要的,所以这里我们解析一个子模板的内容,我们最终将在这个子模板上​​渲染全局模板,这样content新添加的子模板的 会覆盖之前的,可能不正确的content. 我们的目标子模板引用了全局模板中的layout.html,这并不冲突,而且由于全局模板永远不会被执行(我们Render每次执行时都会在函数中克隆一个新的全局模板),因此它也是干净的。当一个模板最终执行时,我们得到了一个干净的layout.html,里面有content我们想要的内容,相当于每次执行都会生成一个新的模板,里面只包含我们需要的布局模板和子模板。想法是一样的,但是执行模板时不是手动生成新模板,而是在函数中自动完成Render

当然,你也可以{{ template }}在子模板中引用其他布局模板,只要这些布局模板不互相覆盖即可,执行时只需要指定目标子模板的名称,以及模板引擎会自动使用{{ template }}其中定义的标签为我们找到布局模板,这些模板都在克隆的全局模板中。

[1] https://github.com/golang/go/commit/12dfc3bee482f16263ce4673a0cce399127e2a0d


Anz*_*zel 13

只要您将模板与"内容"一起解析,就可以使用base.html,如下所示:

base.html文件

{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<body>

header...

{{template "content" .}}

footer...

</body>
</html>
{{end}}
Run Code Online (Sandbox Code Playgroud)

page1.html

{{define "content"}}
I'm page 1
{{end}}
Run Code Online (Sandbox Code Playgroud)

page2.html

{{define "content"}}
I'm page 2
{{end}}
Run Code Online (Sandbox Code Playgroud)

然后ParseFiles与("your-page.html","base.html")和ExecuteTemplate与您的上下文.

tmpl, err := template.New("").ParseFiles("page1.html", "base.html")
// check your err
err = tmpl.ExecuteTemplate(w, "base", yourContext)
Run Code Online (Sandbox Code Playgroud)

  • 那对我不起作用。我使用 ParseGlob,因此所有 html 文件都会被解析。尽管我在 ExecuteTempate 中指定了第一个模板,但始终会加载第二个模板。我真不知道,为什么四年过去了还是这样…… (4认同)