Skip to content

Commit

Permalink
refactor: Use templ instead of standard html/template
Browse files Browse the repository at this point in the history
Use templ as it better suited for htmx development. Supports: parameter based components, typed components, importing components instead of reading in templates
  • Loading branch information
tombrereton authored Jan 15, 2024
1 parent fb79af6 commit a331dfe
Show file tree
Hide file tree
Showing 36 changed files with 617 additions and 295 deletions.
6 changes: 3 additions & 3 deletions .air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/web"
cmd = "go build -o ./tmp/main ./cmd/main.go"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_regex = ["_test.go", ".*_templ.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_ext = ["go", "tpl", "tmpl", "html", "templ"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ tmp

# node
node_modules/
**/styles.css
**/styles.css
**/output.css
11 changes: 4 additions & 7 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,25 @@ COPY web ./web
COPY package*.json .
COPY tailwind.config.js .
RUN npm install
RUN npx tailwindcss -i ./web/static/css/main.css -o ./web/static/css/styles.css --minify
RUN npx tailwindcss -i ./web/static/css/input.css -o ./web/static/css/outpus.css --minify

# Go build stage
FROM golang:latest as gobuilder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
WORKDIR /app/cmd/web
WORKDIR /app/cmd
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Final stage
FROM alpine:latest

ENV PORT=3001 \
IS_DEVELOPMENT=false \
TEMPLATES_DIR=web/templates/ \
STATIC_DIR=./web/static
ENV PORT=3001

WORKDIR /root/

COPY --from=gobuilder /app/cmd/web/main .
COPY --from=gobuilder /app/cmd/main .
COPY --from=tailwindbuilder /node/web/ ./web/

EXPOSE 3001
Expand Down
31 changes: 31 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"context"
"fmt"
"log"
"os"
"os/signal"

"github.com/joho/godotenv"
"github.com/tombrereton/go-hot-reload/internal/application"
)

func main() {
err := godotenv.Load()
if err != nil {
log.Println("No .env file found - loading from environment")
}
cfg := application.NewConfigurationBuilder().
WithPort(os.Getenv("PORT")).
Build()
app := application.NewApp(cfg)

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

err = app.Start(ctx)
if err == nil {
fmt.Println("Shutting down server: %w", err)
}
}
29 changes: 0 additions & 29 deletions cmd/web/main.go

This file was deleted.

15 changes: 4 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,13 @@ go 1.21.5

require (
github.com/PuerkitoBio/goquery v1.8.1
github.com/a-h/templ v0.2.513
github.com/go-chi/chi/v5 v5.0.11
github.com/kelseyhightower/envconfig v1.4.0
)

require (
github.com/bep/debounce v1.2.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
golang.org/x/sys v0.16.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/mavolin/go-htmx v1.0.0
)

require (
github.com/aarol/reload v1.1.1
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/joho/godotenv v1.5.1
golang.org/x/net v0.19.0 // indirect
golang.org/x/net v0.17.0 // indirect
)
30 changes: 8 additions & 22 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/aarol/reload v1.1.1 h1:ouEgTVSHVeClW+f0YSXJdNPHg0yi3zYo4N9BVL5JeVI=
github.com/aarol/reload v1.1.1/go.mod h1:TmwcyY3/j9gx1Tn3pGjanIQwd/weeFEJFNRTsn7wFDI=
github.com/a-h/templ v0.2.513 h1:ZmwGAOx4NYllnHy+FTpusc4+c5msoMpPIYX0Oy3dNqw=
github.com/a-h/templ v0.2.513/go.mod h1:9gZxTLtRzM3gQxO8jr09Na0v8/jfliS97S9W5SScanM=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/mavolin/go-htmx v1.0.0 h1:43rZuemWd23zrMcTU939EsflXjOPxtHy9VraT1CZ6qQ=
github.com/mavolin/go-htmx v1.0.0/go.mod h1:r6O09gzKou9kutq3UiDPZ//Q7IeBCMcs8US5/sHFbvg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand All @@ -31,8 +21,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -42,8 +32,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand All @@ -56,5 +44,3 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
49 changes: 49 additions & 0 deletions internal/application/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package application

import (
"context"
"fmt"
"net/http"
"time"
)

type App struct {
cfg Configuration
router http.Handler
}

func NewApp(c Configuration) *App {
app := &App{
router: loadRoutes(),
cfg: c,
}

return app
}

func (a *App) Start(ctx context.Context) error {
port := a.cfg.port
server := &http.Server{
Addr: ":" + port,
Handler: a.router,
}

ch := make(chan error, 1)
go func() {
err := server.ListenAndServe()
if err != nil {
ch <- fmt.Errorf("error starting server: %w", err)
}
close(ch)
}()

fmt.Printf("Started server at http://localhost:%s\n", port)
select {
case err := <-ch:
return err
case <-ctx.Done():
timeout, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
return server.Shutdown(timeout)
}
}
50 changes: 50 additions & 0 deletions internal/application/app_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package application

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/PuerkitoBio/goquery"
)

func TestApp_Start(t *testing.T) {
cfg := NewConfigurationBuilder().WithPort("7000").Build()
app := NewApp(cfg)

server := httptest.NewServer(app.router)
defer server.Close()

client := server.Client()
resp, err := client.Get(server.URL + "/")
if err != nil {
t.Errorf("Failed to send test request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
t.Errorf("Unexpected status code: got %d, want %d", resp.StatusCode, http.StatusOK)
}

doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
t.Errorf("Failed to parse response body: %v", err)
}

aboutLink := doc.Find("#about-link")
if aboutLink.Length() == 0 {
t.Errorf("Element with id 'about-link' not found")
}

attr, exists := aboutLink.Attr("hx-get")
if !exists {
t.Errorf("Href attribute not found for element with id 'about-link'")
} else if attr != "/about" {
t.Errorf("Unexpected href value: got %s, want /about", attr)
}

label := aboutLink.Text()
if label != "About" {
t.Errorf("Unexpected label value: got %s, want About", label)
}
}
25 changes: 25 additions & 0 deletions internal/application/configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package application

type Configuration struct {
port string
}

type ConfigurationBuilder struct {
configuration Configuration
}

func NewConfigurationBuilder() *ConfigurationBuilder {
return &ConfigurationBuilder{}
}

func (b *ConfigurationBuilder) WithPort(port string) *ConfigurationBuilder {
if port == "" {
panic("port cannot be empty")
}
b.configuration.port = port
return b
}

func (b *ConfigurationBuilder) Build() Configuration {
return b.configuration
}
31 changes: 31 additions & 0 deletions internal/application/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package application

import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/tombrereton/go-hot-reload/internal/handler"
)

func loadRoutes() *chi.Mux {
r := chi.NewRouter()

r.Use(middleware.Logger)
r.Use(middleware.RequestID)

r.Route("/", marketingRoutes)
r.Route("/static", staticRoutes)
return r
}

func marketingRoutes(r chi.Router) {
marketingHandler := &handler.Marketing{}

r.Get("/", marketingHandler.GetLandingPage)
r.Get("/about", marketingHandler.GetAboutPage)
}

func staticRoutes(r chi.Router) {
staticHandler := &handler.Static{}

r.Handle("/*", staticHandler.GetStaticFiles())
}
Loading

0 comments on commit a331dfe

Please sign in to comment.