Prebuild templates (base.html + content) at startup to avoid a file parse every page load

This commit is contained in:
Maximilian 2023-12-22 21:03:15 -06:00
parent 3ffd548623
commit b30af86e58
5 changed files with 79 additions and 32 deletions

View File

@ -23,7 +23,8 @@ type Configuration struct {
} }
Template struct { Template struct {
BaseName string `json:"BaseTemplateName"` BaseName string `json:"BaseTemplateName"`
ContentPath string `json:"ContentPath"`
} }
} }

View File

@ -22,7 +22,7 @@ func (g *Get) ShowHome(w http.ResponseWriter, _ *http.Request) {
Test: "Hello World!", Test: "Hello World!",
} }
templating.RenderTemplate(g.App, w, "templates/pages/home.html", data) templating.RenderTemplate(w, "templates/pages/home.html", data)
} }
func (g *Get) ShowRegister(w http.ResponseWriter, r *http.Request) { func (g *Get) ShowRegister(w http.ResponseWriter, r *http.Request) {
@ -39,7 +39,7 @@ func (g *Get) ShowRegister(w http.ResponseWriter, r *http.Request) {
CsrfToken: CsrfToken, CsrfToken: CsrfToken,
} }
templating.RenderTemplate(g.App, w, "templates/pages/register.html", data) templating.RenderTemplate(w, "templates/pages/register.html", data)
} }
func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) { func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
@ -56,7 +56,7 @@ func (g *Get) ShowLogin(w http.ResponseWriter, r *http.Request) {
CsrfToken: CsrfToken, CsrfToken: CsrfToken,
} }
templating.RenderTemplate(g.App, w, "templates/pages/login.html", data) templating.RenderTemplate(w, "templates/pages/login.html", data)
} }
func (g *Get) Logout(w http.ResponseWriter, r *http.Request) { func (g *Get) Logout(w http.ResponseWriter, r *http.Request) {

View File

@ -12,6 +12,7 @@
"HttpPort": "8090" "HttpPort": "8090"
}, },
"Template": { "Template": {
"BaseTemplateName": "templates/base.html" "BaseTemplateName": "templates/base.html",
"ContentPath": "templates"
} }
} }

View File

@ -6,6 +6,7 @@ import (
"GoWeb/database" "GoWeb/database"
"GoWeb/models" "GoWeb/models"
"GoWeb/routes" "GoWeb/routes"
"GoWeb/templating"
"context" "context"
"embed" "embed"
"errors" "errors"
@ -67,6 +68,13 @@ func main() {
routes.Get(&appLoaded) routes.Get(&appLoaded)
routes.Post(&appLoaded) routes.Post(&appLoaded)
// Prepare templates
err = templating.BuildPages(&appLoaded)
if err != nil {
slog.Error("error building templates: " + err.Error())
os.Exit(1)
}
// Start server // Start server
server := &http.Server{Addr: appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port} server := &http.Server{Addr: appLoaded.Config.Listen.Ip + ":" + appLoaded.Config.Listen.Port}
go func() { go func() {

View File

@ -2,45 +2,82 @@ package templating
import ( import (
"GoWeb/app" "GoWeb/app"
"fmt"
"html/template" "html/template"
"io/fs"
"log/slog" "log/slog"
"net/http" "net/http"
) )
// RenderTemplate renders and serves a template from the embedded filesystem optionally with given data var templates = make(map[string]*template.Template) // This is only used here, does not need to be in app.App
func RenderTemplate(app *app.App, w http.ResponseWriter, contentPath string, data any) {
templatePath := app.Config.Template.BaseName
templateContent, err := app.Res.ReadFile(templatePath) func BuildPages(app *app.App) error {
basePath := app.Config.Template.BaseName
baseContent, err := app.Res.ReadFile(basePath)
if err != nil { if err != nil {
return fmt.Errorf("error reading base file: %w", err)
}
base, err := template.New(basePath).Parse(string(baseContent)) // Sets filepath as name and parses content
if err != nil {
return fmt.Errorf("error parsing base file: %w", err)
}
readFilesRecursively := func(fsys fs.FS, root string) ([]string, error) {
var files []string
err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("error walking the path %q: %w", path, err)
}
if !d.IsDir() {
files = append(files, path)
}
return nil
})
return files, err
}
// Get all file paths in the directory tree
filePaths, err := readFilesRecursively(app.Res, app.Config.Template.ContentPath)
if err != nil {
return fmt.Errorf("error reading files recursively: %w", err)
}
for _, contentPath := range filePaths { // Create a new template base + content for each page
content, err := app.Res.ReadFile(contentPath)
if err != nil {
return fmt.Errorf("error reading content file %s: %w", contentPath, err)
}
t, err := base.Clone()
if err != nil {
return fmt.Errorf("error cloning base template: %w", err)
}
_, err = t.Parse(string(content))
if err != nil {
return fmt.Errorf("error parsing content: %w", err)
}
templates[contentPath] = t
}
return nil
}
func RenderTemplate(w http.ResponseWriter, contentPath string, data any) {
t, ok := templates[contentPath]
if !ok {
err := fmt.Errorf("template not found for path: %s", contentPath)
slog.Error(err.Error()) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, "Template not found", 404)
return return
} }
t, err := template.New(templatePath).Parse(string(templateContent)) err := t.Execute(w, data) // Execute prebuilt template with dynamic data
if err != nil {
slog.Error(err.Error())
http.Error(w, err.Error(), 500)
return
}
content, err := app.Res.ReadFile(contentPath)
if err != nil {
slog.Error(err.Error())
http.Error(w, err.Error(), 500)
return
}
t, err = t.Parse(string(content))
if err != nil {
slog.Error(err.Error())
http.Error(w, err.Error(), 500)
return
}
err = t.Execute(w, data)
if err != nil { if err != nil {
err = fmt.Errorf("error executing template: %w", err)
slog.Error(err.Error()) slog.Error(err.Error())
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return