reduce allocs by re-using string builder

This commit is contained in:
maddalax 2024-09-19 20:34:50 -05:00
parent faf33ef93d
commit 93bb1d64ba
7 changed files with 40 additions and 48 deletions

View file

@ -5,17 +5,18 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"runtime" "runtime"
"strings"
) )
type Headers = map[string]string type Headers = map[string]string
type Partial struct { type Partial struct {
Headers *Headers Headers *Headers
Root string Root *Element
} }
func (p *Partial) Render() string { func (p *Partial) Render(builder *strings.Builder) {
return p.Root p.Root.Render(builder)
} }
type Page struct { type Page struct {
@ -30,23 +31,23 @@ func NewPage(root Ren) *Page {
} }
} }
func NewPageWithHttpMethod(httpMethod string, root Ren) *Page { func NewPageWithHttpMethod(httpMethod string, root *Element) *Page {
return &Page{ return &Page{
HttpMethod: httpMethod, HttpMethod: httpMethod,
Root: root, Root: root,
} }
} }
func NewPartialWithHeaders(headers *Headers, root Ren) *Partial { func NewPartialWithHeaders(headers *Headers, root *Element) *Partial {
return &Partial{ return &Partial{
Headers: headers, Headers: headers,
Root: root.Render(), Root: root,
} }
} }
func NewPartial(root Ren) *Partial { func NewPartial(root *Element) *Partial {
return &Partial{ return &Partial{
Root: root.Render(), Root: root,
} }
} }

View file

@ -69,12 +69,12 @@ func Increment(amount int) JsCommand {
func SetInnerHtml(r Ren) JsCommand { func SetInnerHtml(r Ren) JsCommand {
// language=JavaScript // language=JavaScript
return JsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", r.Render())} return JsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", Render(r))}
} }
func SetOuterHtml(r Ren) JsCommand { func SetOuterHtml(r Ren) JsCommand {
// language=JavaScript // language=JavaScript
return JsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", r.Render())} return JsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", Render(r))}
} }
func AddAttribute(name, value string) JsCommand { func AddAttribute(name, value string) JsCommand {

View file

@ -2,13 +2,15 @@ package h
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
) )
func Render(node Ren) string { func Render(node Ren) string {
start := time.Now() start := time.Now()
html := node.Render() builder := &strings.Builder{}
node.Render(builder)
duration := time.Since(start) duration := time.Since(start)
fmt.Printf("render took %d microseconds\n", duration.Microseconds()) fmt.Printf("render took %d microseconds\n", duration.Microseconds())
return html return builder.String()
} }

View file

@ -5,15 +5,13 @@ import (
"strings" "strings"
) )
func (node *Element) Render() string { func (node *Element) Render(builder *strings.Builder) {
builder := &strings.Builder{}
// some elements may not have a tag, such as a Fragment // some elements may not have a tag, such as a Fragment
if node.tag != "" { if node.tag != "" {
builder.WriteString("<" + node.tag) builder.WriteString("<" + node.tag)
builder.WriteString(" ") builder.WriteString(" ")
for name, value := range node.attributes { for name, value := range node.attributes {
builder.WriteString(NewAttribute(name, value).Render()) NewAttribute(name, value).Render(builder)
} }
} }
@ -34,9 +32,9 @@ func (node *Element) Render() string {
for _, child := range node.children { for _, child := range node.children {
switch child.(type) { switch child.(type) {
case *AttributeMap: case *AttributeMap:
builder.WriteString(child.(*AttributeMap).Render()) child.Render(builder)
case *LifeCycle: case *LifeCycle:
builder.WriteString(child.(*LifeCycle).Render()) child.Render(builder)
} }
} }
@ -53,52 +51,42 @@ func (node *Element) Render() string {
case *LifeCycle: case *LifeCycle:
continue continue
default: default:
builder.WriteString(child.Render()) child.Render(builder)
} }
} }
if node.tag != "" { if node.tag != "" {
builder.WriteString("</" + node.tag + ">") builder.WriteString("</" + node.tag + ">")
} }
str := builder.String()
return str
} }
func (a *AttributeR) Render() string { func (a *AttributeR) Render(builder *strings.Builder) {
return fmt.Sprintf(`%s="%s"`, a.Name, a.Value) builder.WriteString(fmt.Sprintf(`%s="%s"`, a.Name, a.Value))
} }
func (t *TextContent) Render() string { func (t *TextContent) Render(builder *strings.Builder) {
return t.Content builder.WriteString(t.Content)
} }
func (r *RawContent) Render() string { func (r *RawContent) Render(builder *strings.Builder) {
return r.Content builder.WriteString(r.Content)
} }
func (c *ChildList) Render() string { func (c *ChildList) Render(builder *strings.Builder) {
builder := &strings.Builder{}
for _, child := range c.Children { for _, child := range c.Children {
builder.WriteString(child.Render()) child.Render(builder)
} }
str := builder.String()
return str
} }
func (m *AttributeMap) Render() string { func (m *AttributeMap) Render(builder *strings.Builder) {
builder := &strings.Builder{}
m2 := m.ToMap() m2 := m.ToMap()
for k, v := range m2 { for k, v := range m2 {
builder.WriteString(NewAttribute(k, v).Render()) NewAttribute(k, v).Render(builder)
} }
str := builder.String()
return str
} }
func (l *LifeCycle) Render() string { func (l *LifeCycle) Render(builder *strings.Builder) {
m := make(map[string]string) m := make(map[string]string)
for event, commands := range l.handlers { for event, commands := range l.handlers {
@ -114,8 +102,7 @@ func (l *LifeCycle) Render() string {
children = append(children, Attribute(event, js)) children = append(children, Attribute(event, js))
} }
result := Children(children...).Render() Children(children...).Render(builder)
return result
} }
func (m *AttributeMap) ToMap() map[string]string { func (m *AttributeMap) ToMap() map[string]string {

View file

@ -14,7 +14,7 @@ type Element struct {
children []Ren children []Ren
} }
func (node *Element) AppendChild(child Ren) Ren { func (node *Element) AppendChild(child Ren) *Element {
node.children = append(node.children, child) node.children = append(node.children, child)
return node return node
} }
@ -363,7 +363,7 @@ func List[T any](items []T, mapper func(item T, index int) *Element) *Element {
return node return node
} }
func Fragment(children ...Ren) Ren { func Fragment(children ...Ren) *Element {
return &Element{ return &Element{
tag: "", tag: "",
children: children, children: children,
@ -541,11 +541,11 @@ func NewSwap(selector string, content *Element) SwapArg {
} }
} }
func OobSwap(ctx *RequestContext, content *Element) Ren { func OobSwap(ctx *RequestContext, content *Element) *Element {
return OobSwapWithSelector(ctx, "", content) return OobSwapWithSelector(ctx, "", content)
} }
func OobSwapWithSelector(ctx *RequestContext, selector string, content *Element) Ren { func OobSwapWithSelector(ctx *RequestContext, selector string, content *Element) *Element {
if ctx == nil || ctx.Get("HX-Request") == "" { if ctx == nil || ctx.Get("HX-Request") == "" {
return Empty() return Empty()
} }

View file

@ -4,10 +4,11 @@ import (
"encoding/json" "encoding/json"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"net/url" "net/url"
"strings"
) )
type Ren interface { type Ren interface {
Render() string Render(builder *strings.Builder)
} }
func Ternary[T any](value bool, a T, b T) T { func Ternary[T any](value bool, a T, b T) T {

View file

@ -215,7 +215,8 @@ func UpdateName(ctx *h.RequestContext) *h.Partial {
return h.NewPartial(h.Div(h.Text("failed to update"))) return h.NewPartial(h.Div(h.Text("failed to update")))
} }
return h.NewPartial(h.OobSwap(ctx, Task(task, false))) return h.NewPartial(
h.OobSwap(ctx, Task(task, false)))
} }
func EditNameForm(ctx *h.RequestContext) *h.Partial { func EditNameForm(ctx *h.RequestContext) *h.Partial {