renderer

package
v1.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 5, 2024 License: Apache-2.0 Imports: 20 Imported by: 6

Documentation

Overview

Package render exposes high-performance HTML and JSON rendering functionality. Most use cases can use the Renderer without modification. More advanced use cases can customize template functions and error handling.

The renderer accepts a filesystem (fs.FS). In most cases, this will be a filesystem on disk. However, it accepts the FS interface for testing and embed purposes. Because embed does not perform hot reloading, you may want to use a different fs for development versus production:

//go:embed assets assets/**/*
var _assetsFS embed.FS

func AssetsFS() fs.FS {
  // In dev, just read directly from disk
  if v, _ := strconv.ParseBool(os.Getenv("DEV_MODE")); v {
    return os.DirFS("./assets")
  }

  // Otherwise use the embedded fs
  return _assetsFS
}

The the renderer includes some prebuilt functions, including static asset parsing for CSS and Javascript files. The renderer assumes these files exist in a `static/css` and `static/js` directory at the root of the provided filesystem.

assets/
  \_ static/
    \_ css/
    \_ js/

To render the include tags in a template:

{{ define "home" }}
  {{ cssIncludeTag "css/*.css" }}
  {{ jsIncludeTag "js/index.js" }}
{{ end }}
Example
package main

import (
	"context"
	"net/http"
	"sort"
	"testing/fstest"
	"time"

	"github.com/abcxyz/pkg/renderer"
)

type Server struct {
	db map[string]any
	h  *renderer.Renderer
}

func NewServer(ctx context.Context) *Server {
	// Normally this would come from the filesystem, but to make the test fit in a
	// single file...
	fsys := fstest.MapFS{
		"users/index.html": &fstest.MapFile{
			Data: []byte(`
				{{ define "users/index" }}
					<ul>
						{{ range .Users }}
							<li>{{ . | toUpper }}</li>
						{{ end }}
					</ul>
				{{ end }}
			`),
			Mode: 0o600,
		},
	}

	h, err := renderer.New(ctx, fsys, renderer.WithDebug(true))
	if err != nil {
		panic(err)
	}

	return &Server{
		db: make(map[string]any),
		h:  h,
	}
}

func (s *Server) Routes() http.Handler {
	mux := http.NewServeMux()
	mux.Handle("/users.html", s.HandleUsersIndex())
	mux.Handle("/users.json", s.HandleUsersAPI())
	return mux
}

func (s *Server) HandleUsersIndex() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s.h.RenderHTML(w, "users/index", s.users())
	})
}

func (s *Server) HandleUsersAPI() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		s.h.RenderJSON(w, 200, s.users())
	})
}

func (s *Server) users() []string {
	users := make([]string, 0, len(s.db))
	for k := range s.db {
		users = append(users, k)
	}
	sort.Strings(users)
	return users
}

func main() {
	s := NewServer(context.Background())

	srv := &http.Server{
		Addr:    ":8080",
		Handler: s.Routes(),

		ReadHeaderTimeout: 2 * time.Second,
	}
	_ = srv.ListenAndServe()
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*Renderer) *Renderer

Option is an interface for options to creating a renderer.

func WithDebug

func WithDebug(v bool) Option

WithDebug configures debugging on the renderer.

func WithOnError

func WithOnError(fn func(err error)) Option

WithOnError overwrites the onError handler with the given function. This handler is invoked when an irrecoverable error occurs while rendering, but information cannot be sent back to the client. For example, if HTTP rendering fails after a partial response has been sent.

func WithTemplateFuncs

func WithTemplateFuncs(fns ...template.FuncMap) Option

WithTemplateFuncs registers additional template functions. The renderer includes many helpful functions, but some applications may wish to inject/define their own template helpers. Functions in this map take precedence over the built-in list. If called with multiple func maps or called multiple times with conflicting keys, the last key takes precedence. To delete an entry, supply a key with a nil value.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer is responsible for rendering various content and templates like HTML and JSON responses. This implementation caches templates and uses a pool of buffers.

func New

func New(ctx context.Context, fsys fs.FS, opts ...Option) (*Renderer, error)

New creates a new renderer with the given details.

func NewTesting added in v0.4.0

func NewTesting(ctx context.Context, tb TestingFatalf, fsys fs.FS, opts ...Option) *Renderer

NewTesting is a helper function to create a renderer suitable for injection in tests. It calls t.Fatal if setup fails.

func (*Renderer) RenderHTML

func (r *Renderer) RenderHTML(w http.ResponseWriter, tmpl string, data any)

RenderHTML calls RenderHTMLStatus with a http.StatusOK (200).

func (*Renderer) RenderHTMLStatus

func (r *Renderer) RenderHTMLStatus(w http.ResponseWriter, code int, tmpl string, data any)

RenderHTMLStatus renders the given HTML template by name. It attempts to gracefully handle any rendering errors to avoid partial responses sent to the response by writing to a buffer first, then flushing the buffer to the response.

If template rendering fails, a generic 500 page is returned. In dev mode, the error is included on the page. If flushing the buffer to the response fails, an error is logged, but no recovery is attempted.

The buffers are fetched via a sync.Pool to reduce allocations and improve performance.

func (*Renderer) RenderJSON

func (r *Renderer) RenderJSON(w http.ResponseWriter, code int, data any)

RenderJSON renders the interface as JSON. It attempts to gracefully handle any rendering errors to avoid partial responses sent to the response by writing to a buffer first, then flushing the buffer to the response.

If the provided data is nil and the response code is a 2xx, the body will be empty. If the code is not a 2xx, the response will be of the format `{"error":"<val>"}` where val is the lowercase, JSON-escaped http.StatusText for the provided code.

If rendering fails, a generic 500 JSON response is returned. In dev mode, the error is included in the payload. If flushing the buffer to the response fails, an error is logged, but no recovery is attempted.

The buffers are fetched via a sync.Pool to reduce allocations and improve performance.

type TestingFatalf added in v0.4.0

type TestingFatalf interface {
	Fatalf(format string, args ...any)
}

TestingFatalf is an interface that is satisfied by testing.TB. It exists to avoid depending on the testing package at runtime.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL