RemcoE33
RemcoE33

Reputation: 1620

Embedd sveltekit in golang binary

I'm trying to serve a single binary with embedd to include the SvelteKit website. I use Chi as my router. But I cannot get it to work. I'm getting one of these options below. As I understand the embedd all: option makes sure that the files prefixed with _ are included. I also have tried variations in the StripPrefix method in main V1: /uibuild/ or uibuild/ etc...

Can someone shine some light on it?

Sample repo

  1. List of directory(s), in my case `uibuild`
  2. Blank page at "/" but in the chrome console 404's on nested files
  3. 404 on the main page "/".

Svelte config:

import preprocess from "svelte-preprocess";
import adapter from "@sveltejs/adapter-static";

/** @type {import('@sveltejs/kit').Config} */
const config = {
  kit: {
    adapter: adapter({
      pages: "./../server/uibuild",
      assets: "./../server/uibuild",
      fallback: "index.html",
    }),
  },

  preprocess: [
    preprocess({
      postcss: true,
    }),
  ],
};

export default config;

Main.go V1:

This gives error 3.

package main

import (
    "embed"
    "log"
    "net/http"

    chi "github.com/go-chi/chi/v5"
)

//go:embed all:uibuild
var svelteStatic embed.FS

func main() {

    r := chi.NewRouter()

    r.Handle("/", http.StripPrefix("/uibuild", http.FileServer(http.FS(svelteStatic))))

    log.Fatal(http.ListenAndServe(":8082", r))
}

Main.go V2:

This will give error 2.

static, err := fs.Sub(svelteStatic, "uibuild")
    if err != nil {
        panic(err)
    }

r := chi.NewRouter()
r.Handle("/", http.FileServer(http.FS(static)))

log.Fatal(http.ListenAndServe(":8082", r))

File structure:

.
├── go.mod
├── go.sum
├── main.go
└── uibuild
    ├── _app
    │   ├── immutable
    │   │   ├── assets
    │   │   │   ├── 0.d7cb9c3b.css
    │   │   │   └── _layout.d7cb9c3b.css
    │   │   ├── chunks
    │   │   │   ├── index.6dba6488.js
    │   │   │   └── singletons.b716dd01.js
    │   │   ├── entry
    │   │   │   ├── app.c5e2a2d5.js
    │   │   │   └── start.58733315.js
    │   │   └── nodes
    │   │       ├── 0.ba05e72f.js
    │   │       ├── 1.f4999e32.js
    │   │       └── 2.ad52e74a.js
    │   └── version.json
    ├── favicon.png
    └── index.html

Upvotes: 3

Views: 951

Answers (1)

Brits
Brits

Reputation: 18370

Frustratingly your "Main.go V2" works with the addition of a single character. You are using:

r.Handle("/", http.FileServer(http.FS(static)))

From the docs:

func (mx *Mux) Handle(pattern string, handler http.Handler)

Each routing method accepts a URL pattern and chain of handlers. The URL pattern supports named params (ie. /users/{userID}) and wildcards (ie. /admin/). URL parameters can be fetched at runtime by calling chi.URLParam(r, "userID") for named parameters and chi.URLParam(r, "") for a wildcard parameter.

So you are passing in "/" as the "pattern"; this will match / but nothing else; to fix use:

r.Handle("/*", http.FileServer(http.FS(static)))
// or
r.Mount("/", http.FileServer(http.FS(static)))

I tested this with one of my svelte apps and it ran fine. One refinement you might want to consider is redirecting any requests for files that don't exist to / (otherwise if the user bookmarks a page with a path it will fail to load). See this answer for info.

Further to the above - to demonstrate what I'm saying in the comments add <a href="/about">About</a> to the end of ui/src/routes/+page.svelte and rebuild (both svelte, and then go apps). You will then be able to navigate to the about page (first loading the main page then click on "About"). This is handled by the client side router (so your you will probably not see any requests to the go server). See the linked answer for info on how to get this working when directly accessing a page (e.g. /about).

Here is a quick (and bit hacky) example that will serve the needed bits from the embedded file system and return the main index.html for all other requests (so the svelte router can display the needed page).

package main

import (
    "embed"
    "fmt"
    "io/fs"
    "log"
    "net/http"

    "github.com/go-chi/chi/v5"
)

//go:embed all:uibuild
var svelteStatic embed.FS

func main() {

    s, err := fs.Sub(svelteStatic, "uibuild")
    if err != nil {
        panic(err)
    }

    staticServer := http.FileServer(http.FS(s))

    r := chi.NewRouter()

    r.Handle("/", staticServer) // Not really needed (as the default will pick this up)
    r.Handle("/_app/*", staticServer)      // Need to serve any app components from the embedded files
    r.Handle("/favicon.png", staticServer) // Also serve favicon :-)

    r.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { // Everything else returns the index
        r.URL.Path = "/" // Replace the request path
        staticServer.ServeHTTP(w, r)
    })

    fmt.Println("Running on port: 8082")
    log.Fatal(http.ListenAndServe(":8082", r))
}

Upvotes: 2

Related Questions