golte

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 5, 2024 License: MIT Imports: 5 Imported by: 0

README

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

  • SSR + Hydration
  • NodeJS not required in production
  • Layouts as middleware (they can be nested)
  • Custom error pages
  • Works with Go stdlib, gorilla/mux, chi, gin, echo, or any router that allows access to the underlying http.ResponseWriter and *http.Request
  • Can compile to single, self-contained executable

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:

  • web/page/home.svelte => page/home
  • web/layout/main.svelte => layout/main
  • etc...

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="route1" use:preload>Route1</a>
<a href="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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddLayout

func AddLayout(r *http.Request, component string, props Props)

AddLayout appends the component to the request. Layouts consist of any components with a <slot>. Calling this multiple times on the same request will nest layouts.

func Error

func Error(component string) func(http.Handler) http.Handler

Error returns a middleware that calls SetError. Use this when there are no props needed to render the component. If you need to pass props, use SetError instead.

func Layout

func Layout(component string) func(http.Handler) http.Handler

Layout returns a middleware that calls AddLayout. Use this when there are no props needed to render the component. If you need to pass props, use AddLayout instead.

func New

func New(fsys fs.FS) func(http.Handler) http.Handler

New constructs a golte middleware from the given filesystem. The root of the filesystem should be the golte build directory.

The returned middleware is used to add a render context to incoming requests. It will allow you to use Layout, AddLayout, Page, and RenderPage. It should be mounted on the root of your router. The middleware should not be mounted on routes other than the root.

func Page

func Page(component string) http.HandlerFunc

Page returns a handler that calls RenderPage. Use this when there are no props needed to render the component. If you need to pass props, use RenderPage instead.

func RenderError

func RenderError(w http.ResponseWriter, r *http.Request, message string, status int)

RenderError renders the current error page along with layouts. The error componenet will receive "message" and "status" as props. It will also write the status code to the header.

func RenderPage

func RenderPage(w http.ResponseWriter, r *http.Request, component string, props Props)

RenderPage renders the specified component. If any layouts were added previously, then each subsequent layout will go in the <slot> of the previous layout. The page will be in the <slot> of the last layout.

func SetError

func SetError(r *http.Request, component string)

SetError sets the error page for the request. Errors consist of any components that take the "message" and "status" props. Calling this multiple times on the same request will overrite the previous error page.

Types

type Props

type Props = map[string]any

Props is an alias for map[string]any. It exists for documentation purposes. Props must be JSON-serializable when passing to fuctions defined in this package.

type RenderContext

type RenderContext struct {
	Renderer   *render.Renderer
	Components []render.Entry
	ErrPage    string
	// contains filtered or unexported fields
}

RenderContext is used for lower level control over rendering. It allows direct access to the renderer and component slice.

func GetRenderContext

func GetRenderContext(r *http.Request) *RenderContext

GetRenderContext returns the render context from the request, or nil if it doesn't exist.

func MustGetRenderContext

func MustGetRenderContext(r *http.Request) *RenderContext

MustGetRenderContext is like GetRenderContext, but panics instead of returning nil.

func (*RenderContext) Render

func (r *RenderContext) Render(w http.ResponseWriter)

Render renders all the components in the render context to the writer, with each subsequent component being a child of the previous.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL