Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: replace webpack-dev-server with watch #2246

Merged
merged 11 commits into from
Mar 17, 2022
Merged
4 changes: 2 additions & 2 deletions Procfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
build: while true; do make -qs bin/goalert || make bin/goalert || (echo '\033[0;31mBuild Failure'; sleep 3); sleep 0.1; done

@watch-file=./bin/goalert
goalert: ./bin/goalert -l=localhost:3030 --ui-url=http://localhost:3035 --db-url=postgres://goalert@localhost:5432/goalert?sslmode=disable --listen-sysapi=localhost:1234 --listen-prometheus=localhost:2112
goalert: ./bin/goalert -l=localhost:3030 --ui-dir=web/src/build --db-url=postgres://goalert@localhost:5432/goalert?sslmode=disable --listen-sysapi=localhost:1234 --listen-prometheus=localhost:2112

smtp: go run github.com/mailhog/MailHog -ui-bind-addr=localhost:8025 -api-bind-addr=localhost:8025 -smtp-bind-addr=localhost:1025 | grep -v KEEPALIVE
prom: bin/tools/prometheus --log.level=warn --config.file=devtools/prometheus/prometheus.yml --storage.tsdb.path=bin/prom-data/ --web.listen-address=localhost:9090

@watch-file=./web/src/webpack.config.js
ui: yarn workspace goalert-web webpack serve --config ./webpack.config.js
ui: yarn workspace goalert-web webpack --config ./webpack.config.js --watch
mastercactapus marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions Procfile.cypress
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
build: while true; do make -qs bin/goalert || make bin/goalert || (echo '\033[0;31mBuild Failure'; sleep 3); sleep 0.1; done

@watch-file=./bin/goalert
goalert: go run ./devtools/waitfor postgres://postgres@localhost:5433 && go run ./devtools/procwrap -test=localhost:3042 bin/goalert -l=localhost:3042 --ui-url=http://localhost:3045 --db-url=postgres://postgres@localhost:5433 --slack-base-url=http://localhost:3040/slack --log-errors-only
goalert: go run ./devtools/waitfor postgres://postgres@localhost:5433 && go run ./devtools/procwrap -test=localhost:3042 bin/goalert -l=localhost:3042 --ui-dir=web/src/build --db-url=postgres://postgres@localhost:5433 --slack-base-url=http://localhost:3040/slack --log-errors-only

@watch-file=./web/src/webpack.config.js
ui: yarn workspace goalert-web webpack serve --config ./webpack.config.js --port=3045
ui: yarn workspace goalert-web webpack --config ./webpack.config.js --watch
mastercactapus marked this conversation as resolved.
Show resolved Hide resolved

slack: go run ./devtools/mockslack/cmd/mockslack -client-id=000000000000.000000000000 -client-secret=00000000000000000000000000000000 -access-token=xoxp-000000000000-000000000000-000000000000-00000000000000000000000000000000 -prefix=/slack -single-user=bob -addr=localhost:3046

Expand Down
9 changes: 3 additions & 6 deletions app/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ var (
Use: "version",
Short: "Output the current version.",
RunE: func(cmd *cobra.Command, args []string) error {

migrations := migrate.Names()

fmt.Printf(`Version: %s
Expand Down Expand Up @@ -472,7 +471,6 @@ Migration: %s (#%d)
up := viper.GetString("up")
if down != "" {
n, err := migrate.Down(ctx, c.DBURL, down)

if err != nil {
return errors.Wrap(err, "apply DOWN migrations")
}
Expand All @@ -483,7 +481,6 @@ Migration: %s (#%d)

if up != "" || down == "" {
n, err := migrate.Up(ctx, c.DBURL, up)

if err != nil {
return errors.Wrap(err, "apply UP migrations")
}
Expand All @@ -500,7 +497,6 @@ Migration: %s (#%d)
Use: "set-config",
Short: "Sets current config values in the DB from stdin.",
RunE: func(cmd *cobra.Command, args []string) error {

if viper.GetString("data-encryption-key") == "" && !viper.GetBool("allow-empty-data-encryption-key") {
return validation.NewFieldError("data-encryption-key", "Must not be empty, or set --allow-empty-data-encryption-key")
}
Expand Down Expand Up @@ -689,7 +685,7 @@ func getConfig(ctx context.Context) (Config, error) {

StubNotifiers: viper.GetBool("stub-notifiers"),

UIURL: viper.GetString("ui-url"),
UIDir: viper.GetString("ui-dir"),
}

if cfg.DBURL == "" {
Expand Down Expand Up @@ -773,7 +769,8 @@ func init() {
RootCmd.PersistentFlags().Bool("json", def.JSON, "Log in JSON format.")
RootCmd.PersistentFlags().Bool("log-errors-only", false, "Only log errors (superseeds other flags).")

RootCmd.Flags().String("ui-url", def.UIURL, "Proxy UI requests to an alternate host. Default is to serve bundled assets from memory.")
RootCmd.Flags().String("ui-dir", "", "Serve UI assets from a local directory instead of from memory.")

RootCmd.Flags().Bool("disable-https-redirect", def.DisableHTTPSRedirect, "Disable automatic HTTPS redirects.")

migrateCmd.Flags().String("up", "", "Target UP migration to apply.")
Expand Down
2 changes: 1 addition & 1 deletion app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type Config struct {

StubNotifiers bool

UIURL string
UIDir string

// InitialConfig will be pushed into the config store
// if specified before the engine is started.
Expand Down
3 changes: 1 addition & 2 deletions app/inithttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ func (app *App) initHTTP(ctx context.Context) error {
*app.twilioConfig,
)
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

if strings.HasPrefix(req.URL.Path, "/api/v2/twilio/") {
twilioHandler.ServeHTTP(w, req)
return
Expand All @@ -270,7 +269,7 @@ func (app *App) initHTTP(ctx context.Context) error {
mux.HandleFunc("/health", app.healthCheck)
mux.HandleFunc("/health/engine", app.engineStatus)

webH, err := web.NewHandler(app.cfg.UIURL, app.cfg.HTTPPrefix)
webH, err := web.NewHandler(app.cfg.UIDir, app.cfg.HTTPPrefix)
if err != nil {
return err
}
Expand Down
63 changes: 63 additions & 0 deletions web/etaghandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package web

import (
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"sync"
)

type etagHandler struct {
tags map[string]string
h http.Handler
fs http.FileSystem
mx sync.Mutex
static bool
}

func NewEtagFileServer(files http.FileSystem, static bool) http.Handler {
return &etagHandler{
tags: make(map[string]string),
h: http.FileServer(files),
fs: files,
static: static,
}
}

func (e *etagHandler) etag(name string) string {
e.mx.Lock()
defer e.mx.Unlock()

if tag, ok := e.tags[name]; e.static && ok {
return tag
}

f, err := e.fs.Open(name)
if err != nil {
e.tags[name] = ""
return ""
}
defer f.Close()

h := sha256.New()

_, err = io.Copy(h, f)
if err != nil {
e.tags[name] = ""
return ""
}

tag := `W/"` + hex.EncodeToString(h.Sum(nil)) + `"`
e.tags[name] = tag
return tag
}

func (e *etagHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if tag := e.etag(req.URL.Path); tag != "" {
w.Header().Set("Cache-Control", "public; max-age=60, stale-while-revalidate=600, stale-if-error=259200")
w.Header().Set("ETag", tag)
}

e.h.ServeHTTP(w, req)
}
75 changes: 25 additions & 50 deletions web/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,36 @@ import (
"embed"
"encoding/hex"
"fmt"
"github.com/target/goalert/config"
"github.com/target/goalert/util/errutil"
"html/template"
"io/fs"
"net/http"
"net/http/httputil"
"net/url"
"path"
"sync"
"time"

"github.com/pkg/errors"
"github.com/target/goalert/config"
"github.com/target/goalert/util/errutil"
)

//go:embed src/build
var bundleFS embed.FS

//go:embed live.js
var liveJS string

// NewHandler creates a new http.Handler that will serve UI files
// using bundled assets or by proxying to urlStr if set.
func NewHandler(urlStr, prefix string) (http.Handler, error) {
// using bundled assets or locally if uiDir if set.
func NewHandler(uiDir, prefix string) (http.Handler, error) {
mux := http.NewServeMux()

etags := make(map[string]string)
var mx sync.Mutex
calcTag := func(name string, data []byte) string {
mx.Lock()
defer mx.Unlock()
tag, ok := etags[name]
if ok {
return tag
}
sum := sha256.Sum256(data)
tag = `W/"` + hex.EncodeToString(sum[:]) + `"`
etags[name] = tag
return tag
}

var extraScripts []string
if urlStr == "" {
mux.HandleFunc("/static/", func(w http.ResponseWriter, req *http.Request) {
data, err := bundleFS.ReadFile(path.Join("src/build", req.URL.Path))
if errors.Is(err, fs.ErrNotExist) {
http.NotFound(w, req)
return
}

w.Header().Set("Cache-Control", "public; max-age=60, stale-while-revalidate=600, stale-if-error=259200")
w.Header().Set("ETag", calcTag(req.URL.Path, data))

http.ServeContent(w, req, req.URL.Path, time.Time{}, bytes.NewReader(data))
})
var extraJS string
if uiDir != "" {
extraJS = liveJS
mux.Handle("/static/", NoCache(NewEtagFileServer(http.Dir(uiDir), false)))
} else {
u, err := url.Parse(urlStr)
sub, err := fs.Sub(bundleFS, "src/build")
if err != nil {
return nil, errors.Wrap(err, "parse url")
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(u)
mux.Handle("/static/", proxy)
mux.Handle("/build/", proxy)

// dev mode
extraScripts = []string{"vendor.js"}
mux.Handle("/static/", NewEtagFileServer(http.FS(sub), true))
}

mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
Expand All @@ -76,17 +45,23 @@ func NewHandler(urlStr, prefix string) (http.Handler, error) {
err := indexTmpl.Execute(&buf, renderData{
ApplicationName: cfg.ApplicationName(),
Prefix: prefix,
ExtraScripts: extraScripts,
ExtraJS: template.JS(extraJS),
})
if errutil.HTTPError(req.Context(), w, err) {
return
}

h := sha256.New()
h.Write(buf.Bytes())
indexETag := fmt.Sprintf(`"sha256-%s"`, hex.EncodeToString(h.Sum(nil)))

w.Header().Set("Cache-Control", "private; max-age=60, stale-while-revalidate=600, stale-if-error=259200")
indexETag := fmt.Sprintf(`W/"sha256-%s"`, hex.EncodeToString(h.Sum(nil)))
w.Header().Set("ETag", indexETag)

if uiDir == "" {
w.Header().Set("Cache-Control", "private; max-age=60, stale-while-revalidate=600, stale-if-error=259200")
} else {
w.Header().Set("Cache-Control", "no-store")
}

http.ServeContent(w, req, "/", time.Time{}, bytes.NewReader(buf.Bytes()))
})

Expand Down
6 changes: 2 additions & 4 deletions web/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ func AppVersion() string {
}

return versionStr

}

type renderData struct {
Expand All @@ -52,9 +51,8 @@ type renderData struct {
// Prefix is the URL prefix for the GoAlert application.
Prefix string

// ExtraScripts can be used to load additional javascript files
// before `app.js`.
ExtraScripts []string
// ExtraScripts can be used to load additional javascript.
mastercactapus marked this conversation as resolved.
Show resolved Hide resolved
ExtraJS template.JS
}

func (r renderData) PathPrefix() string { return strings.TrimSuffix(r.Prefix, "/") }
Expand Down
8 changes: 5 additions & 3 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
pathPrefix = {{.PathPrefix}};
applicationName = {{.ApplicationName}};
</script>
{{- $prefix := .Prefix}} {{- range .ExtraScripts}}
<script src="{{$prefix}}/static/{{.}}"></script>
{{- end}}
<script src="{{.Prefix}}/static/app.js"></script>
{{- if .ExtraJS}}
<script defer>
{{.ExtraJS}}
</script>
{{- end}}
</body>
</html>
Loading