~repos /gromer

#golang#htmx#ssr

git clone https://pyrossh.dev/repos/gromer.git

gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.


14edf649 pyros2097

5 years ago
improve routing
cli/cli.go CHANGED
@@ -3,6 +3,7 @@ package cli
3
3
  import (
4
4
  "bytes"
5
5
  "fmt"
6
+ "io/ioutil"
6
7
  "log"
7
8
  "net/http"
8
9
  "os"
@@ -11,6 +12,7 @@ import (
11
12
  "plugin"
12
13
  "strconv"
13
14
  "strings"
15
+ "text/template"
14
16
  "time"
15
17
 
16
18
  "github.com/markbates/pkger"
@@ -149,7 +151,13 @@ func (w *Watcher) addFolder(name string) {
149
151
  }
150
152
  }
151
153
 
154
+ type RouteInfo struct {
155
+ Path string
156
+ FuncName string
152
- var routesMap = map[string]func(*app.RenderContext) app.UI{}
157
+ RenderFunc func(*app.RenderContext) app.UI
158
+ }
159
+
160
+ var routesMap = map[string]RouteInfo{}
153
161
 
154
162
  func getSOPath(p string) string {
155
163
  return "build/" + filepath.Base(filepath.Dir(p)) + ".so"
@@ -159,7 +167,7 @@ func getWasmPath(p string) string {
159
167
  return "build/" + filepath.Base(filepath.Dir(p)) + ".wasm"
160
168
  }
161
169
 
162
- func build(path string) (string, error) {
170
+ func buildSo(path string) (string, error) {
163
171
  soPath := getSOPath(path)
164
172
  out, err := exec.Command("go", "build", "-buildmode=plugin", "-o", soPath, path).CombinedOutput()
165
173
  if err != nil {
@@ -186,51 +194,79 @@ func buildWasm(path string) (string, error) {
186
194
  return wasmPath, nil
187
195
  }
188
196
 
189
- func getRoute(wd, p string) string {
197
+ func getRoute(basePath, p string) (string, string) {
190
- basePath := filepath.Join(wd, "pages")
191
- routePath := strings.Replace(p, basePath, "", 1)
192
- routePath = strings.Replace(routePath, "/main.go", "", -1)
198
+ clean := strings.Replace(strings.Replace(p, basePath, "", 1), ".go", "", -1)
193
- routePath = strings.Replace(routePath, "index", "", -1)
199
+ return strings.Replace(clean, "index", "", -1), strings.Title(strings.Replace(clean, "/", "", -1))
194
- return routePath
195
200
  }
196
201
 
197
- func buildAll(wd string) {
202
+ func writeMainFile(basePath string) {
203
+ tpl, err := template.New("writeMain").Parse(`// GENERATED FILE DO NOT EDIT
204
+ package main
205
+
206
+ import (
207
+ . "github.com/pyros2097/wapp"
208
+ "github.com/pyros2097/wapp/js"
209
+ )
210
+
211
+ func main() {
212
+ {{range $key, $element := .}}
198
- basePath := filepath.Join(wd, "pages")
213
+ if js.Window.URL().Path == "{{.Path}}" {
214
+ Run({{.FuncName}})
215
+ }
216
+ {{end}}
217
+ }
218
+ `)
219
+ if err != nil {
220
+ panic(err)
221
+ }
222
+ buf := bytes.NewBuffer(nil)
223
+ err = tpl.Execute(buf, routesMap)
224
+ if err != nil {
225
+ panic(err)
226
+ }
227
+ err = ioutil.WriteFile("pages/main.go", buf.Bytes(), 0644)
228
+ if err != nil {
229
+ panic(err)
230
+ }
231
+ }
232
+
233
+ func loadRoutes(p *plugin.Plugin, basePath string, dry bool) {
199
234
  err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
200
235
  if err != nil {
201
236
  fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
202
237
  return err
203
238
  }
204
239
  if !info.IsDir() {
240
+ routePath, routeFunc := getRoute(basePath, path)
241
+ if routePath == "/main" {
242
+ return nil
243
+ }
244
+ routesMap[routePath] = RouteInfo{
245
+ Path: routePath,
246
+ FuncName: routeFunc,
247
+ }
248
+ if dry {
249
+ // println("Dry")
250
+ return nil
251
+ }
205
- fmt.Printf("route: %s\n", getRoute(wd, path))
252
+ fmt.Printf("route: %s routeFunc: %s\n", routePath, routeFunc)
206
- soPath, err := build(path)
253
+ renderFn, err := p.Lookup(routeFunc)
207
- load(wd, path, soPath)
208
- buildWasm(path)
209
254
  if err != nil {
210
- println("could not build")
211
255
  return err
212
256
  }
257
+ routesMap[routePath] = RouteInfo{
258
+ Path: routePath,
259
+ FuncName: routeFunc,
260
+ RenderFunc: renderFn.(func(*app.RenderContext) app.UI),
261
+ }
262
+ // println(createPage(routesMap[routePath](app.NewRenderContext())).String())
213
263
  }
214
264
  return nil
215
265
  })
216
266
  if err != nil {
217
- fmt.Printf("error walking the path %q: %v\n", wd, err)
267
+ fmt.Printf("error walking the path %q: %v\n", basePath, err)
218
- return
219
- }
220
- }
221
-
222
- func load(wd, file, soPath string) {
223
- routePath := getRoute(wd, file)
224
- p, err := plugin.Open(soPath)
225
- if err != nil {
226
268
  panic(err)
227
269
  }
228
- renderFn, err := p.Lookup("Route")
229
- if err != nil {
230
- panic(err)
231
- }
232
- routesMap[routePath] = renderFn.(func(*app.RenderContext) app.UI)
233
- // println(createPage(routesMap[routePath](app.NewRenderContext())).String())
234
270
  }
235
271
 
236
272
  func createPage(ui app.UI, wasmPath string) *bytes.Buffer {
@@ -262,14 +298,9 @@ func serve(wd string) {
262
298
  buildFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "build")))
263
299
 
264
300
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
265
- if render, ok := routesMap[r.URL.Path]; ok {
301
+ if routeInfo, ok := routesMap[r.URL.Path]; ok {
266
- wasmPath := "/build/"
267
- if r.URL.Path == "/" {
268
- wasmPath += "index.wasm"
269
- } else {
270
- wasmPath += strings.ReplaceAll(r.URL.Path, "/", "") + ".wasm"
302
+ wasmPath := "/build/" + filepath.Base(wd) + ".wasm"
271
- }
272
- page := createPage(render(app.NewRenderContext()), wasmPath)
303
+ page := createPage(routeInfo.RenderFunc(app.NewRenderContext()), wasmPath)
273
304
  w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
274
305
  w.Header().Set("Content-Type", "text/html")
275
306
  w.WriteHeader(http.StatusOK)
@@ -286,28 +317,41 @@ func serve(wd string) {
286
317
  log.Fatal(http.ListenAndServe(":1234", nil))
287
318
  }
288
319
 
320
+ func buildAll(basePath string) {
321
+ loadRoutes(nil, basePath, true)
322
+ writeMainFile(basePath)
323
+ buildWasm(basePath)
324
+ soPath, err := buildSo(basePath)
325
+ if err != nil {
326
+ println("could not build")
327
+ panic(err)
328
+ }
329
+ p, err := plugin.Open(soPath)
330
+ if err != nil {
331
+ println("could not load so plugin")
332
+ panic(err)
333
+ }
334
+ // fmt.Printf("%+v\n", p)
335
+ loadRoutes(p, basePath, false)
336
+ }
337
+
289
338
  func Watch() {
290
339
  wd, err := os.Getwd()
291
340
  if err != nil {
292
341
  fmt.Printf("could not get wd")
293
342
  return
294
343
  }
344
+ basePath := filepath.Join(wd, "pages")
295
- buildAll(wd)
345
+ buildAll(basePath)
296
346
  watcher := MustRegisterWatcher()
297
347
  go watcher.Watch()
298
348
  go serve(wd)
299
349
  for file := range watcher.update {
300
350
  println("changed: " + file)
301
- soPath, err := build(file)
302
- if err != nil {
303
- println("go build error: " + soPath)
304
- continue
305
- }
306
- load(wd, file, soPath)
351
+ buildAll(basePath)
307
- wasmPath, err := buildWasm(file)
308
- if err != nil {
309
- println("wasm build error: " + wasmPath)
310
- continue
311
- }
312
352
  }
313
353
  }
354
+
355
+ func main() {
356
+ Watch()
357
+ }
example/pages/{about/main.go → about.go} RENAMED
@@ -4,10 +4,6 @@ import (
4
4
  . "github.com/pyros2097/wapp"
5
5
  )
6
6
 
7
- func Route(c *RenderContext) UI {
7
+ func About(c *RenderContext) UI {
8
8
  return Div(Text("About Me"))
9
9
  }
10
-
11
- func main() {
12
- Run(Route)
13
- }
example/pages/{clock/main.go → clock.go} RENAMED
@@ -6,7 +6,7 @@ import (
6
6
  . "github.com/pyros2097/wapp"
7
7
  )
8
8
 
9
- func Route(c *RenderContext) UI {
9
+ func Clock(c *RenderContext) UI {
10
10
  timeValue, setTime := c.UseState(time.Now())
11
11
  running, setRunning := c.UseState(false)
12
12
  startTimer := func() {
@@ -47,7 +47,3 @@ func Route(c *RenderContext) UI {
47
47
  ),
48
48
  )
49
49
  }
50
-
51
- func main() {
52
- Run(Route)
53
- }
example/pages/{container/main.go → container.go} RENAMED
@@ -7,7 +7,7 @@ import (
7
7
  . "github.com/pyros2097/wapp/example/atoms"
8
8
  )
9
9
 
10
- func Counter(c *RenderContext, no string) UI {
10
+ func AtomCounter(c *RenderContext, no string) UI {
11
11
  count := c.UseAtom(CountAtom)
12
12
  return Col(
13
13
  Row(
@@ -29,14 +29,10 @@ func Counter(c *RenderContext, no string) UI {
29
29
  )
30
30
  }
31
31
 
32
- func Route(c *RenderContext) UI {
32
+ func Container(c *RenderContext) UI {
33
33
  return Col(
34
- Counter(c, "1"),
34
+ AtomCounter(c, "1"),
35
- Counter(c, "2"),
35
+ AtomCounter(c, "2"),
36
- Counter(c, "3"),
36
+ AtomCounter(c, "3"),
37
37
  )
38
38
  }
39
-
40
- func main() {
41
- Run(Route)
42
- }
example/pages/{index/main.go → index.go} RENAMED
@@ -6,7 +6,7 @@ import (
6
6
  . "github.com/pyros2097/wapp"
7
7
  )
8
8
 
9
- func Route(c *RenderContext) UI {
9
+ func Index(c *RenderContext) UI {
10
10
  count, setCount := c.UseInt(0)
11
11
  inc := func() { setCount(count() + 1) }
12
12
  dec := func() { setCount(count() - 1) }
@@ -29,7 +29,3 @@ func Route(c *RenderContext) UI {
29
29
  ),
30
30
  )
31
31
  }
32
-
33
- func main() {
34
- Run(Route)
35
- }
example/pages/main.go ADDED
@@ -0,0 +1,27 @@
1
+ // GENERATED FILE DO NOT EDIT
2
+ package main
3
+
4
+ import (
5
+ . "github.com/pyros2097/wapp"
6
+ "github.com/pyros2097/wapp/js"
7
+ )
8
+
9
+ func main() {
10
+
11
+ if js.Window.URL().Path == "/" {
12
+ Run(Index)
13
+ }
14
+
15
+ if js.Window.URL().Path == "/about" {
16
+ Run(About)
17
+ }
18
+
19
+ if js.Window.URL().Path == "/clock" {
20
+ Run(Clock)
21
+ }
22
+
23
+ if js.Window.URL().Path == "/container" {
24
+ Run(Container)
25
+ }
26
+
27
+ }