Is it possible to access template by full file path? - go

I am writing a dynamic website generator where templates are expected to be compiled dynamically (from a changing file system) and the file paths of the templates are important.
With the text/template engine in the Go standard library, you seem to only be able to import a template by filename and not filepath
<!-- templates/index.html -->
<head>
{{ template "head.html" }}
</head>
<!-- templates/partials/head.html -->
<meta />
package main
//go:embed templates
var templates embed.FS
func main() {
folder, _ := fs.Sub(templates , "templates")
tpl, err := template.ParseFS(folder, "index.html" "partials/head.html")
// ...
}
I would like to designate a file system as the root location (rather than manually picking files to include) for my templates and have the imports of templates resolved relative to the template file - using the file path specified in the import. (like EJS - see section "includes")
Is such a thing possible with the default template renderer?
e.g.
folder, _ := fs.Sub(templates , "templates")
tpl, err := template.ParseFS(folder)
<!-- templates/index.html -->
<head>
{{ template "/partials/head.html" }}
</head>

Related

Why doesn't template.ParseFiles() detect this error?

If I specify a non-existent template in my template file, the error is not detected by ParseFiles() but by ExecuteTemplate(). One would expect parsing to detect any missing templates. Detecting such errors during parsing could also lead to performance improvements.
{{define "test"}}
<html>
<head>
<title> test </title>
</head>
<body>
<h1> Hello, world!</h1>
{{template "doesnotexist"}}
</body>
</html>
{{end}}
main.go
package main
import (
"html/template"
"os"
"fmt"
)
func main() {
t, err := template.ParseFiles("test.html")
if err != nil {
fmt.Printf("ParseFiles: %s\n", err)
return
}
err = t.ExecuteTemplate(os.Stdout, "test", nil)
if err != nil {
fmt.Printf("ExecuteTemplate: %s\n", err)
}
}
10:46:30 $ go run main.go
ExecuteTemplate: html/template:test.html:8:19: no such template "doesnotexist"
10:46:31 $
template.ParseFiles() doesn't report missing templates, because often not all the templates are parsed in a single step, and reporting missing templates (by template.ParseFiles()) would not allow this.
It's possible to parse templates using multiple calls, from multiple sources.
For example if you call the Template.Parse() method or your template, you can add more templates to it:
_, err = t.Parse(`{{define "doesnotexist"}}the missing piece{{end}}`)
if err != nil {
fmt.Printf("Parse failed: %v", err)
return
}
The above code will add the missing piece, and your template execution will succeed and generate the output (try it on the Go Playground):
<html>
<head>
<title> test </title>
</head>
<body>
<h1> Hello, world!</h1>
the missing piece
</body>
</html>
Going further, not requiring all templates to be parsed and "presented" gives you optimization possibilities. It's possible there are admin pages which are never used by "normal" users, and are only required if an admin user starts or uses your app. In that case you can speed up startup and same memory by not having to parse admin pages (only when / if an admin user uses your app).
See related: Go template name

Golang correct filepath to serve css files

I have been reading and trying to serve css css files to my html page and nothing has been working . I have been reading this https://forum.golangbridge.org/t/serving-static-css-files/2051/10 to get a better understanding . My project structure is below
func WebRoutes(r *mux.Router) {
r.HandleFunc("/", Index)
// Trying to serve file here and it's not working
r.Handle("/web/content/desktop/", http.StripPrefix("/web/content/desktop/", http.FileServer(http.Dir("desktop"))))
// Below is the correct path since it finds the file
_, err := os.Stat(filepath.Join(".", "/web/content/desktop/", "page.css"))
if err != nil {
println(err.Error())
}
}
I am referencing the file from my html page like this
<link rel="stylesheet" type="text/css" href="/Web/Content/desktop/page.css">
Any suggestions would be great since I can't seem to get my CSS to work .
You're serving your static files with:
http.FileServer(http.Dir("desktop"))
But based on the screenshot the path on disk is not "desktop" but rather "Web/Content/desktop".
Keep in mind that given that you're already using StripPrefix, there's no reason to use the full path unless you want to. You could do:
r.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("web/content/desktop"))))
Which would change the URL to:
<link rel="stylesheet" type="text/css" href="/css/page.css">

Why my golang template is not picking the external javascript file?

I tried so many suggestions from stack overflow but none seems to work. I was not able to pick the external js file.
My main function:
package main
import(
"encoding/json"
"net/http"
"fmt"
"github.com/gorilla/mux"
"github.com/rs/cors"
"text/template"
)
func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/html")
t, _ := template.ParseFiles("index2.html")
t.Execute(w, nil)
}
func main() {
router := mux.NewRouter()
people = append(people, Person{ID: "1", Firstname: "Nic", Lastname: "Raboy", Address: &Address{City: "Dublin", State: "CA"}})
people = append(people, Person{ID: "2", Firstname: "Maria", Lastname: "Raboy"})
fmt.Println(people)
router.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("."))))
router.HandleFunc("/people", GetPeopleEndpoint)
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://localhost:3000"},
AllowCredentials: true,
})
// Insert the middleware
handler := c.Handler(router)
// handler := cors.Default().Handler(router)
http.ListenAndServe(":12345", handler)
}
All my files are in the same directory.
Here is my html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
This is page2
<div id='newdiv'>
</div>
</body>
<script type="text/javascript" src="app2.js">
</script>
</html>
The error I'm getting is "GET http://localhost:12345/app2.js". I don't know where I'm doing the mistake.
This line:
router.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("."))))
Means that requests coming in for URLs under "/files/" will be served from files on disk in the current directory, with the "/files/" prefix removed.
So if you want a file in the current directory named app2.js, then the URL must be /files/app2.js.
router.Handle defines what handler will handle a given path. In this case, that path is /files/. Because of the trailing slash, the same handler will be used for all URLs beginning with /files/.
http.StripPrefix is a handler wrapper. It takes the incoming request, strips off the given prefix (in this case, /files/), removes it from the URL path, and then passes the request on to the handler passed to StripPrefix.
http.FileServer serves files out of a http.FileSystem, in this case provided by http.Dir. http.Dir exposes the files in a directory, in this case the current working directory (".").
So, in total: requests beginning with /files/, will have the /files/ part removed, then whatever is left, that file path will be looked for in the current working directory, and if it is found, it will be served. So, /files/app2.js will serve ./app2.js. Your HTML must reference /files/app2.js, not app.js.
Let's say you have directory structure:
<src-dir>
static
templates
...
And you map handler you're using http.StripPrefix
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
Update this line to-
<script type="text/javascript" src="/static/app2.js"></script>
Now you can access the file via http://localhost:12345/static/app2.js
Writing http.FileServer(http.Dir(".")) is generally a bad idea because it will expose not only your .js files but also you .go files, making you code visible via http requests. For example http://localhost:12345/main.go will make the browser deliver your source code as text.
The best practice should be to collect your static files (js, css, html...) in seperated directories, or just in one, like for example a "resources" package.
Try then with
http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("resources"))))
This code will do the work and allow you to access all your files that are under /resources with the url prefix /files. For example:
http://localhost:12345/files/app2.js

How to use base template file for golang html/template?

Have gin-gonic web app.
There are 3 files:
1) base.html -- base layout file
<!DOCTYPE html>
<html lang="en">
<body>
header...
{{template "content" .}}
footer...
</body>
</html>
2) page1.html, for /page1
{{define "content"}}
<div>
<h1>Page1</h1>
</div>
{{end}}
{{template "base.html"}}
3) page2.html, for /page2
{{define "content"}}
<div>
<h1>Page2</h1>
</div>
{{end}}
{{template "base.html"}}
The problem is that /page1 and /page2 use one template - page2.html. I think that I have misunderstanding of such constructions: {{define "content"}}, {{template "base.html"}}.
Please, can you show an example how to use base layouts in golang?
You can use the base.html as long as you parse the template along with your "content", like so:
base.html
{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<body>
header...
{{template "content" .}}
footer...
</body>
</html>
{{end}}
page1.html
{{define "content"}}
I'm page 1
{{end}}
page2.html
{{define "content"}}
I'm page 2
{{end}}
then ParseFiles with ("your-page.html", "base.html"), and ExecuteTemplate with your context.
tmpl, err := template.New("").ParseFiles("page1.html", "base.html")
// check your err
err = tmpl.ExecuteTemplate(w, "base", yourContext)
Go 1.16 introduces the embed package, which packages non-.go files into binaries, greatly facilitating the deployment of Go programs. The ParseFS function was also added to the standard library html/template, which compiles all the template files contained in embed.FS into a template tree.
// 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()
t.templates is a global template that contains all matching views/*.html templates, all of which are related and can be referenced to each other, and the name of the template is the name of the file, e.g. article.html.
Further, we define a Render method for the *Template type, which implements the Renderer interface of the Echo web framework.
// templates.go
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
You can then specify the renderer for Echo to facilitate generating HTML responses at each handler, simply by passing the name of the template to the c.Render function.
// 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)
}
Since the t.templates template contains all the parsed templates, each template name can be used directly.
In order to assemble HTMLs, we need to use template inheritance. For example, define a layout.html for the basic HTML frame and the <head> element, and set {{block "title"}} and {{block "content"}}, other templates inherit layout.html, and populate or override the layout template's blocks of the same name with their own defined blocks.
The following is the content of the layout.html template.
<!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>
For other templates, you can refer to (inherit from) layout.html and define the blocks in the layout.html template.
For example, login.html reads as follows.
{{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}}
article.html also references layout.html:
{{template "layout.html" .}}
{{define "title"}}<h1>{{.Title}}</h1>{{end}}
{{define "content"}}
<p>{{.URL}}</p>
<article>{{.Content}}</article>
{{end}}
We would expect the blocks defined in the login.html template to override the blocks in layout.html when rendering it, and also when rendering the article.html template. But that's not the case, and it's down to the Go text/template implementation. In our implementation of ParseFS(tmplFS, "views/*.html"), suppose article.html is parsed first and its content block is parsed as a template name, then when the login.html template is parsed later and acontent block is also found in it, text/template will overwrite the template of the same name with the later parsed content, so when all the templates are parsed, there is actually only one template named content in our template tree, which is the content defined in the last parsed template file.
Therefore, when we execute the article.html template, it is possible that the content template is not the content defined in this template, but the content defined in other templates.
The community has proposed some solutions to this problem. For example, instead of using a global template, a new template is created each time it is rendered, containing only the contents of layout.html and the sub-template. But this is really tedious. In fact, when Go 1.6 introduced the block directive [1] for text/template, we were able to do what we wanted with the Clone method, with just a few changes to the code above.
// 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)
}
You can see that only the Render function has been modified here. Instead of executing the global template, we will clone it into a new template, and the content block in this new template may not be the one we want, so here we parse the content of a sub-template we will eventually render on top of this global template, so that the content of the newly added sub-template will overwrite the previous, possibly incorrect content. Our target sub-template references layout.html in the global template, which is not conflicted, and since the global template is never executed (we clone a new global template in the Render function each time it is executed), it is also clean. When a template is finally executed, we have a clean layout.html with the content content we want, which is equivalent to generating a new template each time we execute it, which contains only the layout template and sub-templates we need. The idea is the same, but instead of manually generating a new template when executing the template, it's done automatically in the Render function.
Of course, you can also use {{ template }} to refer to other layout templates in the sub-template, as long as these layout templates do not overwrite each other, you just need to specify the name of the target sub-template when executing, and the template engine will automatically use the {{ template }} tag defined in it to find the layout templates for us, which are all in the cloned global template.
[1] https://github.com/golang/go/commit/12dfc3bee482f16263ce4673a0cce399127e2a0d
As far as I understand, when you use ParseGlob(), Gin parses all the matching files and creates a single template object from them. For doing what you want, you'd need two different templates (one for page 1, another for page 2).
Gin documentation says this is a known limitation and points the way to overcome it:
Gin allow by default use only one html.Template. Check a multitemplate render for using features like go 1.6 block template.
Using the multitemplate library, you can write something like this:
render := multitemplate.NewRenderer()
render.AddFromFiles("page1", "templates/base.html", "templates/page1.html")
render.AddFromFiles("page2", "templates/base.html", "templates/page2.html")
router := gin.Default()
router.HTMLRender = render
// Later
ginContext.HTML(200, "page1", gin.H{
"title": "The Wonderful Page One",
})
This requires more manual setup than I'd hope for, but gets the job done.
The easiest way which avoids a map and works in a single template:
base.html
<!DOCTYPE html>
<html lang="en">
<body>
header...
{{block "content" .}}{{end}}
footer...
</body>
</html>
page1.html
{{template "base.html" .}}
{{define "content"}}This is page 1{{end}}
page2.html
{{template "base.html" .}}
{{define "content"}}This is page 2{{end}}
t := template.Must(template.ParseGlob("*.html"))
err := t.ExecuteTemplate(w, "page1.html", context)
err := t.ExecuteTemplate(w, "page2.html", context)

Loading CSS files from a folder in Go

I'm trying to build a small web app, and I'd like to have all my CSS files in one folder, and have them load automatically on all web pages (sort of like the Rails asset pipeline).
I'm using this to serve the css files, but how would I get them to load with all pages?
http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("/css/"))))
One solution is to make use of the html/template functionality, create all your pages to include the same section like below. I would however leave room to add tags to your head by leaving the in each of your pages.
{{define "page_template"}}
<head>
<title>My page template</title>
{{template "template_css"}}
<!-- page specific css if required -->
<link rel="stylesheet" type="text/css" href="/assets/additional.css" />
</head>
... etc ...
And the template_css:
{{define "template_css"}}
<link rel="stylesheet" type="text/css" href="/assets/allpages.css" />
{{end}}
A snippet of code for the template parsing
tp, err := template.ParseFiles("page_template.html", "template_css.tp")
err = tp.ExecuteTemplate(buf, "page_template", templateParameters)
I think it's easy to implement this simple asset pipeline feature, you can use path.filepath to walk through your css directory, read all the css files, generate a temp css file by join all lines together, then serve the client with the generated file
import (
"path/filepath"
"os"
"io/ioutil"
)
func Generate(path string) *os.File{
f,err := ioutil.TempFile("","all")
if err!=nil{
return nil
}
filepath.Walk(path,func(p string,info os.FileInfo,err error)error{
if err!=nil{
return err
}
if !info.IsDir(){
data,err := ioutil.ReadFile(info.Name())
if err!=nil{
return err
}
f.Write(data)
}
return err
})
return f
}

Resources