Skip to content

Commit 54267cb

Browse files
committed
build browser assets with esbuild, remove page from args that now live in state
1 parent 7ffd855 commit 54267cb

File tree

8 files changed

+75
-41
lines changed

8 files changed

+75
-41
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
examples/components/dist

examples/components/go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/mbertschler/blocks/components
33
go 1.20
44

55
require (
6+
github.com/evanw/esbuild v0.17.16
67
github.com/gin-gonic/gin v1.9.0
78
github.com/mbertschler/blocks v1.0.0
89
)

examples/components/go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
77
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
99
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10+
github.com/evanw/esbuild v0.17.16 h1:Fd5Ugun51MfOAq8tviGkVIADL8tSOtAAVaUGjODHBBo=
11+
github.com/evanw/esbuild v0.17.16/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
1012
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
1113
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
1214
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
@@ -65,6 +67,7 @@ golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
6567
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
6668
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
6769
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
70+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6871
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6972
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
7073
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

examples/components/js/guiapi.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"use strict";
22

3-
var callableFunctions = {}
3+
export var callableFunctions = {}
4+
5+
var state = null;
46

57
const debugGuiapi = true
68

7-
function guiapi(name, args, callback) {
9+
export function guiapi(name, args, callback) {
810
if (debugGuiapi) {
9-
console.log("guiapi", name, args)
11+
console.log("guiapi", "action:", name, "args:", args, "state:", state)
1012
}
1113
if (!callback) {
1214
callback = () => { }
@@ -161,6 +163,15 @@ function hydrateInit(el) {
161163
el.classList.remove("ga")
162164
}
163165

164-
function setupGuiapi() {
166+
export function setupGuiapi() {
167+
if (window.state) {
168+
state = window.state
169+
}
165170
hydrate()
171+
}
172+
173+
export default {
174+
guiapi,
175+
callableFunctions,
176+
setupGuiapi,
166177
}

examples/components/js/main.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
console.log("hello from main.js")
1+
import "../css/main.css"
2+
import "./todolist.js"
3+

examples/components/js/todolist.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use strict";
22

3+
import { guiapi, setupGuiapi, callableFunctions } from "./guiapi.js"
4+
35
const EnterKey = "Enter"
46
const EscapeKey = "Escape"
57

@@ -17,19 +19,19 @@ function initEdit(element, args) {
1719
if (stoppedEditing) {
1820
return
1921
}
20-
guiapi("TodoList.UpdateItem", { id: args.id, text: event.target.value, page: args.page });
22+
guiapi("TodoList.UpdateItem", { id: args.id, text: event.target.value });
2123
return false;
2224
})
2325
element.addEventListener("keydown", function (event) {
2426
if (event.key == EscapeKey) {
2527
stoppedEditing = true
26-
guiapi("TodoList.EditItem", { id: -1, page: args.page })
28+
guiapi("TodoList.EditItem", { id: 0 })
2729
return false;
2830
}
2931
if (event.key != EnterKey) {
3032
return false;
3133
}
32-
guiapi("TodoList.UpdateItem", { id: args.id, text: event.target.value, page: args.page });
34+
guiapi("TodoList.UpdateItem", { id: args.id, text: event.target.value });
3335
})
3436
element.focus()
3537
}

examples/components/main.go

+28-17
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
package main
22

33
import (
4+
"fmt"
45
"log"
56
"net/http"
7+
8+
"github.com/evanw/esbuild/pkg/cli"
69
)
710

811
// TODO:
9-
// -[x] cookie based session
10-
// -[x] guiapi
11-
// -[x] make the storage swappable
12-
// -[x] counter demo
13-
// -[x] turn it into a reusable component
14-
// -[ ] extract page registration from component
15-
// -[ ] add a second page
16-
// -[ ] TODO MVC example?
17-
// -[ ] https://github.com/tastejs/todomvc/blob/master/app-spec.md
18-
19-
// maybe later:
20-
// -[ ] offer cockroachdb as storage
21-
// -[ ] text field for your name
22-
// -[ ] esbuild integration for more complex JS and importing
12+
// -[x] esbuild integration for more complex JS and importing
13+
// -[ ] fake page switching
2314

2415
type App struct {
2516
DB *DB
@@ -34,19 +25,39 @@ func NewApp() *App {
3425
}
3526

3627
func main() {
28+
err := buildBrowserAssets()
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
3733
app := NewApp()
3834
counter := &Counter{App: app}
3935
app.Server.RegisterComponent(counter)
4036
app.Server.RegisterPage("/counter", counter.RenderPage)
4137

4238
registerTodoList(app.Server, app.DB)
4339

44-
app.Server.Static("/js/", "./js")
45-
app.Server.Static("/css/", "./css")
40+
app.Server.Static("/dist/", "./dist")
4641

4742
log.Println("listening on localhost:8000")
48-
err := http.ListenAndServe("localhost:8000", app.Server.Handler())
43+
err = http.ListenAndServe("localhost:8000", app.Server.Handler())
4944
if err != nil {
5045
log.Fatal(err)
5146
}
5247
}
48+
49+
func buildBrowserAssets() error {
50+
log.Println("building browser assets")
51+
options := []string{
52+
"js/main.js",
53+
"--bundle",
54+
"--outfile=dist/bundle.js",
55+
"--minify",
56+
"--sourcemap",
57+
}
58+
returnCode := cli.Run(options)
59+
if returnCode != 0 {
60+
return fmt.Errorf("esbuild failed with code %d", returnCode)
61+
}
62+
return nil
63+
}

examples/components/todolist.go

+19-16
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ import (
2828
// todolist.js: 38 lines
2929
// total: 511 lines
3030

31+
// Code stats after refactoring (excluding comments and imports):
32+
// todolist.go: 405 lines -15%
33+
// todolist.js: 38 lines
34+
// total: 443 lines -14%
35+
3136
func registerTodoList(server *Server, db *DB) {
3237
tl := &TodoList{DB: db}
3338
server.RegisterComponent(tl)
@@ -88,7 +93,7 @@ func todoLayout(todoApp html.Block, state TodoListState) (html.Block, error) {
8893
html.Meta(html.Name("viewport").Content("width=device-width, initial-scale=1")),
8994
html.Title(nil, html.Text("Guiapi • TodoMVC")),
9095
html.Link(html.Rel("stylesheet").Href("https://cdn.jsdelivr.net/npm/[email protected]/index.min.css")),
91-
html.Link(html.Rel("stylesheet").Href("/css/main.css")),
96+
html.Link(html.Rel("stylesheet").Href("/dist/bundle.css")),
9297
),
9398
html.Body(nil,
9499
todoApp,
@@ -98,9 +103,8 @@ func todoLayout(todoApp html.Block, state TodoListState) (html.Block, error) {
98103
html.P(nil, html.Text("Created by "), html.A(html.Href("https://github.com/mbertschler"), html.Text("Martin Bertschler"))),
99104
html.P(nil, html.Text("Part of "), html.A(html.Href("http://todomvc.com"), html.Text("TodoMVC"))),
100105
),
101-
html.Script(html.Src("/js/guiapi.js")),
102106
html.Script(nil, html.JS("var state = "+string(stateJSON)+";")),
103-
html.Script(html.Src("/js/todolist.js")),
107+
html.Script(html.Src("/dist/bundle.js")),
104108
),
105109
),
106110
}, nil
@@ -195,30 +199,30 @@ func (t *TodoList) renderMainBlock(todos *StoredTodo, page string, editItemID in
195199
if page == TodoListPageCompleted && !item.Done {
196200
continue
197201
}
198-
items.Add(t.renderItem(&item, page, editItemID))
202+
items.Add(t.renderItem(&item, editItemID))
199203
}
200204
main := html.Elem("section", html.Class("main"),
201205
html.Input(html.Class("toggle-all").Attr("type", "checkbox")),
202-
html.Label(html.Class("ga").Attr("for", "toggle-all").Attr("ga-on", "click").Attr("ga-action", "TodoList.ToggleAll").
203-
Attr("ga-args", fmt.Sprintf(`{"page":%q}`, page)), html.Text("Mark all as complete")),
206+
html.Label(html.Class("ga").Attr("for", "toggle-all").Attr("ga-on", "click").Attr("ga-action", "TodoList.ToggleAll"),
207+
html.Text("Mark all as complete")),
204208
html.Ul(html.Class("todo-list"),
205209
items,
206210
),
207211
)
208212
return main, nil
209213
}
210214

211-
func (t *TodoList) renderItem(item *StoredTodoItem, page string, editItemID int) html.Block {
215+
func (t *TodoList) renderItem(item *StoredTodoItem, editItemID int) html.Block {
212216
if item.ID == editItemID {
213-
return t.renderItemEdit(item, page, editItemID)
217+
return t.renderItemEdit(item, editItemID)
214218
}
215219

216220
liAttrs := html.Attr("ga-on", "dblclick").
217221
Attr("ga-action", "TodoList.EditItem").
218-
Attr("ga-args", fmt.Sprintf(`{"id":%d,"page":%q}`, item.ID, page))
222+
Attr("ga-args", fmt.Sprintf(`{"id":%d}`, item.ID))
219223
inputAttrs := html.Class("toggle ga").Attr("type", "checkbox").
220224
Attr("ga-on", "click").Attr("ga-action", "TodoList.ToggleItem").
221-
Attr("ga-args", fmt.Sprintf(`{"id":%d,"page":%q}`, item.ID, page))
225+
Attr("ga-args", fmt.Sprintf(`{"id":%d}`, item.ID))
222226
if item.Done {
223227
liAttrs = liAttrs.Class("completed ga")
224228
inputAttrs = inputAttrs.Attr("checked", "")
@@ -232,17 +236,17 @@ func (t *TodoList) renderItem(item *StoredTodoItem, page string, editItemID int)
232236
html.Label(nil, html.Text(item.Text)),
233237
html.Button(html.Class("destroy ga").
234238
Attr("ga-on", "click").Attr("ga-action", "TodoList.DeleteItem").
235-
Attr("ga-args", fmt.Sprintf(`{"id":%d,"page":%q}`, item.ID, page))),
239+
Attr("ga-args", fmt.Sprintf(`{"id":%d}`, item.ID))),
236240
),
237241
)
238242
return li
239243
}
240244

241-
func (t *TodoList) renderItemEdit(item *StoredTodoItem, page string, editItemID int) html.Block {
245+
func (t *TodoList) renderItemEdit(item *StoredTodoItem, editItemID int) html.Block {
242246
li := html.Li(html.Class("editing"),
243247
html.Div(html.Class("view"),
244248
html.Input(html.Class("edit ga").Attr("ga-init", "initEdit").
245-
Attr("ga-args", fmt.Sprintf(`{"id":%d, "page":%q}`, item.ID, page)).Attr("value", item.Text)),
249+
Attr("ga-args", fmt.Sprintf(`{"id":%d}`, item.ID)).Attr("value", item.Text)),
246250
),
247251
)
248252
return li
@@ -277,8 +281,8 @@ func (t *TodoList) renderFooterBlock(todos *StoredTodo, page string) (html.Block
277281

278282
var clearCompletedButton html.Block
279283
if someDone {
280-
clearCompletedButton = html.Button(html.Class("clear-completed ga").Attr("ga-on", "click").Attr("ga-action", "TodoList.ClearCompleted").
281-
Attr("ga-args", fmt.Sprintf(`{"page":%q}`, page)), html.Text("Clear completed"))
284+
clearCompletedButton = html.Button(html.Class("clear-completed ga").Attr("ga-on", "click").Attr("ga-action", "TodoList.ClearCompleted"),
285+
html.Text("Clear completed"))
282286
}
283287

284288
footer := html.Elem("footer", html.Class("footer"),
@@ -316,7 +320,6 @@ func (t *TodoList) NewTodo(ctx *Context, input *NewTodoArgs) (*Response, error)
316320
}
317321
input.Text = strings.TrimSpace(input.Text)
318322
todos.Items = append(todos.Items, StoredTodoItem{ID: highestID + 1, Text: input.Text})
319-
320323
return t.DB.SetTodo(todos)
321324
})
322325
}

0 commit comments

Comments
 (0)