сборка сервера фронта в бинарник
This commit is contained in:
19
build.sh
Executable file
19
build.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Building frontend ==="
|
||||
npm run build
|
||||
|
||||
echo "=== Copying dist to server/ ==="
|
||||
rm -rf server/dist
|
||||
cp -r dist server/dist
|
||||
|
||||
echo "=== Building Go binary ==="
|
||||
cd server
|
||||
go mod init protoc-frontend-server 2>/dev/null || true
|
||||
GOOS=windows GOARCH=amd64 go build -o ../protoc-frontend.exe .
|
||||
|
||||
echo ""
|
||||
echo "=== Done! ==="
|
||||
echo "Binary: protoc-frontend.exe"
|
||||
echo "Usage: protoc-frontend.exe [-port 1111] [-api http://localhost:8000]"
|
||||
BIN
protoc-frontend.exe
Executable file
BIN
protoc-frontend.exe
Executable file
Binary file not shown.
3
server/go.mod
Normal file
3
server/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module protoc-frontend-server
|
||||
|
||||
go 1.25.5
|
||||
89
server/main.go
Normal file
89
server/main.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
var distFS embed.FS
|
||||
|
||||
func main() {
|
||||
port := flag.Int("port", 1112, "port to listen on")
|
||||
apiTarget := flag.String("api", "http://localhost:8000", "backend API URL to proxy /api requests")
|
||||
flag.Parse()
|
||||
|
||||
// Proxy for /api -> backend
|
||||
backendURL, err := url.Parse(*apiTarget)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid api url: %v", err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(backendURL)
|
||||
originalDirector := proxy.Director
|
||||
proxy.Director = func(r *http.Request) {
|
||||
originalDirector(r)
|
||||
// Rewrite /api/... -> /...
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
||||
if r.URL.Path == "" {
|
||||
r.URL.Path = "/"
|
||||
}
|
||||
r.Host = backendURL.Host
|
||||
}
|
||||
|
||||
// Static files from embedded dist/
|
||||
distSub, err := fs.Sub(distFS, "dist")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open embedded dist: %v", err)
|
||||
}
|
||||
fileServer := http.FileServer(http.FS(distSub))
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// API proxy
|
||||
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
|
||||
proxy.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
// SPA: serve static files, fallback to index.html
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Try to serve the file directly
|
||||
path := strings.TrimPrefix(r.URL.Path, "/")
|
||||
if path == "" {
|
||||
path = "index.html"
|
||||
}
|
||||
|
||||
// Check if file exists in embedded FS
|
||||
if f, err := distSub.Open(path); err == nil {
|
||||
f.Close()
|
||||
fileServer.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback to index.html for SPA routing
|
||||
indexFile, err := fs.ReadFile(distSub, "index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "index.html not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write(indexFile)
|
||||
})
|
||||
|
||||
addr := fmt.Sprintf(":%d", *port)
|
||||
log.Printf("Serving on http://localhost%s", addr)
|
||||
log.Printf("API proxy -> %s", *apiTarget)
|
||||
|
||||
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user