Skip to content

Commit f43c329

Browse files
committedJan 5, 2022
Add hot reloading and a websocket connecting directly to go
1 parent b961996 commit f43c329

13 files changed

+505
-62
lines changed
 

‎go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ require (
66
github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77
77
github.com/chromedp/chromedp v0.7.6
88
github.com/evanw/esbuild v0.14.1
9+
github.com/fsnotify/fsnotify v1.5.1
10+
github.com/gorilla/websocket v1.4.2
911
github.com/pkg/errors v0.9.1
1012
)

‎go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3I
66
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
77
github.com/evanw/esbuild v0.14.1 h1:ixWZ3MwLjTZp6WdDNfiZ6QhqgEryN8a0E5a8HDOmc58=
88
github.com/evanw/esbuild v0.14.1/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
9+
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
10+
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
911
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
1012
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
1113
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
1214
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
1315
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
1416
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
17+
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
18+
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
1519
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
1620
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
1721
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -21,6 +25,7 @@ github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzb
2125
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
2226
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
2327
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
28+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2429
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2530
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
2631
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

‎go/cmd/build.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"github.com/evanw/esbuild/pkg/api"
77
"github.com/webappio/greenjs/go/config"
8+
"github.com/webappio/greenjs/go/devserver"
89
"io/ioutil"
910
"log"
1011
"net"
@@ -27,7 +28,7 @@ func Build(args []string) {
2728
prerenderer := &Prerenderer{
2829
Config: conf,
2930
}
30-
var greenJsServer *GreenJsServer
31+
var greenJsServer *devserver.GreenJsServer
3132
var listener net.Listener
3233

3334
wg := sync.WaitGroup{}
@@ -40,7 +41,7 @@ func Build(args []string) {
4041
}
4142

4243
prerenderer.URI = "http://127.0.0.1:" + fmt.Sprint(listener.Addr().(*net.TCPAddr).Port)
43-
greenJsServer = &GreenJsServer{
44+
greenJsServer = &devserver.GreenJsServer{
4445
PageIsRoute: func(s string) bool {
4546
_, ok := prerenderer.pagesVisited.Load(s)
4647
return ok

‎go/cmd/start.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ package cmd
33
import (
44
"flag"
55
"fmt"
6-
"github.com/pkg/errors"
76
"github.com/webappio/greenjs/go/config"
7+
"github.com/webappio/greenjs/go/devserver"
88
"log"
99
"net"
1010
"os"
1111
)
1212

13-
var errNotFound = errors.New("not found")
14-
1513
func Start(args []string) {
1614
flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
1715

@@ -39,9 +37,10 @@ func Start(args []string) {
3937
log.Fatal(err)
4038
}
4139
fmt.Println("Server is listening at", listenAddr)
42-
err = (&GreenJsServer{
40+
err = (&devserver.GreenJsServer{
4341
UpstreamHost: upstreamAddr,
4442
BuildOpts: &buildOpts,
43+
InjectDevSidebar: true,
4544
}).Serve(listener)
4645
if err != nil {
4746
log.Fatal(err)

‎go/cmd/server.go ‎go/devserver/server.go

+82-5
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1-
package cmd
1+
package devserver
22

33
import (
44
"context"
55
"fmt"
66
"github.com/evanw/esbuild/pkg/api"
77
"github.com/pkg/errors"
88
"github.com/webappio/greenjs/go/resources"
9+
"github.com/fsnotify/fsnotify"
10+
"io/fs"
11+
"log"
912
"net"
1013
"net/http"
1114
"net/http/httputil"
1215
"net/url"
16+
"path/filepath"
17+
"strings"
1318
"sync"
1419
)
1520

21+
var errNotFound = errors.New("not found")
22+
1623
type GreenJsServer struct {
1724
UpstreamHost string
1825
BuildOpts *api.BuildOptions
1926
PageIsRoute func(string) bool
27+
InjectDevSidebar bool
28+
29+
fileChangeListeners sync.Map
2030

2131
server *http.Server
2232

@@ -38,14 +48,14 @@ func (srv *GreenJsServer) Serve(listener net.Listener) error {
3848
Setup: func(build api.PluginBuild) {
3949
build.OnEnd(func(result *api.BuildResult) {
4050
srv.errMessagesMutex.Lock()
41-
defer srv.errMessagesMutex.Unlock()
4251
srv.errMessages = result.Errors
52+
srv.errMessagesMutex.Unlock()
4353
})
4454
},
4555
})
4656

4757
result, err := api.Serve(api.ServeOptions{
48-
Servedir: srv.BuildOpts.Outdir,
58+
Servedir: opts.Outdir,
4959
}, opts)
5060
if err != nil {
5161
return errors.Wrap(err, "could not start eslint server")
@@ -82,6 +92,41 @@ func (srv *GreenJsServer) Serve(listener net.Listener) error {
8292
rw.Write([]byte("error")) //TODO make prettier
8393
}
8494

95+
watcher, err := fsnotify.NewWatcher()
96+
if err != nil {
97+
log.Fatal(err)
98+
}
99+
defer watcher.Close()
100+
go func() {
101+
for {
102+
select {
103+
case event, ok := <-watcher.Events:
104+
if !ok {
105+
return
106+
}
107+
if event.Op&fsnotify.Write == fsnotify.Write {
108+
srv.fileChangeListeners.Range(func(key, value interface{}) bool {
109+
value.(func())()
110+
return true
111+
})
112+
} else if event.Op&fsnotify.Create == fsnotify.Create {
113+
watcher.Add(event.Name)
114+
}
115+
case err, ok := <-watcher.Errors:
116+
if !ok {
117+
return
118+
}
119+
log.Println("error:", err)
120+
}
121+
}
122+
}()
123+
err = filepath.WalkDir(filepath.Dir(srv.BuildOpts.EntryPoints[0]), func(path string, d fs.DirEntry, err error) error {
124+
return watcher.Add(path)
125+
})
126+
if err != nil {
127+
log.Print(err)
128+
}
129+
85130
server := http.Server{Handler: srv}
86131
err = server.Serve(listener)
87132
if srv.stopped {
@@ -103,6 +148,31 @@ func (srv *GreenJsServer) Stop() {
103148
}
104149

105150
func (srv *GreenJsServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
151+
if req.URL.Path == "/greenjs-dev-connection" {
152+
srv.HandleWS(rw, req)
153+
return
154+
}
155+
if req.URL.Path == "/greenjs-devserver-client.js" {
156+
rw.Header().Set("Content-Type", "text/javascript")
157+
res := api.Build(api.BuildOptions{
158+
Write: false,
159+
Bundle: true,
160+
MinifySyntax: true,
161+
MinifyWhitespace: true,
162+
Stdin: &api.StdinOptions{
163+
Contents: string(resources.DevserverClientContents),
164+
Loader: api.LoaderJSX,
165+
},
166+
})
167+
for _, err := range res.Errors {
168+
log.Println(err.Text)
169+
log.Println(err.Notes)
170+
}
171+
if len(res.OutputFiles) == 1 {
172+
rw.Write(res.OutputFiles[0].Contents)
173+
}
174+
return
175+
}
106176
isIndex := req.URL.Path == "/"
107177
if srv.PageIsRoute != nil {
108178
isIndex = isIndex || srv.PageIsRoute(req.URL.Path)
@@ -112,9 +182,16 @@ func (srv *GreenJsServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
112182
srv.errMessagesMutex.Lock()
113183
hasErrors = len(srv.errMessages) > 0
114184
srv.errMessagesMutex.Unlock()
115-
rw.Header().Set("Content-Type", "text/html")
116185
if !hasErrors {
117-
rw.Write(resources.IndexHTML)
186+
devScript := ""
187+
if srv.InjectDevSidebar {
188+
devScript = `<div id="greenjs-client-element"></div><script src="/greenjs-devserver-client.js" type="module"></script>`
189+
}
190+
rw.Header().Set("Content-Type", "text/html")
191+
rw.Write([]byte(strings.NewReplacer(
192+
"<!-- dev script-->", devScript,
193+
"App.js", srv.BuildOpts.EntryPoints[0],
194+
).Replace(string(resources.IndexHTML))))
118195
return
119196
}
120197
}

‎go/devserver/sidebar.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package devserver
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"sync/atomic"
7+
"time"
8+
)
9+
import "github.com/gorilla/websocket"
10+
11+
var upgrader = websocket.Upgrader{}
12+
13+
var wsId = int32(0)
14+
15+
func (srv *GreenJsServer) HandleWS(rw http.ResponseWriter, req *http.Request) {
16+
log.Println("Got websocket connection")
17+
ws, err := upgrader.Upgrade(rw, req, nil)
18+
if err != nil {
19+
log.Println("Could not handle websocket request:", err)
20+
return
21+
}
22+
defer ws.Close()
23+
24+
protoState := struct{
25+
ReloadMode string `json:"reloadMode,omitempty"`
26+
LastChange time.Time `json:"lastChange,omitempty"`
27+
}{}
28+
29+
ourId := atomic.AddInt32(&wsId, 1)
30+
srv.fileChangeListeners.Store(ourId, func() {
31+
log.Print("New change detected")
32+
protoState.LastChange = time.Now()
33+
ws.WriteJSON(&protoState)
34+
})
35+
36+
defer func() {
37+
srv.fileChangeListeners.Delete(ourId)
38+
}()
39+
40+
for {
41+
err := ws.ReadJSON(&protoState)
42+
if err != nil {
43+
log.Println(err)
44+
return
45+
}
46+
}
47+
}

‎go/resources/000_server.js

-49
This file was deleted.
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

0 commit comments

Comments
 (0)
Please sign in to comment.