@ -11,30 +11,48 @@ import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"time"
"tailscale.com/tsweb"
)
//go:embed dist/* index.html
//go:embed index.html
var embeddedFS embed . FS
//go:embed dist/*
var embeddedDistFS embed . FS
var serveStartTime = time . Now ( )
func runServe ( ) {
mux := http . NewServeMux ( )
indexBytes , err := generateServeIndex ( )
var distFS fs . FS
if * distDir == "./dist" {
var err error
distFS , err = fs . Sub ( embeddedDistFS , "dist" )
if err != nil {
log . Fatalf ( "Could not drop dist/ prefix from embedded FS: %v" , err )
}
} else {
distFS = os . DirFS ( * distDir )
}
indexBytes , err := generateServeIndex ( distFS )
if err != nil {
log . Fatalf ( "Could not generate index.html: %v" , err )
}
mux . Handle ( "/" , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . ServeContent ( w , r , "index.html" , serveStartTime , bytes . NewReader ( indexBytes ) )
} ) )
mux . Handle ( "/dist/" , http . HandlerFunc ( handleServeDist ) )
mux . Handle ( "/dist/" , http . StripPrefix ( "/dist/" , http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
handleServeDist ( w , r , distFS )
} ) ) )
tsweb . Debugger ( mux )
log . Printf ( "Listening on %s" , * addr )
@ -44,14 +62,19 @@ func runServe() {
}
}
func generateServeIndex ( ) ( [ ] byte , error ) {
func generateServeIndex ( distFS fs . FS ) ( [ ] byte , error ) {
log . Printf ( "Generating index.html...\n" )
rawIndexBytes , err := embeddedFS . ReadFile ( "index.html" )
if err != nil {
return nil , fmt . Errorf ( "Could not read index.html: %w" , err )
}
esbuildMetadataBytes , err := embeddedFS . ReadFile ( "dist/esbuild-metadata.json" )
esbuildMetadataFile , err := distFS . Open ( "esbuild-metadata.json" )
if err != nil {
return nil , fmt . Errorf ( "Could not open esbuild-metadata.json: %w" , err )
}
defer esbuildMetadataFile . Close ( )
esbuildMetadataBytes , err := ioutil . ReadAll ( esbuildMetadataFile )
if err != nil {
return nil , fmt . Errorf ( "Could not read esbuild-metadata.json: %w" , err )
}
@ -62,7 +85,7 @@ func generateServeIndex() ([]byte, error) {
entryPointsToHashedDistPaths := make ( map [ string ] string )
for outputPath , output := range esbuildMetadata . Outputs {
if output . EntryPoint != "" {
entryPointsToHashedDistPaths [ output . EntryPoint ] = outputPath
entryPointsToHashedDistPaths [ output . EntryPoint ] = path. Join ( "dist" , outputPath)
}
}
@ -77,39 +100,30 @@ func generateServeIndex() ([]byte, error) {
return indexBytes , nil
}
// EsbuildMetadata is the subset of metadata struct (described by
// https://esbuild.github.io/api/#metafile) that we care about for mapping
// from entry points to hashed file names.
type EsbuildMetadata = struct {
Outputs map [ string ] struct {
EntryPoint string ` json:"entryPoint,omitempty" `
} ` json:"outputs,omitempty" `
}
var entryPointsToDefaultDistPaths = map [ string ] string {
"src/index.css" : "dist/index.css" ,
"src/index.js" : "dist/index.js" ,
}
func handleServeDist ( w http . ResponseWriter , r * http . Request ) {
p := r . URL . Path [ 1 : ]
func handleServeDist ( w http . ResponseWriter , r * http . Request , distFS fs . FS ) {
path := r . URL . Path
var f fs . File
// Prefer pre-compressed versions generated during the build step.
if tsweb . AcceptsEncoding ( r , "br" ) {
if brotliFile , err := embeddedFS. Open ( p + ".br" ) ; err == nil {
if brotliFile , err := distFS. Open ( path + ".br" ) ; err == nil {
f = brotliFile
w . Header ( ) . Set ( "Content-Encoding" , "br" )
}
}
if f == nil && tsweb . AcceptsEncoding ( r , "gzip" ) {
if gzipFile , err := embeddedFS. Open ( p + ".gz" ) ; err == nil {
if gzipFile , err := distFS. Open ( path + ".gz" ) ; err == nil {
f = gzipFile
w . Header ( ) . Set ( "Content-Encoding" , "gzip" )
}
}
if f == nil {
if rawFile , err := embeddedFS. Open ( r . URL . Path [ 1 : ] ) ; err == nil {
if rawFile , err := distFS. Open ( path ) ; err == nil {
f = rawFile
} else {
http . Error ( w , err . Error ( ) , http . StatusNotFound )
@ -130,5 +144,5 @@ func handleServeDist(w http.ResponseWriter, r *http.Request) {
w . Header ( ) . Set ( "Cache-Control" , "public, max-age=31535996" )
w . Header ( ) . Set ( "Vary" , "Accept-Encoding" )
http . ServeContent ( w , r , path .Base ( r . URL . Path ) , serveStartTime , fSeeker )
http . ServeContent ( w , r , path , serveStartTime , fSeeker )
}