nichady / golte

Render Svelte components in your Go http handlers.
https://pkg.go.dev/github.com/nichady/golte
MIT License
200 stars 7 forks source link
go golang svelte

Golte

golte is a library for Go that allows you to render Svelte components in your http handlers, supporting SSR+hydration capabilities without requiring NodeJS.

Features

How does it work?

  1. Svelte source files are first compiled to client and server JavaScript code (requires NodeJS during development only).

  2. The generated code is embedded using Go's embed package. A middleware is created from this which can then be registered in your router.

  3. From there, functions like golte.RenderPage and golte.AddLayout can be called to render components.

  4. After running go build, the end result is a single binary that houses a full web app, with complete support for SSR and hydration.

Installing

Prerequisites

Make sure Go and NodeJS are installed (NodeJS is only needed during development).

Setting up project

Initialize go.mod and package.json if not done so already:

go mod init <projectname>
npm init

Set {"type": "module"} in package.json.

Install Golte:

go get github.com/nichady/golte@latest
npm install golte@latest
# Make sure that the Go module and npm package are the same version.

Example

Suppose we have the following file structure:

web/
    page/
        home.svelte
        about.svelte
        contact.svelte
        login.svelte
        profile.svelte
    layout/
        main.svelte
    app.html
main.go
...(any other files)

We can run npx golte to generate a build/ directory. By default, Golte will use web/app.html as the template file and search for .svelte files inside the web/ directory (this can be changed in the config file).

Each .svelte file will be able to be rendered inside Go code by name. The name of the components are path of the file relative to the source directory (web/), without the file extension.

For example:

Suppose we want to make a program with the following requirements:

  1. Five routes: /, /about, /contact, /login, and /profile.
  2. /, /about, and /contact will have a layout; /login, and /profile will not.
  3. /profile will have props passed to it, which can be accessed with "export let".

This is what main.go would look like, using chi as the router:

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/nichady/golte"

    "example/build" // this package will be generated by golte
)

func main() {
    r := chi.NewRouter()

    // register the main Golte middleware
    r.Use(build.Golte)

    r.Group(func(r chi.Router) {
        r.Use(golte.Layout("layout/main"))

        // these routes will have a layout
        r.Get("/", golte.Page("page/home"))
        r.Get("/about", golte.Page("page/about"))
        r.Get("/contact", golte.Page("page/contact"))
    })

    r.Get("/login", golte.Page("page/login"))

    // this route will have props passed to it
    r.Get("/profile", func(w http.ResponseWriter, r *http.Request) {
        username := "john123"
        age := 22

        golte.RenderPage(w, r, "page/profile", map[string]any{
            "username": username,
            "age":      age,
        })
    })

    http.ListenAndServe(":8000", r)
}

For more comprehensive examples using other routers, passing props, and nesting layouts, see _examples.

For a list of all the possible functions, see the package docs.

Configuration

The build process of golte can be configured to change things like the source or build directory.

Golte will recognize one of the following files in the root directory of your project:

golte.config.js
golte.config.mjs
golte.config.ts
golte.config.mts

Here is an example for golte.config.ts:

import { Config } from "golte/config"

export default {
    template: "web/template.html",
    srcDir: "web/components/",
    outDir: "dist/",
} satisfies Config;

For all of the possible configuration options, see golte/config.
Note: You must set {"type": "module"} in your package.json for config file to work.

If you wish to use TypeScript or other preprocessors in your Svelte files, you can add a normal svelte.config.js file to your project root.

JavaScript Exports

The golte npm package provides some exports that you can use in your components:

golte

import { preload } from "golte";
<a href="https://github.com/nichady/golte/blob/master/route1" use:preload>Route1</a>
<a href="https://github.com/nichady/golte/blob/master/route2" use:preload={"mount"}>Route2</a>

preload - a Svelte action that can be used in <a> tags. Using this turns the <a> into a Golte link, stopping the page from reloading when clicked.

golte/stores

import { url } from "golte/stores";
<p>The current url is {$url.href}</p>

url - a Svelte store that describes the current URL of the page

Why?

I wanted a way to create web apps in Svelte while using Go as the backend language to create a single, self-contained binary. One option would be to embed a Svelte SPA into a Go binary. However, that solution lacks SSR capabilities and won't work when JavaScript is disabled.

I discovered Bud, but it forces you to structure your project in a specific way rather than treating your program as a normal Go app with a main() function and your own router. It also requires you to install their own cli. It also doesn't support layouts (I think).

There is also Inertia.js, but it requires NodeJS for SSR, and I don't like how layouts need to specified inside the page components.

So, I created Golte to solve these problems.