mirror of
https://github.com/miniflux/v2.git
synced 2025-08-26 18:21:01 +00:00
Instead of blindly compiling all the common/ templates for every view/ ones, let's be explicit about the dependencies. This should significantly decrease the resident memory consumption, as ParseTemplate is responsible for ~10M of the current 11M of heap memory on my instance, so any win there is interesting. This will also allow better factorization of templates, now that everything is explicit. Another side-effect is that it'll make testing easier, as we now have a comprehensive list of views/ templates affected by a change in a file in common/
116 lines
4.2 KiB
Go
116 lines
4.2 KiB
Go
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package template // import "miniflux.app/v2/internal/template"
|
|
|
|
import (
|
|
"bytes"
|
|
"embed"
|
|
"html/template"
|
|
"time"
|
|
|
|
"miniflux.app/v2/internal/locale"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
//go:embed templates/common/*.html
|
|
var commonTemplateFiles embed.FS
|
|
|
|
//go:embed templates/views/*.html
|
|
var viewTemplateFiles embed.FS
|
|
|
|
// Engine handles the templating system.
|
|
type Engine struct {
|
|
templates map[string]*template.Template
|
|
funcMap *funcMap
|
|
}
|
|
|
|
// NewEngine returns a new template engine.
|
|
func NewEngine(router *mux.Router) *Engine {
|
|
return &Engine{
|
|
templates: make(map[string]*template.Template),
|
|
funcMap: &funcMap{router},
|
|
}
|
|
}
|
|
|
|
func (e *Engine) ParseTemplates() {
|
|
funcMap := e.funcMap.Map()
|
|
templates := map[string][]string{ // this isn't a global variable so that it can be garbage-collected.
|
|
"about.html": {"layout.html", "settings_menu.html"},
|
|
"add_subscription.html": {"feed_menu.html", "layout.html", "settings_menu.html"},
|
|
"api_keys.html": {"layout.html", "settings_menu.html"},
|
|
"bookmark_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"categories.html": {"layout.html"},
|
|
"category_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"category_feeds.html": {"feed_list.html", "layout.html"},
|
|
"choose_subscription.html": {"feed_menu.html", "layout.html"},
|
|
"create_api_key.html": {"layout.html", "settings_menu.html"},
|
|
"create_category.html": {"layout.html"},
|
|
"create_user.html": {"layout.html", "settings_menu.html"},
|
|
"edit_category.html": {"layout.html", "settings_menu.html"},
|
|
"edit_feed.html": {"layout.html"},
|
|
"edit_user.html": {"layout.html", "settings_menu.html"},
|
|
"entry.html": {"layout.html"},
|
|
"feed_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"feeds.html": {"feed_list.html", "feed_menu.html", "item_meta.html", "layout.html", "pagination.html"},
|
|
"history_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"import.html": {"feed_menu.html", "layout.html"},
|
|
"integrations.html": {"layout.html", "settings_menu.html"},
|
|
"login.html": {"layout.html"},
|
|
"offline.html": {},
|
|
"search.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"sessions.html": {"layout.html", "settings_menu.html"},
|
|
"settings.html": {"layout.html", "settings_menu.html"},
|
|
"shared_entries.html": {"layout.html", "pagination.html"},
|
|
"tag_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"unread_entries.html": {"item_meta.html", "layout.html", "pagination.html"},
|
|
"users.html": {"layout.html", "settings_menu.html"},
|
|
"webauthn_rename.html": {"layout.html"},
|
|
}
|
|
|
|
for name, dependencies := range templates {
|
|
tpl := template.New("").Funcs(funcMap)
|
|
for _, dependency := range dependencies {
|
|
template.Must(tpl.ParseFS(commonTemplateFiles, "templates/common/"+dependency))
|
|
}
|
|
e.templates[name] = template.Must(tpl.ParseFS(viewTemplateFiles, "templates/views/"+name))
|
|
}
|
|
|
|
// Sanity check to ensure that all templates are correctly declared in `templates`.
|
|
if entries, err := viewTemplateFiles.ReadDir("templates/views"); err == nil {
|
|
for _, entry := range entries {
|
|
if _, ok := e.templates[entry.Name()]; !ok {
|
|
panic("Template " + entry.Name() + " isn't declared in ParseTemplates")
|
|
}
|
|
}
|
|
} else {
|
|
panic("Unable to read all embedded views templates")
|
|
}
|
|
}
|
|
|
|
// Render process a template.
|
|
func (e *Engine) Render(name string, data map[string]any) []byte {
|
|
tpl, ok := e.templates[name]
|
|
if !ok {
|
|
panic("The template " + name + " does not exists.")
|
|
}
|
|
|
|
printer := locale.NewPrinter(data["language"].(string))
|
|
|
|
// Functions that need to be declared at runtime.
|
|
tpl.Funcs(template.FuncMap{
|
|
"elapsed": func(timezone string, t time.Time) string {
|
|
return elapsedTime(printer, timezone, t)
|
|
},
|
|
"t": printer.Printf,
|
|
"plural": printer.Plural,
|
|
})
|
|
|
|
var b bytes.Buffer
|
|
if err := tpl.ExecuteTemplate(&b, "base", data); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return b.Bytes()
|
|
}
|