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"
"reflect"
"runtime"
"strings"
)
type Headers = map[string]string
type Partial struct {
Headers *Headers
Root string
Root *Element
}
func (p *Partial) Render() string {
return p.Root
func (p *Partial) Render(builder *strings.Builder) {
p.Root.Render(builder)
}
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{
HttpMethod: httpMethod,
Root: root,
}
}
func NewPartialWithHeaders(headers *Headers, root Ren) *Partial {
func NewPartialWithHeaders(headers *Headers, root *Element) *Partial {
return &Partial{
Headers: headers,
Root: root.Render(),
Root: root,
}
}
func NewPartial(root Ren) *Partial {
func NewPartial(root *Element) *Partial {
return &Partial{
Root: root.Render(),
Root: root,
}
}

View file

@ -69,12 +69,12 @@ func Increment(amount int) JsCommand {
func SetInnerHtml(r Ren) JsCommand {
// 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 {
// 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 {

View file

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

View file

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

View file

@ -14,7 +14,7 @@ type Element struct {
children []Ren
}
func (node *Element) AppendChild(child Ren) Ren {
func (node *Element) AppendChild(child Ren) *Element {
node.children = append(node.children, child)
return node
}
@ -363,7 +363,7 @@ func List[T any](items []T, mapper func(item T, index int) *Element) *Element {
return node
}
func Fragment(children ...Ren) Ren {
func Fragment(children ...Ren) *Element {
return &Element{
tag: "",
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)
}
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") == "" {
return Empty()
}

View file

@ -4,10 +4,11 @@ import (
"encoding/json"
"github.com/labstack/echo/v4"
"net/url"
"strings"
)
type Ren interface {
Render() string
Render(builder *strings.Builder)
}
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.OobSwap(ctx, Task(task, false)))
return h.NewPartial(
h.OobSwap(ctx, Task(task, false)))
}
func EditNameForm(ctx *h.RequestContext) *h.Partial {