Documentation
¶
Overview ¶
Package temple provides an HTML rendering framework built on top of the html/template package.
Temple is organized around Components and Renderables. A Component is some piece of the HTML document that you want included in the page's output. A Renderable is a Component that gets rendered itself rather than being included in another Component. The homepage of a site is probably a Renderable; the site's navbar is probably a Component, as is the base layout that all pages have in common.
temple also has the concept of a Site. Each server should have a Site, which acts as a singleton for the server and provides the fs.FS containing the templates that Components are using. A Site will also be available at render time, as .Site, so it can hold configuration data used across all pages.
To render a page, pass it to the Render function. The page itself will be made available as .Page within the template, and the Site will be available as .Site.
Components tend to be structs, with properties for whatever data they want to pass to their templates. When a Component relies on another Component, our homepage including a navbar for example, a good practice is to make an instance of the navbar Component a property on the homepage Component struct. That allows the homepage to select, e.g., which link in the navbar is highlighted as active. It's also a good idea to include the navbar Component in the output of a UseComponents method on the homepage Component, so all its methods (the templates it uses, any CSS or JS that it embeds or links to, any Components _it_ relies on...) will all get included whenever the homepage Component is rendered.
Index ¶
- Variables
- func LoggingContext(ctx context.Context, logger *slog.Logger) context.Context
- func Render[SiteType Site, PageType Renderable](ctx context.Context, out io.Writer, site SiteType, page PageType)
- type CSSEmbedder
- type CSSLinker
- type CachedSite
- type Component
- type ComponentUser
- type FuncMapExtender
- type JSEmbedder
- type JSLinker
- type RenderData
- type Renderable
- type ServerErrorPager
- type Site
- type TemplateCacher
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoTemplatePath is returned when a template path is needed, but // none are supplied. ErrNoTemplatePath = errors.New("need at least one template path") // ErrTemplatePatternMatchesNoFiles is returned when a template path is // a pattern, but that pattern doesn't match any files. ErrTemplatePatternMatchesNoFiles = errors.New("pattern matches no files") )
Functions ¶
func LoggingContext ¶
LoggingContext returns a context.Context with the slog.Logger embedded in it in such a way that temple will be able to find it. Passing the returned context.Context to temple functions will let temple write its logging output to that logger.
func Render ¶
func Render[SiteType Site, PageType Renderable](ctx context.Context, out io.Writer, site SiteType, page PageType)
Render renders the passed Renderable to the Writer. If it can't, a server error page is written instead. If the Site implements ServerErrorPager, that will be rendered; if not, a simple text page indicating a server error will be written.
Example (Basic) ¶
package main import ( "context" "log/slog" "os" "impractical.co/temple" ) type MySite struct { // anonymously embedding a *CachedSite makes MySite a Site implementation *temple.CachedSite // a configurable title for our site Title string } type HomePage struct { Layout BaseLayout } func (HomePage) Templates(_ context.Context) []string { return []string{"home.html.tmpl"} } func (h HomePage) UseComponents(_ context.Context) []temple.Component { return []temple.Component{ h.Layout, } } func (HomePage) Key(_ context.Context) string { return "home.html.tmpl" } func (h HomePage) ExecutedTemplate(_ context.Context) string { return h.Layout.BaseTemplate() } type BaseLayout struct { } func (b BaseLayout) Templates(_ context.Context) []string { return []string{b.BaseTemplate()} } func (BaseLayout) BaseTemplate() string { return "base.html.tmpl" } func main() { // normally you'd use something like embed.FS or os.DirFS for this // for example purposes, we're just hardcoding values var templates = staticFS{ "home.html.tmpl": `{{ define "body" }}Hello, world. This is my home page.{{ end }}`, "base.html.tmpl": ` <!doctype html> <html lang="en"> <head> <title>{{ .Site.Title }}</title> </head> <body> {{ block "body" . }}{{ end }} </body> </html>`, } // usually the context comes from the request, but here we're building it from scratch and adding a logger ctx := temple.LoggingContext(context.Background(), slog.Default()) site := MySite{ CachedSite: temple.NewCachedSite(templates), Title: "My Example Site", } page := HomePage{Layout: BaseLayout{}} temple.Render(ctx, os.Stdout, site, page) }
Output: <!doctype html> <html lang="en"> <head> <title>My Example Site</title> </head> <body> Hello, world. This is my home page. </body> </html>
Types ¶
type CSSEmbedder ¶
type CSSEmbedder interface { // EmbedCSS returns the CSS, without <style> tags, that should be // embedded directly in the output HTML. // // If this Component embeds any other Components, it should include // their EmbedCSS output in its own EmbedCSS output. EmbedCSS(context.Context) template.CSS }
CSSEmbedder is an interface that Components can fulfill to include some CSS that should be embedded directly into the rendered HTML. The contents will be made available to the template as .EmbeddedCSS.
type CSSLinker ¶
type CSSLinker interface { // LinkCSS returns a list of URLs to CSS files that should be linked to // from the output HTML. // // If this Component embeds any other Components, it should include // their LinkCSS output in its own LinkCSS output. LinkCSS(context.Context) []string }
CSSLinker is an interface that Components can fulfill to include some CSS that should be loaded through a <link> element in the template. The contents will be made available to the template as .LinkedCSS.
type CachedSite ¶
type CachedSite struct {
// contains filtered or unexported fields
}
CachedSite is an implementation of the Site interface that can be embedded in other Site implementations. It fulfills the Site interface and the TemplateCacher interface, caching templates in memory and exposing the template fs.FS passed to it in NewCachedSite. A CachedSite must be instantiated through NewCachedSite, its empty value is not usable.
func NewCachedSite ¶
func NewCachedSite(templates fs.FS) *CachedSite
NewCachedSite returns a CachedSite instance that is ready to be used.
func (*CachedSite) GetCachedTemplate ¶
GetCachedTemplate returns the cached template associated with the passed key, if one exists. If no template is cached for that key, it returns nil.
It can safely be used by multiple goroutines.
func (*CachedSite) SetCachedTemplate ¶
SetCachedTemplate caches a template for the given key.
It can safely be used by multiple goroutines.
func (*CachedSite) TemplateDir ¶
func (s *CachedSite) TemplateDir(_ context.Context) fs.FS
TemplateDir returns an fs.FS containing all the templates needed to render a Site's Components. In this case, we just pass back what the consumer passed in.
type Component ¶
type Component interface { // Templates returns a list of filepaths to html/template contents that // need to be parsed before the component can be rendered. Templates(context.Context) []string }
Component is an interface for a UI component that can be rendered to HTML.
type ComponentUser ¶
type ComponentUser interface { // UseComponents returns the Components that this Component relies on. UseComponents(context.Context) []Component }
ComponentUser is an interface that a Component can optionally implement to list the Components that it relies upon. These Components will automatically have the appropriate methods called if they implement any of the optional interfaces. A Component that uses other Components but doesn't return them with this interface is responsible for including the output of any optional interfaces that they implement in its own implementation of those interfaces and including their Templates output in its own Templates output.
type FuncMapExtender ¶
type FuncMapExtender interface { // FuncMap returns an html/template.FuncMap containing all the // functions that the Component is adding to the FuncMap. FuncMap(context.Context) template.FuncMap }
FuncMapExtender is an interface that Components can fulfill to add to the map of functions available to them when rendering.
type JSEmbedder ¶
type JSEmbedder interface { // EmbedJS returns the JavaScript, without <script> tags, that should // be embedded directly in the output HTML. EmbedJS(context.Context) template.JS }
JSEmbedder is an interface that Components can fulfill to include some JavaScript that should be embedded directly into the rendered HTML. The contents will be made available to the template as .EmbeddedJS.
type JSLinker ¶
type JSLinker interface { // LinkJS returns a list of URLs to JavaScript files that should be // linked to from the output HTML. // // If this Component embeds any other Components, it should include // their LinkJS output in its own LinkJS output. LinkJS(context.Context) []string }
JSLinker is an interface that Components can fulfill to include some JavaScript that should be loaded separately from the HTML document, using a <script> tag with a src attribute. The contents will be made available to the template as .LinkedJS.
type RenderData ¶
type RenderData[SiteType Site, PageType Renderable] struct { // Site is an instance of the Site type, containing all the // configuration and information about a Site. This can be used to // avoid passing global configuration options to every single page. Site SiteType // Page is the information for a specific page, embedded in that page's // Renderable type. Page PageType // EmbeddedJS is the result of calling EmbedJS on the Renderable, if // the Renderable supports the JSEmbedder interface. EmbeddedJS template.JS // EmbeddedCSS is the result of calling EmbedCSS on the Renderable, if // the Renderable supports the CSSEmbedder interface. EmbeddedCSS template.CSS // LinkedJS is the result of calling LinkJS on the Renderable, if the // Renderable supports the JSLinker interface. LinkedJS []string // LinkedCSS is the result of calling LinkCSS on the Renderable, if the // Renderable supports the CSSLinker interface. LinkedCSS []string }
RenderData is the data that is passed to a page when rendering it.
type Renderable ¶
type Renderable interface { Component // Key is a unique key to use when caching this page so it doesn't need // to be re-parsed. A good key is consistent, but unique per Renderable. Key(context.Context) string // ExecutedTemplate is the template that needs to actually be executed // when rendering the page. // // This is usually not the template for the Component defining the // page; it's usually the "base" template that Component defining the // page fills blocks in. ExecutedTemplate(context.Context) string }
Renderable is an interface for a page that can be passed to Render. It defines a single logical page of the application, composed of one or more Components. It should contain all the information needed to render the Components to HTML.
type ServerErrorPager ¶
type ServerErrorPager interface {
ServerErrorPage(ctx context.Context) Renderable
}
ServerErrorPager defines an interface that Sites can optionally implement. If a Site implements ServerErrorPager and Render encounters an error, the output of ServerErrorPage will be rendered.
type Site ¶
type Site interface { // TemplateDir returns an fs.FS containing all the templates needed to // render every Renderable on the Site. // // The path to templates within the fs.FS should match the output of // TemplatePaths for Components. TemplateDir(ctx context.Context) fs.FS }
Site is an interface for the singleton that will be used to render HTML. Consumers should use it to store any clients or cross-request state they need, and use it to render Renderables.
A Site needs to be able to surface the templates it relies on as an fs.FS.
type TemplateCacher ¶
type TemplateCacher interface { // GetCachedTemplate returns the *template.Template specified by the // passed key. It should return nil if the template hasn't been cached // yet. GetCachedTemplate(ctx context.Context, key string) *template.Template // SetCachedTemplate stores the passed *template.Template under the // passed key, for later retrieval with GetCachedTemplate. // // Any errors encountered should be logged, but as this is a // best-effort operation, will not be surfaced outside the function. SetCachedTemplate(ctx context.Context, key string, tmpl *template.Template) }
TemplateCacher is an optional interface for Sites. Those fulfilling it can cache their template parsing using the output of Key from each Renderable to save on the overhead of parsing the template each time. The templates being parsed for a given key should be the same every time, as should the template getting executed, but the data may still be different, so the output HTML cannot be safely presumed to be cacheable.