Golte: Svelte SSR and Hydration for Go

repository·master·Indexed 42 days ago

https://github.com/nichady/golte

A library for Go that enables rendering Svelte components with SSR and hydration capabilities, allowing developers to build full-stack web applications as single, self-contained Go binaries without requiring NodeJS in production. It includes a CLI for build management, middleware for layout and page rendering, and client-side utilities like the `preload` action and `goto()` navigation.

Tokens
5K
Snippets
20
Records
23
Agent score
54%

What's inside Golte

  1. How Golte works: SSR and Hydration

    master

    Golte enables Server-Side Rendering (SSR) and hydration for Svelte components within a Go application without requiring NodeJS in production.

    The Workflow:

    1. Compilation: During development, Svelte source files are compiled to client and server JavaScript (requires NodeJS).
    2. Embedding: The generated code is embedded into your Go binary using Go's embed package.
    3. Middleware: A middleware is generated from the build, which you register with your Go router.
    4. Rendering: You use functions like golte.RenderPage and golte.AddLayout to serve components.
    5. Deployment: After go build, you get a single, self-contained binary containing the full web app.
  2. Install Golte for Go and npm

    master

    To use Golte, you must install both the Go module and the npm package. Ensure they are the same version to maintain compatibility.

    Prerequisites:

    • Go installed.
    • NodeJS installed (required for development/compilation only).

    Setup Steps:

    1. Initialize your project:
      go mod init <projectname>
      npm init
    2. Set "type": "module" in your package.json.
    3. Install the packages:
      go get github.com/nichady/golte@latest
      npm install golte@latest
    go get github.com/nichady/golte@latest
    npm install golte@latest
  3. Run Golte examples

    master

    The examples directory contains sample applications to demonstrate Golte's capabilities. You can explore different implementations such as routers (demonstrating different router configurations) and blog (a simple blog application).

    To run an example, first install the dependencies, then use the npm run command followed by the specific example name.

    npm install
    npm run routers
    npm run blog
  4. Render Svelte components in Go handlers

    master

    Once the build is generated (using npx golte), Svelte components are accessible in Go by their path relative to the source directory, excluding the .svelte extension.

    Example Mapping:

    • web/page/home.svelte $\rightarrow$ page/home
    • web/layout/main.svelte $\rightarrow$ layout/main

    Usage with a Router (e.g., chi):

    1. Register the generated build.Golte middleware.
    2. Use golte.Layout(path) to wrap routes in a layout.
    3. Use golte.Page(path) for simple route handlers.
    4. Use golte.RenderPage(w, r, path, props) to pass data to a component via a map.
    package main
    
    import (
    	"net/http"
    	"github.com/go-chi/chi/v5"
    	"github.com/nichady/golte"
    
    	"example/build" // generated by golte
    )
    
    func main() {
    	r := chi.NewRouter()
    
    	// 1. Register middleware
    	r.Use(build.Golte)
    
    	r.Group(func(r chi.Router) {
    		// 2. Apply a layout
    		r.Use(golte.Layout("layout/main"))
    
    		// 3. Render a page
    		r.Get("/", golte.Page("page/home"))
    		r.Get("/about", golte.Page("page/about"))
    	})
    
    	// 4. Render a page with props
    	r.Get("/profile", func(w http.ResponseWriter, r *http.Request) {
    		props := map[string]any{
    			"username": "john123",
    			"age":      22,
    		}
    		golte.RenderPage(w, r, "page/profile", props)
    	})
    
    	http.ListenAndServe(":8000", r)
    }
  5. Configure the Golte build process

    master

    You can customize the build process (e.g., changing source or build directories) by creating a configuration file in your project root. Golte supports .js, .mjs, .ts, or .mts extensions.

    Supported Config Files:

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

    Note: You must have "type": "module" set in your package.json for the configuration file to work. If using TypeScript, you can also include a standard svelte.config.js for Svelte preprocessors.

    import { Config } from "golte/config"
    
    export default {
    	template: "web/template.html",
    	srcDir: "web/components/",
    	outDir: "dist/",
    } satisfies Config;
  6. Use the `preload` action for client-side navigation

    master

    The preload action from the golte npm package can be applied to <a> tags in Svelte components. This converts standard links into Golte links, enabling client-side navigation and preventing full page reloads.

    <script>
      import { preload } from "golte";
    </script>
    
    <a href="/route1" use:preload>Route1</a>
    <a href="/route2" use:preload={|"mount"|}>Route2</a>
  7. Access the current URL with the `url` store

    master

    The url store provided by golte/stores allows Svelte components to reactively access information about the current URL.

    <script>
      import { url } from "golte/stores";
    </script>
    
    <p>The current url is {$url.href}</p>
  8. Configure Golte build via golte.config

    master

    The build process is controlled by a configuration object. The CLI merges your provided configuration with a set of defaults.

    Default Configuration Values

    • template: "web/app.html" (The HTML template for the client)
    • srcDir: "web/" (Directory containing Svelte components)
    • outDir: "build/" (Output directory for build artifacts)
    • package: true (If true, generates embed.go in outDir using the name of the outDir)
    • assets: "golte_" (Prefix/base path for assets)
    • mode: "prod" (Default build mode)
    • vite: An object containing Vite configuration, which is merged with internal build settings (e.g., cssCodeSplit: true and the Svelte plugin with hydratable: true).

    Package Option

    The package option determines how the Go embedding file is named:

    • If true: The package name is the basename of outDir.
    • If a string: The package name is set to that specific string.
    • If false: No embed.go file is generated.
  9. Render components using RenderContext.Render

    master

    The Render method on *RenderContext executes the actual rendering process. It takes all components stored in the context's Components slice and renders them to the provided http.ResponseWriter. Each subsequent component in the slice is treated as a child of the previous one.

    If the rendering process encounters an error, Render will respond to the client with an http.StatusInternalServerError and the error message.

    func (r *RenderContext) Render(w http.ResponseWriter)
  10. Render a page with Page()

    master

    The Page(component string) function returns an http.HandlerFunc that renders a specific component. This is a convenience wrapper for RenderPage when no custom props are required.

    Use this for simple routes where the component does not need dynamic data passed via props.

    mux.HandleFunc("/about", golte.Page("AboutPage"))
  11. Manage layouts with AddLayout() and Layout()

    master

    Layouts are components that contain a <slot>. You can wrap pages in layouts using two methods:

    1. Middleware approach: Use Layout(component string) as middleware to apply a layout to all routes handled by the subsequent handlers. This is ideal when no props are needed for the layout.
    2. Manual approach: Use AddLayout(r, component, props) within a handler to append a layout to the current request. This allows you to pass specific Props to the layout.

    Calling AddLayout multiple times on the same request will result in nested layouts.

    // Using middleware
    mux.Handle("/dashboard", golte.Layout("DashboardLayout")(dashboardHandler))
    
    // Using manual addition in a handler
    func dashboardHandler(w http.ResponseWriter, r *http.Request) {
    	golte.AddLayout(r, "SidebarLayout", golte.Props{"theme": "dark"})
    	golte.RenderPage(w, r, "DashboardPage", nil)
    }
  12. Handle errors with SetError() and RenderError()

    master

    Golte provides mechanisms to define and render error pages.

    1. Define the error component: Use SetError(r, component) or the Error(component) middleware to specify which component should be used to display errors. Error components must accept message and status as props.
    2. Trigger error rendering: Use RenderError(w, r, message, status) to render the configured error page. This function will also write the provided status code to the HTTP header.

    RenderError will render the error component within the current request's layout stack.

    // 1. Set the error component via middleware
    mux.Handle("/", golte.Error("MyErrorComponent")(rootHandler))
    
    // 2. Trigger the error in a handler
    func handler(w http.ResponseWriter, r *http.Request) {
    	if err := doSomething(); err != nil {
    		golte.RenderError(w, r, "Something went wrong", http.StatusInternalServerError)
    		return
    	}
    }