feat(templates): hot reloading templates
this is supposed to be mostly temporaly until a better templates interface is made
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
comicverse "forge.capytal.company/capytalcode/project-comicverse"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/templates"
|
||||
"forge.capytal.company/loreddev/x/tinyssert"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
@@ -112,6 +113,9 @@ func main() {
|
||||
d := os.DirFS("./assets")
|
||||
opts = append(opts, comicverse.WithAssets(d))
|
||||
|
||||
t := templates.NewHotTemplates(os.DirFS("./templates"))
|
||||
opts = append(opts, comicverse.WithTemplates(t))
|
||||
|
||||
opts = append(opts, comicverse.WithDevelopmentMode())
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ func New(cfg Config, opts ...Option) (http.Handler, error) {
|
||||
bucket: cfg.Bucket,
|
||||
|
||||
assets: assets.Files(),
|
||||
templates: templates.Templates(),
|
||||
developmentMode: false,
|
||||
ctx: context.Background(),
|
||||
|
||||
@@ -50,6 +51,9 @@ func New(cfg Config, opts ...Option) (http.Handler, error) {
|
||||
if app.assets == nil {
|
||||
return nil, errors.New("static files must not be a nil interface")
|
||||
}
|
||||
if app.templates == nil {
|
||||
return nil, errors.New("templates must not be a nil interface")
|
||||
}
|
||||
|
||||
if app.ctx == nil {
|
||||
return nil, errors.New("context must not be a nil interface")
|
||||
@@ -82,6 +86,10 @@ func WithAssets(f fs.FS) Option {
|
||||
return func(app *app) { app.assets = joinedfs.Join(f, app.assets) }
|
||||
}
|
||||
|
||||
func WithTemplates(t templates.ITemplate) Option {
|
||||
return func(app *app) { app.templates = t }
|
||||
}
|
||||
|
||||
func WithAssertions(a tinyssert.Assertions) Option {
|
||||
return func(app *app) { app.assert = a }
|
||||
}
|
||||
@@ -102,6 +110,7 @@ type app struct {
|
||||
ctx context.Context
|
||||
|
||||
assets fs.FS
|
||||
templates templates.ITemplate
|
||||
developmentMode bool
|
||||
|
||||
handler http.Handler
|
||||
@@ -147,9 +156,9 @@ func (app *app) setup() error {
|
||||
app.handler, err = router.New(router.Config{
|
||||
Service: service,
|
||||
|
||||
Templates: templates.Templates(),
|
||||
Templates: app.templates,
|
||||
DisableCache: app.developmentMode,
|
||||
Assets: app.assets,
|
||||
Assets: app.assets,
|
||||
|
||||
Assertions: app.assert,
|
||||
Logger: app.logger.WithGroup("router"),
|
||||
|
||||
2
makefile
2
makefile
@@ -13,7 +13,7 @@ dev/server:
|
||||
--build.cmd "go build -o tmp/bin/main ./cmd" \
|
||||
--build.bin "tmp/bin/main" \
|
||||
--build.exclude_dir "node_modules" \
|
||||
--build.include_ext "go,html" \
|
||||
--build.include_ext "go" \
|
||||
--build.stop_on_error "false" \
|
||||
--misc.clean_on_exit true \
|
||||
-- -dev -port $(PORT) -hostname 0.0.0.0
|
||||
|
||||
@@ -2,12 +2,12 @@ package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"forge.capytal.company/capytalcode/project-comicverse/service"
|
||||
"forge.capytal.company/capytalcode/project-comicverse/templates"
|
||||
"forge.capytal.company/loreddev/x/smalltrip"
|
||||
"forge.capytal.company/loreddev/x/smalltrip/exception"
|
||||
"forge.capytal.company/loreddev/x/smalltrip/middleware"
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
type router struct {
|
||||
service *service.Service
|
||||
|
||||
templates *template.Template
|
||||
templates templates.ITemplate
|
||||
assets fs.FS
|
||||
cache bool
|
||||
|
||||
@@ -59,7 +59,7 @@ func New(cfg Config) (http.Handler, error) {
|
||||
type Config struct {
|
||||
Service *service.Service
|
||||
|
||||
Templates *template.Template
|
||||
Templates templates.ITemplate
|
||||
Assets fs.FS
|
||||
DisableCache bool
|
||||
|
||||
|
||||
@@ -1,39 +1,77 @@
|
||||
package templates
|
||||
|
||||
// INFO: This will probably become a new lib in loreddev/x at some point
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
var (
|
||||
patterns = []string{"*.html", "layouts/*.html"}
|
||||
functions = template.FuncMap{
|
||||
"args": func(pairs ...any) (map[string]any, error) {
|
||||
if len(pairs)%2 != 0 {
|
||||
return nil, errors.New("misaligned map in template arguments")
|
||||
}
|
||||
|
||||
m := make(map[string]any, len(pairs)/2)
|
||||
|
||||
for i := 0; i < len(pairs); i += 2 {
|
||||
key, ok := pairs[i].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot use type %T as map key", pairs[i])
|
||||
}
|
||||
|
||||
m[key] = pairs[i+1]
|
||||
}
|
||||
|
||||
return m, nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
//go:embed *.html layouts/*.html
|
||||
var embedded embed.FS
|
||||
|
||||
var temps = template.Must(template.New("templates").Funcs(template.FuncMap{
|
||||
"args": func(pairs ...any) (map[string]any, error) {
|
||||
if len(pairs)%2 != 0 {
|
||||
return nil, errors.New("misaligned map in template arguments")
|
||||
}
|
||||
|
||||
m := make(map[string]any, len(pairs)/2)
|
||||
|
||||
for i := 0; i < len(pairs); i += 2 {
|
||||
key, ok := pairs[i].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot use type %T as map key", pairs[i])
|
||||
}
|
||||
|
||||
m[key] = pairs[i+1]
|
||||
}
|
||||
|
||||
return m, nil
|
||||
},
|
||||
}).ParseFS(embedded,
|
||||
"*.html",
|
||||
"layouts/*.html",
|
||||
))
|
||||
var temps = template.Must(template.New("templates").Funcs(functions).ParseFS(embedded, patterns...))
|
||||
|
||||
func Templates() *template.Template {
|
||||
return temps
|
||||
return temps // TODO: Support for local templates/hot-reloading without rebuild
|
||||
}
|
||||
|
||||
func NewHotTemplates(fsys fs.FS) *HotTemplate {
|
||||
return &HotTemplate{
|
||||
fs: fsys,
|
||||
}
|
||||
}
|
||||
|
||||
type HotTemplate struct {
|
||||
fs fs.FS
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
func (t *HotTemplate) Execute(wr io.Writer, data any) error {
|
||||
te, err := template.New("hot-templates").Funcs(functions).ParseFS(t.fs, patterns...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return te.Execute(wr, data)
|
||||
}
|
||||
|
||||
func (t *HotTemplate) ExecuteTemplate(wr io.Writer, name string, data any) error {
|
||||
te, err := template.New("hot-templates").Funcs(functions).ParseFS(t.fs, patterns...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return te.ExecuteTemplate(wr, name, data)
|
||||
}
|
||||
|
||||
type ITemplate interface {
|
||||
Execute(wr io.Writer, data any) error
|
||||
ExecuteTemplate(wr io.Writer, name string, data any) error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user