сборка сервера фронта в бинарник

This commit is contained in:
Artem Tsyrulnikov
2026-03-25 12:16:57 +03:00
parent 045cb4232a
commit 99920fca56
4 changed files with 111 additions and 0 deletions

19
build.sh Executable file
View 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

Binary file not shown.

3
server/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module protoc-frontend-server
go 1.25.5

89
server/main.go Normal file
View 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)
}
}