Add build option to embed static files in the built binary

This commit is contained in:
Alessio 2023-08-19 01:58:31 -03:00
parent 4273413e98
commit ff3dba9980
4 changed files with 58 additions and 30 deletions

View File

@ -4,8 +4,8 @@ set -x
set -e set -e
if [[ -n "$1" ]]; then if [[ -n "$1" ]]; then
go build -ldflags="-s -w -X main.version_string=$1" -o tw ./twitter go build -ldflags="-s -w -X main.version_string=$1 -X webserver.is_production=true" -o tw ./twitter
else else
go build -ldflags="-s -w" -o tw ./twitter go build -ldflags="-s -w -X webserver.is_production=true" -o tw ./twitter
fi fi
chmod +x tw chmod +x tw

View File

@ -0,0 +1,10 @@
package webserver
import (
"embed"
)
//go:embed "tpl" "static"
var embedded_files embed.FS
var use_embedded = false

View File

@ -5,8 +5,11 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
"io/fs"
"net/http" "net/http"
"path"
"path/filepath" "path/filepath"
"runtime"
"runtime/debug" "runtime/debug"
"github.com/Masterminds/sprig/v3" "github.com/Masterminds/sprig/v3"
@ -20,6 +23,32 @@ func panic_if(err error) {
} }
} }
var this_dir string
func init() {
_, this_file, _, _ := runtime.Caller(0) // `this_file` is absolute path to this source file
this_dir = path.Dir(this_file)
}
func get_filepath(s string) string {
if use_embedded {
return s
}
return path.Join(this_dir, s)
}
func glob(path string) []string {
var ret []string
var err error
if use_embedded {
ret, err = fs.Glob(embedded_files, get_filepath(path))
} else {
ret, err = filepath.Glob(get_filepath(path))
}
panic_if(err)
return ret
}
// func (app *Application) error_400(w http.ResponseWriter) { // func (app *Application) error_400(w http.ResponseWriter) {
// http.Error(w, "Bad Request", 400) // http.Error(w, "Bad Request", 400)
// } // }
@ -52,11 +81,7 @@ type TweetCollection interface {
// Creates a template from the given template file using all the available partials. // Creates a template from the given template file using all the available partials.
// Calls `app.buffered_render` to render the created template. // Calls `app.buffered_render` to render the created template.
func (app *Application) buffered_render_tweet_page(w http.ResponseWriter, tpl_file string, data TweetCollection) { func (app *Application) buffered_render_tweet_page(w http.ResponseWriter, tpl_file string, data TweetCollection) {
partials, err := filepath.Glob(get_filepath("tpl/includes/*.tpl")) partials := append(glob("tpl/includes/*.tpl"), glob("tpl/tweet_page_includes/*.tpl")...)
panic_if(err)
tweet_partials, err := filepath.Glob(get_filepath("tpl/tweet_page_includes/*.tpl"))
panic_if(err)
partials = append(partials, tweet_partials...)
r := renderer{ r := renderer{
Funcs: func_map(template.FuncMap{ Funcs: func_map(template.FuncMap{
@ -77,8 +102,7 @@ func (app *Application) buffered_render_tweet_page(w http.ResponseWriter, tpl_fi
// Creates a template from the given template file using all the available partials. // Creates a template from the given template file using all the available partials.
// Calls `app.buffered_render` to render the created template. // Calls `app.buffered_render` to render the created template.
func (app *Application) buffered_render_basic_page(w http.ResponseWriter, tpl_file string, data interface{}) { func (app *Application) buffered_render_basic_page(w http.ResponseWriter, tpl_file string, data interface{}) {
partials, err := filepath.Glob(get_filepath("tpl/includes/*.tpl")) partials := glob("tpl/includes/*.tpl")
panic_if(err)
r := renderer{ r := renderer{
Funcs: func_map(template.FuncMap{"active_user": app.get_active_user}), Funcs: func_map(template.FuncMap{"active_user": app.get_active_user}),
@ -90,11 +114,7 @@ func (app *Application) buffered_render_basic_page(w http.ResponseWriter, tpl_fi
} }
func (app *Application) buffered_render_tweet_htmx(w http.ResponseWriter, tpl_name string, data TweetCollection) { func (app *Application) buffered_render_tweet_htmx(w http.ResponseWriter, tpl_name string, data TweetCollection) {
partials, err := filepath.Glob(get_filepath("tpl/includes/*.tpl")) partials := append(glob("tpl/includes/*.tpl"), glob("tpl/tweet_page_includes/*.tpl")...)
panic_if(err)
tweet_partials, err := filepath.Glob(get_filepath("tpl/tweet_page_includes/*.tpl"))
panic_if(err)
partials = append(partials, tweet_partials...)
r := renderer{ r := renderer{
Funcs: func_map(template.FuncMap{ Funcs: func_map(template.FuncMap{
@ -113,8 +133,7 @@ func (app *Application) buffered_render_tweet_htmx(w http.ResponseWriter, tpl_na
} }
func (app *Application) buffered_render_basic_htmx(w http.ResponseWriter, tpl_name string, data interface{}) { func (app *Application) buffered_render_basic_htmx(w http.ResponseWriter, tpl_name string, data interface{}) {
partials, err := filepath.Glob(get_filepath("tpl/includes/*.tpl")) partials := glob("tpl/includes/*.tpl")
panic_if(err)
r := renderer{ r := renderer{
Funcs: func_map(template.FuncMap{"active_user": app.get_active_user}), Funcs: func_map(template.FuncMap{"active_user": app.get_active_user}),
@ -147,7 +166,13 @@ type renderer struct {
// Render the given template using a bytes.Buffer. This avoids the possibility of failing partway // Render the given template using a bytes.Buffer. This avoids the possibility of failing partway
// through the rendering, and sending an imcomplete response with "Bad Request" or "Server Error" at the end. // through the rendering, and sending an imcomplete response with "Bad Request" or "Server Error" at the end.
func (r renderer) BufferedRender(w io.Writer) { func (r renderer) BufferedRender(w io.Writer) {
tpl, err := template.New("").Funcs(r.Funcs).ParseFiles(r.Filenames...) var tpl *template.Template
var err error
if use_embedded {
tpl, err = template.New("").Funcs(r.Funcs).ParseFS(embedded_files, r.Filenames...)
} else {
tpl, err = template.New("").Funcs(r.Funcs).ParseFiles(r.Filenames...)
}
panic_if(err) panic_if(err)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)

View File

@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -88,17 +87,6 @@ func get_default_user() scraper.User {
} }
} }
var this_dir string
func init() {
_, this_file, _, _ := runtime.Caller(0) // `this_file` is absolute path to this source file
this_dir = path.Dir(this_file)
}
func get_filepath(s string) string {
return path.Join(this_dir, s)
}
// Manual router implementation. // Manual router implementation.
// I don't like the weird matching behavior of http.ServeMux, and it's not hard to write by hand. // I don't like the weird matching behavior of http.ServeMux, and it's not hard to write by hand.
func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -110,7 +98,12 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")[1:] parts := strings.Split(r.URL.Path, "/")[1:]
switch parts[0] { switch parts[0] {
case "static": case "static":
if use_embedded {
// Serve directly from the embedded files
http.FileServer(http.FS(embedded_files)).ServeHTTP(w, r)
} else {
http.StripPrefix("/static", http.FileServer(http.Dir(get_filepath("static")))).ServeHTTP(w, r) http.StripPrefix("/static", http.FileServer(http.Dir(get_filepath("static")))).ServeHTTP(w, r)
}
case "tweet": case "tweet":
app.TweetDetail(w, r) app.TweetDetail(w, r)
case "content": case "content":