gromer
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
It uses a declarative syntax using inline jsx like templates for components and pages.
It also generates http handlers for your routes which follow a particular folder structure. Similar to other frameworks like nextjs, sveltekit.
You can install this extension vscode-go-inline-html to get syntax highlighting for these templates.
Requirements
go >= v1.18Install
go get -u -v github.com/pyros2097/gromer/cmd/gromerUsing
You need to follow this directory structure similar to nextjs for the api route handlers to be generated and run the gromer command.
These are some components
routes/todo.go
func Todo(c Context, todo *todos.Todo) *Node { return c.Render(` <li id="todo-{todo.ID}" class="{ completed: todo.Completed }"> <div class="view"> <form hx-target="#todo-{todo.ID}" hx-swap="outerHTML"> <input type="hidden" name="intent" value="complete" /> <input type="hidden" name="id" value="{todo.ID}" /> <input class="checkbox" type="checkbox" checked="{value}" /> </form> <label>{todo.Text}</label> <form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete"> <input type="hidden" name="intent" value="delete" /> <input type="hidden" name="id" value="{todo.ID}" /> <button class="destroy"></button> </form> </div> </li> `)}These are normal page routes
routes/get.go
type GetParams struct { Page int `json:"page"` Filter string `json:"filter"`}
func GET(c Context, params GetParams) (*Node, int, error) { c.Meta("title", "Gromer Todos") c.Meta("description", "Gromer Todos") c.Meta("author", "gromer") c.Meta("keywords", "gromer") return c.Render(` <div class="todoapp"> <header class="header"> <h1>todos</h1> <form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''"> <input type="hidden" name="intent" value="create" /> <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" /> </form> </header> <section class="main"> <input class="toggle-all" id="toggle-all" type="checkbox" /> <label for="toggle-all">Mark all as complete</label> <TodoList id="todo-list" page="{params.Page}" filter="{params.Filter}" /> </section> <footer class="footer"> <TodoCount filter="{params.Filter}" /> <ul class="filters"> <li> <a href="?filter=all">All</a> </li> <li> <a href="?filter=active">Active</a> </li> <li> <a href="?filter=completed">Completed</a> </li> </ul> <form hx-target="#todo-list" hx-post="/"> <input type="hidden" name="intent" value="clear_completed" /> <button type="submit" class="clear-completed" >Clear completed</button> </form> </footer> </div> `), 200, nil}And then run the gromer cli command annd it will generate the route handlers in a main.go file,
main.go
// Code generated by gromer. DO NOT EDIT.package main
import ( "github.com/gorilla/mux" "github.com/pyros2097/gromer" "github.com/pyros2097/gromer/assets" "github.com/pyros2097/gromer/gsx" "github.com/rs/zerolog/log" "gocloud.dev/server"
"github.com/pyros2097/gromer/_example/assets" "github.com/pyros2097/gromer/_example/components" "github.com/pyros2097/gromer/_example/containers" "github.com/pyros2097/gromer/_example/routes/404" "github.com/pyros2097/gromer/_example/routes" "github.com/pyros2097/gromer/_example/routes/about"
)
func init() { gsx.RegisterComponent(components.Todo, "todo") gsx.RegisterComponent(components.Checkbox, "value") gsx.RegisterComponent(containers.TodoCount, "filter") gsx.RegisterComponent(containers.TodoList, "page", "filter")}
func main() { baseRouter := mux.NewRouter() baseRouter.Use(gromer.LogMiddleware) baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
staticRouter := baseRouter.NewRoute().Subrouter() staticRouter.Use(gromer.CacheMiddleware) gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS) gromer.StaticRoute(staticRouter, "/assets/", assets.FS) gromer.StylesRoute(staticRouter, "/styles.css")
pageRouter := baseRouter.NewRoute().Subrouter() gromer.Handle(pageRouter, "GET", "/", routes.GET) gromer.Handle(pageRouter, "POST", "/", routes.POST) gromer.Handle(pageRouter, "GET", "/about", about.GET)
log.Info().Msg("http server listening on http://localhost:3000") srv := server.New(baseRouter, nil) if err := srv.ListenAndServe(":3000"); err != nil { log.Fatal().Stack().Err(err).Msg("failed to listen") }}TODO:
Add inline css formatting
Add inline html formatting