Go使用embed.FS加载html/template异常 所有路由均返回test.html内容
问题原因
Go 标准库 html/template 的模板定义遵循全局共享、后加载覆盖前加载的规则:
- 你通过
template.ParseFS一次性将所有视图模板、布局模板加载到同一个全局view模板实例中,home.html、test.html、process.html三个业务模板都定义了名为content的全局模板块。 {{define}}指令的注册在模板解析阶段完成,和它在文件中的书写顺序无关,模板按文件遍历顺序加载,test.html加载顺序最晚,它定义的content块直接覆盖了之前两个业务模板的同名块。- 不管调用
ExecuteTemplate执行哪个业务模板,模板内引用base布局时,base调用的content块永远是最后被加载的 test 页面版本,因此所有路由都返回 test 页面的渲染结果。
额外说明:你代码中 init 函数的错误判断逻辑是冗余的,template.Must 方法已经内置了错误处理,解析失败会直接抛出 panic,后续判断 err != nil 的逻辑永远不会触发,可以直接删除。
解决方案
以下两种方案均基于标准库实现,任选其一即可:
方案1:拆分模板实例,业务模板单独解析(推荐)
不要全局共用一个包含所有业务模板的实例,预加载公共布局/组件模板,每次渲染对应路由时再单独解析对应业务模板,避免块名冲突:
// 全局只预加载公共布局和公共组件模板 var baseTpl *template.Template func init() { baseTpl = template.Must(template.ParseFS(resources.Views, "templates/layouts/*.html", "templates/partials/*.html")) }
修改路由处理函数,每个路由单独绑定对应业务模板:
func home(w http.ResponseWriter, r *http.Request) { // 克隆基础模板避免污染公共实例 tpl, err := baseTpl.Clone() if err != nil { log.Fatalln(err) } // 仅解析当前路由对应的业务模板 tpl = template.Must(tpl.ParseFS(resources.Views, "templates/views/home.html")) // 直接执行基础布局模板即可,会自动使用当前业务模板定义的content块 err = tpl.ExecuteTemplate(w, "base", nil) if err != nil { log.Fatalln(err) } } // test、process 路由同理修改,仅替换解析的业务模板文件名即可 func test(w http.ResponseWriter, r *http.Request) { tpl, err := baseTpl.Clone() if err != nil { log.Fatalln(err) } tpl = template.Must(tpl.ParseFS(resources.Views, "templates/views/test.html")) err = tpl.ExecuteTemplate(w, "base", nil) if err != nil { log.Fatalln(err) } } func process(w http.ResponseWriter, r *http.Request) { tpl, err := baseTpl.Clone() if err != nil { log.Fatalln(err) } tpl = template.Must(tpl.ParseFS(resources.Views, "templates/views/process.html")) err = tpl.ExecuteTemplate(w, "base", nil) if err != nil { log.Fatalln(err) } }
方案2:全局块重命名,避免重名冲突
如果你不想拆分模板实例,可以给每个业务模板的 content 块定义唯一名称,同时修改布局模板适配动态块名:
- 修改
base.html布局,将固定调用content块改为接收块名参数:
{{define "base"}} <!doctype html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta charset="utf-8"> </head> <body> {{template .ContentBlock .}} </body> </html> {{end}}
- 修改所有业务模板的块名为唯一值,以home.html为例:
{{define "home.html"}} {{template "base" map "ContentBlock" "home-content" "." .}} {{end}} {{define "home-content"}} This is home {{end}}
test.html和process.html同理修改块名为test-content、process-content即可。
内容的提问来源于stack exchange,提问作者Hasan A Yousef
相关产品推荐
相关产品推荐

