Skip to content

Commit

Permalink
Merge pull request #2 from joerdav/main
Browse files Browse the repository at this point in the history
feat: templ templates
  • Loading branch information
acaloiaro authored Jul 4, 2023
2 parents 6caf7d2 + 048d50d commit c6916f8
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 57 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/hugo-htmx-go-template"
cmd = "go build -o ./tmp/hugo-htmx-go-template server.go"
cmd = "templ generate 2> /dev/null && go build -o ./tmp/hugo-htmx-go-template server.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 = []
# 50ms in nanoseconds
kill_delay = 500000000
Expand Down
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
# hugo-htmx-go-template

This is a project template combining [Hugo](https://gohugo.io), [htmx](https://htmx.org), and an optional API server written in Go, using `html/template` for HTML rendering.
This is a project template combining [Hugo](https://gohugo.io), [htmx](https://htmx.org), and an optional API server written in Go, using `html/template` or [templ](https://github.com/a-h/templ/) for HTML rendering.

## Why?

Hugo is a fantastic static site build tool, and there are few things about Hugo that can or should be improved.
Hugo is a fantastic static site building tool, and there are few things about Hugo that can or should be improved.

The existence of this project template should in no way suggest that static sites should be dynamic. If your site _can_ be static, it _should_ be static.
The existence of this project template does not suggest that all static sites should be dynamic. If your site _can_ be static, it _should_ be static.

Yet there are instances in which one might need to add dynamic functionality to static Hugo sites. That is the purpose of this project template. Not to make all static sites dynamic, but to provide a simple solution to add islands of dynamic behavior to static sites.
Yet there are instances in which one might want to add dynamic functionality to static Hugo sites. That is the purpose of this project template. Not to make all static sites dynamic, but to provide a simple solution to add islands of dynamic behavior to static sites.

This allows us to build fast, easily deployable HTML content, but with the added ability of meeting a new class dynamic behavior needs.
This allows us to build fast, easily deployable HTML content, with the added ability of meeting a new class dynamic behavior needs.

Example use cases include

- Contact forms
- Comment systems
- Up/Down vote systems
- You know ... website stuff

You shouldn't have to reach for a SaaS product to offer dynamic content on your static sites.

## About

Expand All @@ -24,8 +33,8 @@ This template provides example code and simple developer tooling for running Hug

**What developer tooling is provided?**

1. `bin/develop`: This utility both starts the hugo server (`hugo server`) and the API server (`go run server.go`) for development. If the user has `air` installed (https://github.com/cosmtrek/air), it will hot reload the API server code when changes to server.go are made.
2. `bin/build`: This utility builds a binary with your entire Hugo site embedded within. Note: this is only to be used to deploy Hugo sites as a self-contained binary.
1. `bin/develop`: This utility both starts the hugo server (`hugo server`) and the API server (`go run server.go`) for development. If the user has `air` installed (https://github.com/cosmtrek/air), API server code will hot-reload when changes to server.go are made.
2. `bin/build`: This utility builds a binary with your entire Hugo site embedded within. This allows Hugo sites to be deployed as a self-contained binary.

## Getting Started

Expand All @@ -44,27 +53,37 @@ go build -o bin/build internal/cmd/build/main.go
mkdir public && touch public/.empty
```

If you'll be using the API server, it's useful to install `air` if you want auto-rebuild functionality when running `bin/develop`:
If you'll be using the API server, it's useful to install `air` to hot redeploy Go code changes when running `bin/develop`:

```bash
go install github.com/cosmtrek/air@latest
```

To make changes to `templ` templates (.templ files), first install `templ`. Using `templ` is optional. The [goodbye world](https://github.com/acaloiaro/hugo-htmx-go-template/blob/main/server.go#L83) example shows `templ` in action.

```bash
go install github.com/a-h/templ/cmd/templ@latest
```

## Run

To start the development server(s), run `bin/develop`

Hugo will run on its develop port at [http://localhost:1313](http://localhost:1313) and the API server runs on [http://localhost:1314](http://localhost:1314).

Example content using Hugo/htmx/go api is available at: [http://localhost:1313/posts/hello-world/](http://localhost:1313/posts/hello-world/)
Example content is available at: [http://localhost:1313/posts/hello-world/](http://localhost:1313/posts/hello-world/)

You can also use `bin/build` to build a fat binary of your Hugo site, which will be available at [http://localhost:1314](http://localhost:1314) after running `build/server`.
`bin/build` can be used to build fat binaries of your Hugo site, which will be available at [http://localhost:1314](http://localhost:1314) after running `build/server`.

![screenshot](https://user-images.githubusercontent.com/3331648/248586236-1ad03704-4f13-418c-aa9a-3122742c6b8c.png)

## Changing `templ` templates

To use `templ` templates, you will need to `templ` installed. The `air` configuration watches for changes to `templ` files and automatically builds them.

## Deploy

Hugo sites made from this template can be deployed in two ways.
Hugo sites made from this template project can be deployed in two ways.

### Traditional deployment

Expand Down
6 changes: 3 additions & 3 deletions content/posts/goodbye-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ date: 2023-06-23T15:58:00-06:00
draft: false
---

## Goodbye world is a static template example
## Goodbye world is a `templ` template example

Every time `Click Me!` is clicked, a request is sent to fetch static template `/goodbyeworld.html`.
Every time `Click Me!` is clicked, a request is sent to the API server, which renders `templ GoodbyeWorld()` from `partial/templates.templ`.

{{< html.inline >}}
<button
hx-get="/goodbyeworld.html"
hx-get="{{ .Site.Params.apiBaseUrl }}/goodbyeworld.html"
hx-trigger="click"
hx-target="#goodbye"
hx-swap="beforeend">
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/acaloiaro/hugo-htmx-go-template

go 1.20

require github.com/a-h/templ v0.2.304
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/a-h/templ v0.2.304 h1:vIgCNazkW6NiYifFIGYNRfBkoBzOMZMO1NibIayzihE=
github.com/a-h/templ v0.2.304/go.mod h1:3oc37WS5rpDvFGi6yeknvTKt50xCu67ywQsM43Wr4PU=
1 change: 0 additions & 1 deletion partials/goodbyeworld.html

This file was deleted.

10 changes: 10 additions & 0 deletions partials/templates.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package partials

templ HelloWorldGreeting(name string) {
<h3>Greeting: Hello, { name }!</h3>
}

templ GoodbyeWorld() {
<p>Goodbye, World!</p>
}

92 changes: 92 additions & 0 deletions partials/templates_templ.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 27 additions & 39 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"bytes"
"embed"
"fmt"
"html/template"
"io/fs"
"log"
"net/http"
"text/template"

"github.com/a-h/templ"
"github.com/acaloiaro/hugo-htmx-go-template/partials"
)

//go:embed all:public
Expand All @@ -20,9 +23,28 @@ func main() {
// Serve all hugo content (the 'public' directory) at the root url
mux.Handle("/", http.FileServer(http.FS(serverRoot)))

cors := func(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// in development, the Origin is the the Hugo server, i.e. http://localhost:1313
// but in production, it is the domain name where one's site is deployed
//
// CHANGE THIS: You likely do not want to allow any origin (*) in production. The value should be the base URL of
// where your static content is served
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, hx-target, hx-current-url, hx-request")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
h.ServeHTTP(w, r)
}
}

// Add any number of handlers for custom endpoints here
mux.HandleFunc("/hello_world", helloWorld)
mux.HandleFunc("/hello_world_form", helloWorldForm)
mux.HandleFunc("/goodbyeworld.html", cors(templ.Handler(partials.GoodbyeWorld())))
mux.HandleFunc("/hello_world", cors(http.HandlerFunc(helloWorld)))
mux.HandleFunc("/hello_world_form", cors(http.HandlerFunc(helloWorldForm)))

fmt.Printf("Starting API server on port 1314\n")
if err := http.ListenAndServe("0.0.0.0:1314", mux); err != nil {
Expand All @@ -36,15 +58,6 @@ func main() {
//
// It responds with the the HTML partial `partials/helloworld.html`
func helloWorld(w http.ResponseWriter, r *http.Request) {
// in development, the Origin is the the Hugo server, i.e. http://localhost:1313
// but in production, it is the domain name where one's site is deployed
w.Header().Set("Access-Control-Allow-Origin", "*")

if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}

name := r.URL.Query().Get("name")
if name == "null" || name == "" {
name = "World"
Expand All @@ -68,18 +81,6 @@ func helloWorld(w http.ResponseWriter, r *http.Request) {
//
// It responds with a simple greeting HTML partial
func helloWorldForm(w http.ResponseWriter, r *http.Request) {
// in development, the Origin is the the Hugo server, i.e. http://localhost:1313
// but in production, it is the domain name where one's site is deployed
// for this demo, we're using `*` to keep things simple
w.Header().Set("Access-Control-Allow-Origin", "*")

if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, hx-current-url, hx-request")
w.WriteHeader(http.StatusNoContent)
return
}

name := "World"
// The name is not in the query param, let's see if it was submitted as a form
if err := r.ParseForm(); err != nil {
Expand All @@ -88,23 +89,10 @@ func helloWorldForm(w http.ResponseWriter, r *http.Request) {
}

name = r.FormValue("name")
// we're dealing with a really simple template; let's use an inline string instead of a whole separate file for this
// one
tmpl, err := template.New("form_response").Parse("<h3>Greeting: Hello, {{ .Name }}!</h3>")
if err != nil {
ise(err, w)
if err := partials.HelloWorldGreeting(name).Render(r.Context(), w); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

var buff = bytes.NewBufferString("")
err = tmpl.Execute(buff, map[string]string{"Name": name})
if err != nil {
ise(err, w)
return
}

w.WriteHeader(http.StatusOK)
w.Write(buff.Bytes())
}

func ise(err error, w http.ResponseWriter) {
Expand Down

0 comments on commit c6916f8

Please sign in to comment.