~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.


4b041238 Peter John

3 years ago
remove api routes and update readme
_example/.gitignore → .gitignore RENAMED
File without changes
_example/main.go CHANGED
@@ -30,7 +30,6 @@ func init() {
30
30
  func main() {
31
31
  baseRouter := mux.NewRouter()
32
32
  baseRouter.Use(gromer.LogMiddleware)
33
-
34
33
  baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
35
34
 
36
35
  staticRouter := baseRouter.NewRoute().Subrouter()
@@ -44,12 +43,6 @@ func main() {
44
43
  gromer.Handle(pageRouter, "POST", "/", routes.POST)
45
44
  gromer.Handle(pageRouter, "GET", "/about", about.GET)
46
45
 
47
-
48
- apiRouter := baseRouter.NewRoute().Subrouter()
49
- apiRouter.Use(gromer.CorsMiddleware)
50
-
51
-
52
-
53
46
  log.Info().Msg("http server listening on http://localhost:3000")
54
47
  srv := server.New(baseRouter, nil)
55
48
  if err := srv.ListenAndServe(":3000"); err != nil {
_example/readme.md CHANGED
@@ -13,4 +13,6 @@ This example demonstrates gromer with a simple web app.
13
13
  make setup
14
14
  make update
15
15
  make dev
16
+ make build
17
+ make run
16
18
  ```
cmd/gromer/main.go CHANGED
@@ -134,20 +134,9 @@ func main() {
134
134
  return gromer.RouteDefs[i].Path < gromer.RouteDefs[j].Path
135
135
  })
136
136
  pageRoutes := []gromer.RouteDefinition{}
137
- apiRoutes := []gromer.RouteDefinition{}
138
137
  for _, r := range gromer.RouteDefs {
139
138
  fmt.Printf("%-6s %s %-6s\n", r.Method, r.Path, r.PkgPath)
140
- if strings.Contains(r.Path, "/api/") {
141
- apiRoutes = append(apiRoutes, r)
142
- } else {
143
- pageRoutes = append(pageRoutes, r)
139
+ pageRoutes = append(pageRoutes, r)
144
- }
145
- }
146
- err = velvet.Helpers.Add("title", func(v string) string {
147
- return strings.Title(strings.ToLower(v))
148
- })
149
- if err != nil {
150
- log.Fatal(err)
151
140
  }
152
141
  hasRouteMap := map[string]bool{}
153
142
  routeImports := []gromer.RouteDefinition{}
@@ -182,7 +171,6 @@ func main() {
182
171
  ctx := velvet.NewContext()
183
172
  ctx.Set("moduleName", moduleName)
184
173
  ctx.Set("pageRoutes", pageRoutes)
185
- ctx.Set("apiRoutes", apiRoutes)
186
174
  ctx.Set("routeImports", routeImports)
187
175
  ctx.Set("componentNames", componentNames)
188
176
  ctx.Set("containerNames", containerNames)
@@ -217,8 +205,7 @@ func init() {
217
205
  func main() {
218
206
  baseRouter := mux.NewRouter()
219
207
  baseRouter.Use(gromer.LogMiddleware)
220
- {{#if notFoundPkg}}
221
- baseRouter.NotFoundHandler = gromer.StatusHandler({{ notFoundPkg }}.GET)
208
+ {{#if notFoundPkg}}baseRouter.NotFoundHandler = gromer.StatusHandler({{ notFoundPkg }}.GET)
222
209
  {{/if}}
223
210
  staticRouter := baseRouter.NewRoute().Subrouter()
224
211
  staticRouter.Use(gromer.CacheMiddleware)
@@ -230,12 +217,6 @@ func main() {
230
217
  {{#each pageRoutes as |route| }}gromer.Handle(pageRouter, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }})
231
218
  {{/each}}
232
219
 
233
- apiRouter := baseRouter.NewRoute().Subrouter()
234
- apiRouter.Use(gromer.CorsMiddleware)
235
- {{#each apiRoutes as |route| }}gromer.Handle(apiRouter, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }})
236
- {{/each}}
237
-
238
-
239
220
  log.Info().Msg("http server listening on http://localhost:3000")
240
221
  srv := server.New(baseRouter, nil)
241
222
  if err := srv.ListenAndServe(":3000"); err != nil {
readme.md CHANGED
@@ -3,10 +3,12 @@
3
3
  [![Version](https://badge.fury.io/gh/pyros2097%2Fgromer.svg)](https://github.com/pyros2097/gromer)
4
4
 
5
5
  **gromer** is a framework and cli to build multipage web apps in golang using [htmx](https://htmx.org/) and [alpinejs](https://alpinejs.dev/).
6
+
6
7
  It uses a declarative syntax using inline jsx like templates for components and pages.
8
+
7
9
  It also generates http handlers for your routes which follow a particular folder structure. Similar to other frameworks like nextjs, sveltekit.
8
10
 
9
- You can install this extension [vscode-go-inline-html](https://marketplace.visualstudio.com/items?itemName=pyros2097.vscode-go-inline-html) to get
11
+ You can install this extension [vscode-go-inline-html](https://marketplace.visualstudio.com/items?itemName=pyros2097.vscode-go-inline-html) to get
10
12
  syntax highlighting for these templates.
11
13
 
12
14
  # Requirements
@@ -33,23 +35,23 @@ You need to follow this directory structure similar to nextjs for the api route
33
35
 
34
36
  ```go
35
37
  func Todo(c Context, todo *todos.Todo) *Node {
36
- return c.Render(`
38
+ return c.Render(`
37
- <li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
39
+ <li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
38
- <div class="view">
40
+ <div class="view">
39
- <form hx-target="#todo-{todo.ID}" hx-swap="outerHTML">
41
+ <form hx-target="#todo-{todo.ID}" hx-swap="outerHTML">
40
- <input type="hidden" name="intent" value="complete" />
42
+ <input type="hidden" name="intent" value="complete" />
41
- <input type="hidden" name="id" value="{todo.ID}" />
43
+ <input type="hidden" name="id" value="{todo.ID}" />
42
- <input class="checkbox" type="checkbox" checked="{value}" />
44
+ <input class="checkbox" type="checkbox" checked="{value}" />
43
- </form>
45
+ </form>
44
- <label>{todo.Text}</label>
46
+ <label>{todo.Text}</label>
45
- <form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete">
47
+ <form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete">
46
- <input type="hidden" name="intent" value="delete" />
48
+ <input type="hidden" name="intent" value="delete" />
47
- <input type="hidden" name="id" value="{todo.ID}" />
49
+ <input type="hidden" name="id" value="{todo.ID}" />
48
- <button class="destroy"></button>
50
+ <button class="destroy"></button>
49
- </form>
51
+ </form>
50
- </div>
52
+ </div>
51
- </li>
53
+ </li>
52
- `)
54
+ `)
53
55
  }
54
56
  ```
55
57
 
@@ -59,49 +61,49 @@ func Todo(c Context, todo *todos.Todo) *Node {
59
61
 
60
62
  ```go
61
63
  type GetParams struct {
62
- Page int `json:"page"`
64
+ Page int `json:"page"`
63
- Filter string `json:"filter"`
65
+ Filter string `json:"filter"`
64
66
  }
65
67
 
66
68
  func GET(c Context, params GetParams) (*Node, int, error) {
67
- c.Meta("title", "Gromer Todos")
69
+ c.Meta("title", "Gromer Todos")
68
- c.Meta("description", "Gromer Todos")
70
+ c.Meta("description", "Gromer Todos")
69
- c.Meta("author", "gromer")
71
+ c.Meta("author", "gromer")
70
- c.Meta("keywords", "gromer")
72
+ c.Meta("keywords", "gromer")
71
- return c.Render(`
73
+ return c.Render(`
72
- <div class="todoapp">
74
+ <div class="todoapp">
73
- <header class="header">
75
+ <header class="header">
74
- <h1>todos</h1>
76
+ <h1>todos</h1>
75
- <form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
77
+ <form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
76
- <input type="hidden" name="intent" value="create" />
78
+ <input type="hidden" name="intent" value="create" />
77
- <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" />
79
+ <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" />
78
- </form>
80
+ </form>
79
- </header>
81
+ </header>
80
- <section class="main">
82
+ <section class="main">
81
- <input class="toggle-all" id="toggle-all" type="checkbox" />
83
+ <input class="toggle-all" id="toggle-all" type="checkbox" />
82
- <label for="toggle-all">Mark all as complete</label>
84
+ <label for="toggle-all">Mark all as complete</label>
83
- <TodoList id="todo-list" page="{params.Page}" filter="{params.Filter}" />
85
+ <TodoList id="todo-list" page="{params.Page}" filter="{params.Filter}" />
84
- </section>
86
+ </section>
85
- <footer class="footer">
87
+ <footer class="footer">
86
- <TodoCount filter="{params.Filter}" />
88
+ <TodoCount filter="{params.Filter}" />
87
- <ul class="filters">
89
+ <ul class="filters">
88
- <li>
90
+ <li>
89
- <a href="?filter=all">All</a>
91
+ <a href="?filter=all">All</a>
90
- </li>
92
+ </li>
91
- <li>
93
+ <li>
92
- <a href="?filter=active">Active</a>
94
+ <a href="?filter=active">Active</a>
93
- </li>
95
+ </li>
94
- <li>
96
+ <li>
95
- <a href="?filter=completed">Completed</a>
97
+ <a href="?filter=completed">Completed</a>
96
- </li>
98
+ </li>
97
- </ul>
99
+ </ul>
98
- <form hx-target="#todo-list" hx-post="/">
100
+ <form hx-target="#todo-list" hx-post="/">
99
- <input type="hidden" name="intent" value="clear_completed" />
101
+ <input type="hidden" name="intent" value="clear_completed" />
100
- <button type="submit" class="clear-completed" >Clear completed</button>
102
+ <button type="submit" class="clear-completed" >Clear completed</button>
101
- </form>
103
+ </form>
102
- </footer>
104
+ </footer>
103
- </div>
105
+ </div>
104
- `), 200, nil
106
+ `), 200, nil
105
107
  }
106
108
  ```
107
109
 
@@ -114,59 +116,50 @@ And then run the gromer cli command annd it will generate the route handlers in
114
116
  package main
115
117
 
116
118
  import (
117
- "github.com/gorilla/mux"
119
+ "github.com/gorilla/mux"
118
- "github.com/pyros2097/gromer"
120
+ "github.com/pyros2097/gromer"
119
- "github.com/rs/zerolog/log"
121
+ "github.com/rs/zerolog/log"
120
- "gocloud.dev/server"
122
+ "gocloud.dev/server"
121
-
123
+
122
- "github.com/pyros2097/gromer/_example/assets"
124
+ "github.com/pyros2097/gromer/_example/assets"
123
- "github.com/pyros2097/gromer/_example/components"
125
+ "github.com/pyros2097/gromer/_example/components"
124
- "github.com/pyros2097/gromer/_example/containers"
126
+ "github.com/pyros2097/gromer/_example/containers"
125
- "github.com/pyros2097/gromer/_example/routes/404"
127
+ "github.com/pyros2097/gromer/_example/routes/404"
126
- "github.com/pyros2097/gromer/_example/routes"
128
+ "github.com/pyros2097/gromer/_example/routes"
127
- "github.com/pyros2097/gromer/_example/routes/about"
129
+ "github.com/pyros2097/gromer/_example/routes/about"
128
- "github.com/pyros2097/gromer/gsx"
130
+ "github.com/pyros2097/gromer/gsx"
129
131
 
130
132
  )
131
133
 
132
134
  func init() {
133
- gsx.RegisterComponent(components.Todo, "todo")
135
+ gsx.RegisterComponent(components.Todo, "todo")
134
- gsx.RegisterComponent(components.Checkbox, "value")
136
+ gsx.RegisterComponent(components.Checkbox, "value")
135
137
 
136
- gsx.RegisterComponent(containers.TodoCount, "filter")
138
+ gsx.RegisterComponent(containers.TodoCount, "filter")
137
- gsx.RegisterComponent(containers.TodoList, "page", "filter")
139
+ gsx.RegisterComponent(containers.TodoList, "page", "filter")
138
- gromer.RegisterAssets(assets.FS)
140
+ gromer.RegisterAssets(assets.FS)
139
141
  }
140
142
 
141
143
  func main() {
142
- baseRouter := mux.NewRouter()
144
+ baseRouter := mux.NewRouter()
143
- baseRouter.Use(gromer.LogMiddleware)
145
+ baseRouter.Use(gromer.LogMiddleware)
144
-
145
- baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
146
+ baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
146
-
147
+
147
- staticRouter := baseRouter.NewRoute().Subrouter()
148
+ staticRouter := baseRouter.NewRoute().Subrouter()
148
- staticRouter.Use(gromer.CacheMiddleware)
149
+ staticRouter.Use(gromer.CacheMiddleware)
149
- gromer.GromerRoute(staticRouter, "/gromer/")
150
+ gromer.GromerRoute(staticRouter, "/gromer/")
150
- gromer.StaticRoute(staticRouter, "/assets/")
151
+ gromer.StaticRoute(staticRouter, "/assets/")
151
- gromer.StylesRoute(staticRouter, "/styles.css")
152
+ gromer.StylesRoute(staticRouter, "/styles.css")
152
-
153
+
153
- pageRouter := baseRouter.NewRoute().Subrouter()
154
+ pageRouter := baseRouter.NewRoute().Subrouter()
154
- // gromer.ApiExplorerRoute(pageRouter, "/explorer")
155
- gromer.Handle(pageRouter, "GET", "/", routes.GET)
155
+ gromer.Handle(pageRouter, "GET", "/", routes.GET)
156
- gromer.Handle(pageRouter, "POST", "/", routes.POST)
156
+ gromer.Handle(pageRouter, "POST", "/", routes.POST)
157
- gromer.Handle(pageRouter, "GET", "/about", about.GET)
157
+ gromer.Handle(pageRouter, "GET", "/about", about.GET)
158
-
159
-
160
- apiRouter := baseRouter.NewRoute().Subrouter()
161
- apiRouter.Use(gromer.CorsMiddleware)
162
-
163
-
164
-
165
- log.Info().Msg("http server listening on http://localhost:3000")
158
+ log.Info().Msg("http server listening on http://localhost:3000")
166
- srv := server.New(baseRouter, nil)
159
+ srv := server.New(baseRouter, nil)
167
- if err := srv.ListenAndServe(":3000"); err != nil {
160
+ if err := srv.ListenAndServe(":3000"); err != nil {
168
- log.Fatal().Stack().Err(err).Msg("failed to listen")
161
+ log.Fatal().Stack().Err(err).Msg("failed to listen")
169
- }
162
+ }
170
163
  }
171
164
  ```
172
165