From 2f6535a5a9199c0256640ba3a1b00653c13026a9 Mon Sep 17 00:00:00 2001 From: Rob Thorne Date: Sun, 24 Apr 2022 21:18:31 -0700 Subject: [PATCH] Added support for React hot loading. --- .gitignore | 4 + Makefile | 32 + README.md | 2 + asset-server.go | 42 +- dev-proxy.go | 27 + examples/sample-program/main.go | 5 +- examples/sample-program/test-template.tmpl | 5 +- package.json | 15 + react/.gitignore | 2 + react/preamble.js | 880 +++++++++++++++++++++ react/refresh-loader.js | 6 + tags.go | 11 +- vueglue.go | 8 + 13 files changed, 1030 insertions(+), 9 deletions(-) create mode 100644 Makefile create mode 100644 dev-proxy.go create mode 100644 package.json create mode 100644 react/.gitignore create mode 100644 react/preamble.js create mode 100644 react/refresh-loader.js diff --git a/.gitignore b/.gitignore index c9fa4b4..c0b532a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,10 @@ dist dist-ssr *.local +# we build a copy of the react preamble, so +# package-lock.json is not in the repo. +package-lock.json + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b2b969e --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ + +BROWSERFY := $(shell command -v browserify 2> /dev/null ) + +clean: + @echo clean up preable files... + @- rm -rf package-lock.js node_modules react/preamble.js + @echo cleaned up. + +node_modules: package.json + @echo installing node dependencies + @npm install + +node_modules/react-refresh/runtime.js: node_modules + +node_modules/react-refresh: node_modules/react-refresh/runtime.js + @echo installing react-refresh + @npm install react-refresh@latest + +react/preamble.js: node_modules/react-refresh/runtime.js +ifndef BROWSERFY + @echo browserfy not found so installing + @npm install -g browserify +endif + @echo installing preamble + @browserify react/refresh-loader.js -o react/preamble.js + +react: react/preamble.js + +test: + @echo running tests... + @go test -v . + diff --git a/README.md b/README.md index 023bdef..682860d 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ func main() { Environment: "production", AssetsPath: "dist", EntryPoint: "src/main.js", + Platform: "vue", FS: dist, } @@ -113,6 +114,7 @@ func main() { Environment: "development", AssetsPath: "frontend", EntryPoint: "src/main.js", + Platform: "vue", FS: os.DirFS("frontend"), } diff --git a/asset-server.go b/asset-server.go index b637c17..3d10b63 100644 --- a/asset-server.go +++ b/asset-server.go @@ -1,6 +1,7 @@ package vueglue import ( + "embed" "io/fs" "log" "net/http" @@ -8,6 +9,9 @@ import ( "strings" ) +//go:embed react +var embedFiles embed.FS + // FileServer is a customized version of http.FileServer // that can handle either an embed.FS or a os.DirFS fs.FS. // Since development directories used for hot updates @@ -56,6 +60,22 @@ func (vg *VueGlue) guardedFileServer(serveDir fs.FS) http.Handler { } } + // handle any special-cased files + if len(parts) > 0 { + baseFile := parts[len(parts)-1] + if baseFile == "preamble.js" { + // react preamble file + bytes, err := embedFiles.ReadFile("react/preamble.js") + if err != nil { + log.Println("could not load preamble:", err) + http.NotFound(w, r) + return + } + serveOneFile(w, r, bytes, "application/javascript") + return + } + } + if vg.Debug { log.Println("entered FS", r.URL.Path) dir, err := fs.ReadDir(serveDir, ".") @@ -70,8 +90,8 @@ func (vg *VueGlue) guardedFileServer(serveDir fs.FS) http.Handler { } } - - fileServer := http.StripPrefix(stripPrefix, http.FileServer(http.FS(serveDir))) + loggingFS := logRequest(http.FileServer(http.FS(serveDir))) + fileServer := http.StripPrefix(stripPrefix, loggingFS) fileServer.ServeHTTP(w, r) } @@ -112,3 +132,21 @@ func (wrpr wrapperFS) Open(path string) (fs.File, error) { return f, nil } + +// serveOneFile is used for serving special-cased files. +func serveOneFile(w http.ResponseWriter, r *http.Request, data []byte, ctype string) { + w.Header().Add("Content-Type", ctype) + _, err := w.Write(data) + if err != nil { + log.Println("could not write file:", err) + } +} + +func logRequest(next http.Handler) http.Handler { + log.Println("invoked log handler") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s - %s %s %s", r.RemoteAddr, r.Proto, r.Method, r.URL.RequestURI()) + + next.ServeHTTP(w, r) + }) +} diff --git a/dev-proxy.go b/dev-proxy.go new file mode 100644 index 0000000..34b39ca --- /dev/null +++ b/dev-proxy.go @@ -0,0 +1,27 @@ +package vueglue + +import ( + "log" + "net/http" +) + +// Redirector for dev server + +func (vg *VueGlue) DevServerRedirector() http.Handler { + + handler := func(w http.ResponseWriter, r *http.Request) { + original := r.URL.Path + prefix := "/dev/" + if len(original) < len(prefix) || original[:len(prefix)] != prefix { + http.NotFound(w, r) + return + } + + rest := original[len(prefix)-1:] + log.Println("rest: ", rest) + w.Header().Set("Content-Type", "application/javascript") + http.Redirect(w, r, vg.DevServer+rest, http.StatusPermanentRedirect) + } + + return http.HandlerFunc(handler) +} diff --git a/examples/sample-program/main.go b/examples/sample-program/main.go index 977e214..57a2fb7 100644 --- a/examples/sample-program/main.go +++ b/examples/sample-program/main.go @@ -13,6 +13,7 @@ import ( var environment string var assets string var jsEntryPoint string +var platform = "vue" var vueData *vueglue.VueGlue @@ -40,6 +41,7 @@ func main() { flag.StringVar(&environment, "env", "development", "development|production") flag.StringVar(&assets, "assets", "frontend", "location of javascript files. dist for production.") flag.StringVar(&jsEntryPoint, "entryp", "src/main.js", "relative path of the entry point of the js app.") + flag.StringVar(&platform, "platform", "vue", "target platform for JavaScript (vue|react|svelte") flag.Parse() // We pass the file system with the built Vue @@ -51,6 +53,7 @@ func main() { config.Environment = environment config.AssetsPath = assets config.EntryPoint = jsEntryPoint + config.Platform = platform //config.FS = os.DirFS(assets) config.FS = dist @@ -79,7 +82,7 @@ func main() { return } mux.Handle("/src/", fsHandler) - mux.HandleFunc("/", pageWithAVue) + mux.Handle("/", logRequest(http.HandlerFunc(pageWithAVue))) log.Println("Starting server on :4000") err = http.ListenAndServe(":4000", mux) diff --git a/examples/sample-program/test-template.tmpl b/examples/sample-program/test-template.tmpl index 7346d36..0ffaf0c 100644 --- a/examples/sample-program/test-template.tmpl +++ b/examples/sample-program/test-template.tmpl @@ -7,10 +7,7 @@ {{ if $vue }} {{ $vue.RenderTags }} {{ end }} - - +