all repos — gomponents @ 3a9a1fd26540ddd0299771a729740cdd88239dde

HTML components in pure Go

Rename and document fork
Alan Pearce alan@alanpearce.eu
Wed, 19 Mar 2025 12:20:24 +0100
commit

3a9a1fd26540ddd0299771a729740cdd88239dde

parent

5eae1eef0f0a090ae569a23c0c52db356a109cfc

D .github/FUNDING.yml
@@ -1,2 +0,0 @@-custom:
-  - "https://maragu.gumroad.com/l/gomponents"
M README.mdREADME.md
@@ -2,10 +2,10 @@ # Tired of complex template languages? 
 <img src="logo.png" alt="Logo" width="300" align="right">
 
-[![GoDoc](https://pkg.go.dev/badge/maragu.dev/gomponents)](https://pkg.go.dev/maragu.dev/gomponents)
-[![Go](https://github.com/maragudk/gomponents/actions/workflows/ci.yml/badge.svg)](https://github.com/maragudk/gomponents/actions/workflows/ci.yml)
-[![codecov](https://codecov.io/gh/maragudk/gomponents/branch/main/graph/badge.svg)](https://codecov.io/gh/maragudk/gomponents)
-[![Go Report Card](https://goreportcard.com/badge/maragu.dev/gomponents)](https://goreportcard.com/report/maragu.dev/gomponents)
+[![GoDoc](https://pkg.go.dev/badge/go.alanpearce.eu/gomponents)](https://pkg.go.dev/go.alanpearce.eu/gomponents)
+[![Go](https://github.com/alanpearce/gomponents/actions/workflows/ci.yml/badge.svg)](https://github.com/alanpearce/gomponents/actions/workflows/ci.yml)
+[![codecov](https://codecov.io/gh/alanpearce/gomponents/branch/main/graph/badge.svg)](https://codecov.io/gh/alanpearce/gomponents)
+[![Go Report Card](https://goreportcard.com/badge/go.alanpearce.eu/gomponents)](https://goreportcard.com/report/go.alanpearce.eu/gomponents)
 
 Try HTML components in pure Go.
 
@@ -14,12 +14,15 @@ They render to HTML 5, and make it easy for you to build reusable components. So you can focus on building your app instead of learning yet another templating language.
 
 ```shell
-go get maragu.dev/gomponents
+go get go.alanpearce.eu/gomponents
 ```
 
-Made with ✨sparkles✨ by [maragu](https://www.maragu.dev/).
+Made with ✨sparkles✨ by [maragu](https://www.maragu.dev/), forked by [alanpearce](https://alanpearce.eu) to add helpers.
+
+## Fork changes
 
-Does your company depend on this project? [Contact me at markus@maragu.dk](mailto:markus@maragu.dk?Subject=Supporting%20your%20project) to discuss options for a one-time or recurring invoice to ensure its continued thriving.
+- `MapWithIndex` and `MapMap` for mapping over slices and maps respectively
+- `If` and `Iff` take an extra argument to render a fallback component when the condition is false
 
 ## Features
 
@@ -43,16 +46,16 @@ ## Usage
 
 ```shell
-go get maragu.dev/gomponents
+go get go.alanpearce.eu/gomponents
 ```
 
 ```go
 package main
 
 import (
-	. "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/components"
-	. "maragu.dev/gomponents/html"
+	. "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/components"
+	. "go.alanpearce.eu/gomponents/html"
 )
 
 func Navbar(authenticated bool, currentPath string) Node {
@@ -120,27 +123,6 @@ I accept code contributions, especially with new HTML elements and attributes.
 I always welcome issues discussing interesting aspects of gomponents, and obviously bug reports and the like.
 But otherwise, I consider gomponents pretty much feature complete.
-
-New features to the core library are unlikely to be merged, since I like keeping it simple and the API small.
-In particular, new functions around collections (similar to `Map`) or flow control (`IfElse`/`Else`) will not be added.
-`Map` was introduced before generics where a thing, and I think it's better to start using generic functions
-from the stdlib or other libraries, instead of adding gomponents-specific variations of them to this library.
-
-If there's something missing that you need, I would recommend to keep small helper functions around in your own projects.
-And if all else fails, you can always use an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE):
-
-```go
-func list(ordered bool) Node {
-	return func() Node {
-		// Do whatever you need to do, imperatively
-		if ordered {
-			return Ol()
-		} else {
-			return Ul()
-		}
-	}()
-}
-```
 
 ### What's up with the specially named elements and attributes?
 
M components/components.gocomponents/components.go
@@ -6,8 +6,8 @@ "io" 	"sort"
 	"strings"
 
-	g "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/html"
+	g "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/html"
 )
 
 // HTML5Props for [HTML5].
M components/components_test.gocomponents/components_test.go
@@ -4,10 +4,10 @@ import ( 	"os"
 	"testing"
 
-	g "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/components"
-	. "maragu.dev/gomponents/html"
-	"maragu.dev/gomponents/internal/assert"
+	g "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/components"
+	. "go.alanpearce.eu/gomponents/html"
+	"go.alanpearce.eu/gomponents/internal/assert"
 )
 
 func TestHTML5(t *testing.T) {
@@ -20,16 +20,27 @@ Head:        []g.Node{Link(Rel("stylesheet"), Href("/hat.css"))}, 			Body:        []g.Node{Div()},
 		})
 
-		assert.Equal(t, `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Hat</title><meta name="description" content="Love hats."><link rel="stylesheet" href="/hat.css"></head><body><div></div></body></html>`, e)
+		assert.Equal(
+			t,
+			`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Hat</title><meta name="description" content="Love hats."><link rel="stylesheet" href="/hat.css"></head><body><div></div></body></html>`,
+			e,
+		)
 	})
 
-	t.Run("returns no language, description, and extra head/body elements if empty", func(t *testing.T) {
-		e := HTML5(HTML5Props{
-			Title: "Hat",
-		})
+	t.Run(
+		"returns no language, description, and extra head/body elements if empty",
+		func(t *testing.T) {
+			e := HTML5(HTML5Props{
+				Title: "Hat",
+			})
 
-		assert.Equal(t, `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Hat</title></head><body></body></html>`, e)
-	})
+			assert.Equal(
+				t,
+				`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Hat</title></head><body></body></html>`,
+				e,
+			)
+		},
+	)
 
 	t.Run("returns an html5 document template with additional HTML attributes", func(t *testing.T) {
 		e := HTML5(HTML5Props{
@@ -41,7 +52,11 @@ Body:        []g.Node{Div()}, 			HTMLAttrs:   []g.Node{Class("h-full"), ID("htmlid")},
 		})
 
-		assert.Equal(t, `<!doctype html><html lang="en" class="h-full" id="htmlid"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Hat</title><meta name="description" content="Love hats."><link rel="stylesheet" href="/hat.css"></head><body><div></div></body></html>`, e)
+		assert.Equal(
+			t,
+			`<!doctype html><html lang="en" class="h-full" id="htmlid"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Hat</title><meta name="description" content="Love hats."><link rel="stylesheet" href="/hat.css"></head><body><div></div></body></html>`,
+			e,
+		)
 	})
 }
 
M go.modgo.mod
@@ -1,3 +1,3 @@-module "maragu.dev/gomponents"
+module go.alanpearce.eu/gomponents
 
 go 1.18
M gomponents_test.gogomponents_test.go
@@ -8,8 +8,8 @@ "os" 	"strings"
 	"testing"
 
-	g "maragu.dev/gomponents"
-	"maragu.dev/gomponents/internal/assert"
+	g "go.alanpearce.eu/gomponents"
+	"go.alanpearce.eu/gomponents/internal/assert"
 )
 
 func TestNodeFunc(t *testing.T) {
M html/attributes.gohtml/attributes.go
@@ -1,7 +1,7 @@ package html
 
 import (
-	g "maragu.dev/gomponents"
+	g "go.alanpearce.eu/gomponents"
 )
 
 func Async() g.Node {
@@ -138,7 +138,7 @@ return Data(name, v) }
 
 func SlotAttr(v string) g.Node {
-  return g.Attr("slot", v)
+	return g.Attr("slot", v)
 }
 
 func For(v string) g.Node {
M html/attributes_test.gohtml/attributes_test.go
@@ -4,9 +4,9 @@ import ( 	"fmt"
 	"testing"
 
-	g "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/html"
-	"maragu.dev/gomponents/internal/assert"
+	g "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/html"
+	"go.alanpearce.eu/gomponents/internal/assert"
 )
 
 func TestBooleanAttributes(t *testing.T) {
@@ -126,17 +126,17 @@ {Name: "popover", Func: Popover}, 	}
 
 	for _, test := range tests {
-		t.Run(test.Name + "(no args)", func(t *testing.T) {
+		t.Run(test.Name+"(no args)", func(t *testing.T) {
 			n := g.El("div", test.Func())
 			assert.Equal(t, fmt.Sprintf(`<div %v></div>`, test.Name), n)
 		})
 
-		t.Run(test.Name +"(one arg)", func(t *testing.T) {
+		t.Run(test.Name+"(one arg)", func(t *testing.T) {
 			n := g.El("div", test.Func("hat"))
 			assert.Equal(t, fmt.Sprintf(`<div %v="hat"></div>`, test.Name), n)
 		})
 
-		t.Run(test.Name + "(two args panics)", func(t *testing.T) {
+		t.Run(test.Name+"(two args panics)", func(t *testing.T) {
 			defer func() {
 				if r := recover(); r == nil {
 					t.Errorf("expected a panic")
M html/elements.gohtml/elements.go
@@ -8,7 +8,7 @@ import (
 	"io"
 
-	g "maragu.dev/gomponents"
+	g "go.alanpearce.eu/gomponents"
 )
 
 // Doctype returns a special kind of [g.Node] that prefixes its sibling with the string "<!doctype html>".
M html/elements_test.gohtml/elements_test.go
@@ -6,9 +6,9 @@ "fmt" 	"strings"
 	"testing"
 
-	g "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/html"
-	"maragu.dev/gomponents/internal/assert"
+	g "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/html"
+	"go.alanpearce.eu/gomponents/internal/assert"
 )
 
 type erroringWriter struct{}
M http/handler.gohttp/handler.go
@@ -4,7 +4,7 @@ import (
 	"net/http"
 
-	g "maragu.dev/gomponents"
+	g "go.alanpearce.eu/gomponents"
 )
 
 // Handler is like [http.Handler] but returns a [g.Node] and an error.
M http/handler_test.gohttp/handler_test.go
@@ -7,8 +7,8 @@ "net/http" 	"net/http/httptest"
 	"testing"
 
-	g "maragu.dev/gomponents"
-	ghttp "maragu.dev/gomponents/http"
+	g "go.alanpearce.eu/gomponents"
+	ghttp "go.alanpearce.eu/gomponents/http"
 )
 
 func TestAdapt(t *testing.T) {
@@ -51,18 +51,21 @@ t.Fatal(`body is`, body) 		}
 	})
 
-	t.Run("errors with status code if error implements StatusCode method and renders node", func(t *testing.T) {
-		h := ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (g.Node, error) {
-			return g.El("div"), statusCodeError{http.StatusTeapot}
-		})
-		code, body := get(t, h)
-		if code != http.StatusTeapot {
-			t.Fatal("status code is", code)
-		}
-		if body != "<div></div>" {
-			t.Fatal(`body is`, body)
-		}
-	})
+	t.Run(
+		"errors with status code if error implements StatusCode method and renders node",
+		func(t *testing.T) {
+			h := ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (g.Node, error) {
+				return g.El("div"), statusCodeError{http.StatusTeapot}
+			})
+			code, body := get(t, h)
+			if code != http.StatusTeapot {
+				t.Fatal("status code is", code)
+			}
+			if body != "<div></div>" {
+				t.Fatal(`body is`, body)
+			}
+		},
+	)
 
 	t.Run("errors with 500 if other error and renders node", func(t *testing.T) {
 		h := ghttp.Adapt(func(w http.ResponseWriter, r *http.Request) (g.Node, error) {
M internal/assert/assert.gointernal/assert/assert.go
@@ -5,7 +5,7 @@ import ( 	"strings"
 	"testing"
 
-	g "maragu.dev/gomponents"
+	g "go.alanpearce.eu/gomponents"
 )
 
 // Equal checks for equality between the given expected string and the rendered Node string.
M internal/examples/app/go.modinternal/examples/app/go.mod
@@ -2,4 +2,4 @@ module app 
 go 1.23.2
 
-require maragu.dev/gomponents v1.0.0-beta1
+require go.alanpearce.eu/gomponents v1.0.0-beta1
M internal/examples/app/go.suminternal/examples/app/go.sum
@@ -1,2 +1,2 @@-maragu.dev/gomponents v1.0.0-beta1 h1:I51NqKfrtQC4GxuWShqW5CT5BrfToMEueLD76IhdSXs=
-maragu.dev/gomponents v1.0.0-beta1/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
+go.alanpearce.eu/gomponents v1.0.0-beta1 h1:I51NqKfrtQC4GxuWShqW5CT5BrfToMEueLD76IhdSXs=
+go.alanpearce.eu/gomponents v1.0.0-beta1/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
M internal/examples/app/go.work.suminternal/examples/app/go.work.sum
@@ -1,1 +1,1 @@-maragu.dev/gomponents v1.0.0-beta1/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
+go.alanpearce.eu/gomponents v1.0.0-beta1/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
M internal/examples/app/html/about.gointernal/examples/app/html/about.go
@@ -3,14 +3,15 @@ import (
 	"time"
 
-	. "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/html"
+	. "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/html"
 )
 
 func AboutPage() Node {
 	now := time.Now()
 
-	return page("About",
+	return page(
+		"About",
 		H1(Text("About")),
 
 		P(Textf("Built with gomponents and rendered at %v.", now.Format(time.TimeOnly))),
@@ -20,6 +21,10 @@ If(now.Second()%2 == 0, Text("It's an even second!")), 			If(now.Second()%2 != 0, Text("It's an odd second!")),
 		),
 
-		Img(Class("max-w-sm"), Src("https://www.gomponents.com/images/logo.png"), Alt("gomponents logo")),
+		Img(
+			Class("max-w-sm"),
+			Src("https://www.gomponents.com/images/logo.png"),
+			Alt("gomponents logo"),
+		),
 	)
 }
M internal/examples/app/html/components.gointernal/examples/app/html/components.go
@@ -1,9 +1,9 @@ package html
 
 import (
-	. "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/components"
-	. "maragu.dev/gomponents/html"
+	. "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/components"
+	. "go.alanpearce.eu/gomponents/html"
 )
 
 func page(title string, children ...Node) Node {
@@ -56,7 +56,8 @@ ) }
 
 func footer() Node {
-	return Div(Class("bg-gray-900 text-white shadow text-center h-16 flex items-center justify-center"),
+	return Div(
+		Class("bg-gray-900 text-white shadow text-center h-16 flex items-center justify-center"),
 		A(Href("https://www.gomponents.com"), Text("gomponents")),
 	)
 }
M internal/examples/app/html/home.gointernal/examples/app/html/home.go
@@ -1,17 +1,22 @@ package html
 
 import (
-	. "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/html"
+	. "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/html"
 )
 
 func HomePage(items []string) Node {
-	return page("Home",
+	return page(
+		"Home",
 		H1(Text("Home")),
 
 		P(Text("This is a gomponents example app!")),
 
-		P(Raw(`Have a look at the <a href="https:/github.com/maragudk/gomponents/tree/main/internal/examples/app">source code</a> to see how it’s structured.`)),
+		P(
+			Raw(
+				`Have a look at the <a href="https:/github.com/alanpearce/gomponents/tree/main/internal/examples/app">source code</a> to see how it’s structured.`,
+			),
+		),
 
 		Ul(Map(items, func(s string) Node {
 			return Li(Text(s))
M internal/examples/app/http/pages.gointernal/examples/app/http/pages.go
@@ -3,8 +3,8 @@ import (
 	"net/http"
 
-	. "maragu.dev/gomponents"
-	ghttp "maragu.dev/gomponents/http"
+	. "go.alanpearce.eu/gomponents"
+	ghttp "go.alanpearce.eu/gomponents/http"
 
 	"app/html"
 )
M internal/import/import_test.gointernal/import/import_test.go
@@ -3,17 +3,20 @@ import (
 	"testing"
 
-	. "maragu.dev/gomponents"
-	. "maragu.dev/gomponents/components"
-	. "maragu.dev/gomponents/html"
-	. "maragu.dev/gomponents/http"
+	. "go.alanpearce.eu/gomponents"
+	. "go.alanpearce.eu/gomponents/components"
+	. "go.alanpearce.eu/gomponents/html"
+	. "go.alanpearce.eu/gomponents/http"
 )
 
 func TestImports(t *testing.T) {
-	t.Run("this is just a test that does nothing, but I need the dot imports above", func(t *testing.T) {
-		_ = El("div")
-		_ = A()
-		_ = HTML5(HTML5Props{})
-		_ = Adapt(nil)
-	})
+	t.Run(
+		"this is just a test that does nothing, but I need the dot imports above",
+		func(t *testing.T) {
+			_ = El("div")
+			_ = A()
+			_ = HTML5(HTML5Props{})
+			_ = Adapt(nil)
+		},
+	)
 }