|
1 | 1 | # Muxt [](https://pkg.go.dev/github.com/crhntr/muxt) [](https://github.com/crhntr/muxt/actions/workflows/go.yml)
|
2 | 2 |
|
3 |
| -Since Go 1.22, the standard library route **mu**ltiple**x**er [`*http.ServeMux`](https://pkg.go.dev/net/http#ServeMux) uses http methods, hosts, and path parameters. |
4 |
| -Muxt extends this syntax to add method signatures and type static analysis based template type safety to make it faster to write and test server side rendered hypermedia web applications. |
5 |
| - |
6 |
| -Muxt generates Go code. It does not require you to add any dependencies outside the Go standard library. |
| 3 | +**Muxt** is a Go code generator that helps you build server-side rendered web apps with minimal overhead, leveraging Go 1.22’s improved `http.ServeMux` and standard `html/template` features. |
| 4 | +No extra runtime dependencies are required—just plain Go code. |
7 | 5 |
|
8 | 6 | - It allows you to register HTTP routes from [HTML templates](https://pkg.go.dev/html/template)
|
9 | 7 | - It generates handler functions and registers them on an [`*http.ServeMux`](https://pkg.go.dev/net/http#ServeMux)
|
@@ -90,145 +88,6 @@ var (
|
90 | 88 |
|
91 | 89 | ```
|
92 | 90 |
|
93 |
| -### Making Template Source Files Discoverable |
94 |
| - |
95 |
| -Muxt needs your template source files to be embedded in the package in the current directory for it to discover and parse them (see the "Go Generate Comment Example" above). |
96 |
| - |
97 |
| -You need to add a globally scoped variable with type `embed.FS` (like `templatesSource` in the example). |
98 |
| -It should be passed into a call either the function `"html/template".ParseFS` or method `"html/template".Template.ParseFS`. |
99 |
| -Before it does so, it can call any of the following functions or methods in the right hand side of the `templates` variable declaration. |
100 |
| - |
101 |
| -Muxt will call any of the functions: |
102 |
| -- [`Must`](https://pkg.go.dev/html/template#Must) |
103 |
| -- [`Parse`](https://pkg.go.dev/html/template#Parse) |
104 |
| -- [`New`](https://pkg.go.dev/html/template#New) |
105 |
| -- [`ParseFS`](https://pkg.go.dev/html/template#ParseFS) |
106 |
| - |
107 |
| -or methods: |
108 |
| -- [`Template.Parse`](https://pkg.go.dev/html/template#Template.Parse) |
109 |
| -- [`Template.New`](https://pkg.go.dev/html/template#Template.New) |
110 |
| -- [`Template.ParseFS`](https://pkg.go.dev/html/template#Template.ParseFS) |
111 |
| -- [`Template.Delims`](https://pkg.go.dev/html/template#Template.Delims) |
112 |
| -- [`Template.Option`](https://pkg.go.dev/html/template#Template.Option) |
113 |
| -- [`Template.Funcs`](https://pkg.go.dev/html/template#Template.Option) |
114 |
| - |
115 |
| -Muxt iterates over the resulting parsed templates to discover templates matching the template name pattern documented in the "Naming Templates" section below. |
116 |
| - |
117 |
| -### Naming Templates |
118 |
| - |
119 |
| -`muxt generate` will read your embedded HTML templates and generate/register an [`http.HandlerFunc`](https://pkg.go.dev/net/http#HandlerFunc) for each template with a name that matches an expected patten. |
120 |
| - |
121 |
| -If the template name does not match the pattern, it is ignored by muxt. |
122 |
| - |
123 |
| -Since Go 1.22, the standard library route **mu**ltiple**x**er can parse path parameters. |
124 |
| - |
125 |
| -It has expects strings like this |
126 |
| - |
127 |
| -`[METHOD ][HOST]/[PATH]` |
128 |
| - |
129 |
| -Muxt extends this a little bit. |
130 |
| - |
131 |
| -`[METHOD ][HOST]/[PATH ][HTTP_STATUS ][CALL]` |
132 |
| - |
133 |
| -A template name that muxt understands looks like this: |
134 |
| - |
135 |
| -```gotemplate |
136 |
| -{{define "GET /greet/{language} 200 Greeting(ctx, language)" }} |
137 |
| - <h1>{{.Hello}}</h1> |
138 |
| -{{end}} |
139 |
| -``` |
140 |
| - |
141 |
| -In this template name |
142 |
| -- Passed through to `http.ServeMux` |
143 |
| - - we define the HTTP Method `GET`, |
144 |
| - - the path prefix `/greet/` |
145 |
| - - the path parameter called `language` (available in the call scope as `language`) |
146 |
| -- Used by muxt to generate a `http.HandlerFunc` |
147 |
| - - the status code to use when muxt calls WriteHeader is `200` aka `http.StatusOK` |
148 |
| - - the method name on the configured receiver to call is `Greeting` |
149 |
| - - the parameters to pass to `Greeting` are `ctx` and `language` |
150 |
| - |
151 |
| -#### [`*http.ServeMux`](https://pkg.go.dev/net/http#ServeMux) Patterns |
152 |
| - |
153 |
| -Here is an excerpt from [the standard libary documentation.](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux) |
154 |
| - |
155 |
| -> Patterns can match the method, host and path of a request. Some examples: |
156 |
| -> - "/index.html" matches the path "/index.html" for any host and method. |
157 |
| -> - "GET /static/" matches a GET request whose path begins with "/static/". |
158 |
| -> - "example.com/" matches any request to the host "example.com". |
159 |
| -> - "example.com/{$}" matches requests with host "example.com" and path "/". |
160 |
| -> - "/b/{bucket}/o/{objectname...}" matches paths whose first segment is "b" and whose third segment is "o". The name "bucket" denotes the second segment and "objectname" denotes the remainder of the path. |
161 |
| -
|
162 |
| -#### The Method Call Scope |
163 |
| - |
164 |
| -There are three parameters you can pass to a method that always generate the same code |
165 |
| - |
166 |
| -- `ctx` -> `http.Request.Context` |
167 |
| -- `request` -> `*http.Request` |
168 |
| -- `response` -> `http.ResponseWriter` (if you use this, muxt won't generate code to call WriteHeader, you have to do this) |
169 |
| - |
170 |
| -Using these three, the generated code will look something like this. |
171 |
| - |
172 |
| -Given `{{define "GET / F(ctx, response, request)"}}Hello{{end}}`, |
173 |
| - |
174 |
| -You will get a handler generated like this: |
175 |
| - |
176 |
| -```go |
177 |
| -package main |
178 |
| - |
179 |
| -import ( |
180 |
| - "context" |
181 |
| - "net/http" |
182 |
| -) |
183 |
| - |
184 |
| -type RoutesReceiver interface { |
185 |
| - F(ctx context.Context, response http.ResponseWriter, request *http.Request) any |
186 |
| -} |
187 |
| - |
188 |
| -func routes(mux *http.ServeMux, receiver RoutesReceiver) { |
189 |
| - mux.HandleFunc("GET /", func(response http.ResponseWriter, request *http.Request) { |
190 |
| - ctx := request.Context() |
191 |
| - data := receiver.F(ctx, response, request) |
192 |
| - execute(response, request, false, "GET / F(ctx, response, request)", http.StatusOK, data) |
193 |
| - }) |
194 |
| -} |
195 |
| - |
196 |
| -func execute(http.ResponseWriter, *http.Request, bool, string, int, any) {} |
197 |
| -``` |
198 |
| - |
199 |
| -You can also map path values from the path pattern to identifiers and pass them to your handler. |
200 |
| - |
201 |
| - |
202 |
| -Given `{{define "GET /articles/{id} ReadArticle(ctx, id)"}}{{end}}`, |
203 |
| - |
204 |
| -You will get a handler generated like this: |
205 |
| - |
206 |
| -```go |
207 |
| -package main |
208 |
| - |
209 |
| -import ( |
210 |
| - "context" |
211 |
| - "net/http" |
212 |
| -) |
213 |
| - |
214 |
| -type RoutesReceiver interface { |
215 |
| - ReadArticle(ctx context.Context, id string) any |
216 |
| -} |
217 |
| - |
218 |
| -func routes(mux *http.ServeMux, receiver RoutesReceiver) { |
219 |
| - mux.HandleFunc("GET /articles/{id}", func(response http.ResponseWriter, request *http.Request) { |
220 |
| - ctx := request.Context() |
221 |
| - id := request.PathValue("id") |
222 |
| - data := receiver.ReadArticle(ctx, id) |
223 |
| - execute(response, request, true, "GET /articles/{id} ReadArticle(ctx, id)", http.StatusOK, data) |
224 |
| - }) |
225 |
| -} |
226 |
| - |
227 |
| -func execute(http.ResponseWriter, *http.Request, bool, string, int, any) {} |
228 |
| -``` |
229 |
| - |
230 |
| -_TODO add more documentation on form and typed arguments_ |
231 |
| - |
232 | 91 | ## Examples
|
233 | 92 |
|
234 | 93 | The [example directory](example) has a worked example.
|
|
0 commit comments