⚙️ Concept of Golang HTML render engine with frontend components and dynamic behavior

SSC Engine

GitHub Go Report Card

An HTML render engine concept that brings frontend-like components experience to the server side with native html/template on steroids. Supports any serving basis (net/http/gin/etc), that provides io.Writer in response.

Disclaimer
This project is an experimental concept. Code quality may not meet your expectations. Don’t use in production. In case of any issues/proposals, feel free to open an issue

References

Main project landing: https://ssceng.codes
Documentation: https://ssceng.codes/docs/
Demo project: https://github.com/yuriizinets/ssceng/tree/master/demo

Support

Or directly with Bitcoin: 18WY5KRWVKVxjWKFzJLqqALsRrsDh4snqg

Owner
Comments
  • Prototype: Multi-stage component UI update on Action

    Prototype: Multi-stage component UI update on Action

    Explore possibility to use server-sent events to deliver multiple UI updates.
    Logic must to be similar to "flush" functionality of ORM frameworks, something like kyoto.SSAFlush(p)

    Required knowledge:

    • SSE API (https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
    • SSA lifecycle theory and code (https://kyoto.codes/extended-features.html#ssa-lifecycle and https://github.com/yuriizinets/kyoto/blob/master/ext.ssa.go)
    • SSA communication layer code (https://github.com/yuriizinets/kyoto/blob/master/payload/src/dynamics.ts)

    Issue might be pretty hard to implement for people unfamiliar with project, but very interesting for those who want to explore project codebase.

  • Prototype of functional way to define pages/components

    Prototype of functional way to define pages/components

    Current working prototype looks like this:

    ...
    
    func PageIndex(b *kyoto.Builder) {
    	b.Template(func() *template.Template {
    		return template.Must(template.New("page.index.html").ParseGlob("*.html"))
    	})
    	b.Init(func() {
    		b.State.Set("Title", "Kyoto in a functional way")
    		b.Component("UUID1", ComponentUUID)
    		b.Component("UUID2", ComponentUUID)
    	})
    }
    
    func ComponentUUID(b *kyoto.Builder) {
    	b.Async(func() error {
    		// Execute request
    		resp, err := http.Get("http://httpbin.org/uuid")
    		if err != nil {
    			return err
    		}
    		// Defer closing of response body
    		defer resp.Body.Close()
    		// Decode response
    		data := map[string]string{}
    		json.NewDecoder(resp.Body).Decode(&data)
    		// Set state
    		b.State.Set("UUID", data["uuid"])
    		// Return
    		return nil
    	})
    }
    
  • Component actions not being executed

    Component actions not being executed

    I have an action which I've defined on a component. The component code looks like this:

    package components
    
    import (
    	"log"
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    	"github.com/yuriizinets/go-ssc"
    
    	"github.com/kodah/blog/controller"
    	"github.com/kodah/blog/service"
    )
    
    type SignIn struct {
    	Context  *gin.Context
    	Username string
    	Password string
    	Errors   []string
    }
    
    func (c *SignIn) Actions() ssc.ActionsMap {
    	return ssc.ActionsMap{
    		"DoSignIn": func(args ...interface{}) {
    			var configService service.ConfigService = service.ConfigurationService("")
    			if configService.Error() != nil {
    				log.Printf("Error while connecting to DB service. error=%s", configService.Error())
    				c.Errors = append(c.Errors, "Internal server error")
    
    				return
    			}
    
    			log.Printf("Triggered sign in")
    
    			var dbService service.DBService = service.SQLiteDBService("")
    			if dbService.Error() != nil {
    				log.Printf("Error while connecting to DB service. error=%s", dbService.Error())
    				c.Errors = append(c.Errors, "Internal server error")
    
    				return
    			}
    
    			var loginService service.LoginService = service.DynamicLoginService(dbService)
    			var jwtService service.JWTService = service.JWTAuthService()
    			var loginController controller.LoginController = controller.LoginHandler(loginService, jwtService)
    
    			c.Context.Set("Username", c.Username)
    			c.Context.Set("Password", c.Password)
    
    			session := service.NewSessionService(c.Context, false)
    			token := loginController.Login(c.Context)
    
    			session.Set("token", token)
    
    			err := session.Save()
    			if err != nil {
    				log.Printf("Error while saving session. error=%s", err)
    			}
    
    			log.Printf("Login successful. user=%s", c.Username)
    
    			c.Context.Redirect(http.StatusFound, "/")
    		},
    	}
    }
    

    The template:

    {{ define "SignIn" }}
    <div {{ componentattrs . }}>
        <h1 class="title is-4">Sign in</h1>
        <p id="errorFeedback" class="help has-text-danger is-hidden">
            {{ .Username }} {{ .Password }}
        </p>
        <div class="field">
            <div class="control">
                <input class="input is-medium" value="{{ .Username }}" oninput="{{ bind `Username` }}" type="text" placeholder="username">
            </div>
        </div>
    
        <div class="field">
            <div class="control">
                <input class="input is-medium" value="{{ .Password }}" oninput="{{ bind `Password` }}" type="password" placeholder="password">
            </div>
        </div>
        <button onclick="{{ action `DoSignIn` `{}` }}" class="button is-block is-primary is-fullwidth is-medium">Submit</button>
        <br />
        <small><em>Be nice to the auth system.</em></small>
    </div>
    {{ end }}
    

    and is included like this:

                    <div class="column sign-in has-text-centered">
                        {{ template "SignIn" .SignInComponent }}
                    </div>
    

    Where the component inclusion looks like this:

    package frontend
    
    import (
    	"html/template"
    
    	"github.com/gin-gonic/gin"
    	"github.com/yuriizinets/go-ssc"
    
    	"github.com/kodah/blog/frontend/components"
    )
    
    type PageSignIn struct {
    	ctx             *gin.Context
    	SignInComponent ssc.Component
    }
    
    func (p *PageSignIn) Template() *template.Template {
    	return template.Must(template.New("page.signin.html").Funcs(ssc.Funcs()).ParseGlob("frontend/new_templates/*/*.html"))
    }
    
    func (p *PageSignIn) Init() {
    	p.SignInComponent = ssc.RegC(p, &components.SignIn{
    		Context: p.ctx,
    	})
    
    	return
    }
    
    func (*PageSignIn) Meta() ssc.Meta {
    	return ssc.Meta{
    		Title:       "Test kodah's blog",
    		Description: "Test description",
    		Canonical:   "",
    		Hreflangs:   nil,
    		Additional:  nil,
    	}
    }
    

    When I run the DoSignIn action it is not executed though;

        <button onclick="{{ action `DoSignIn` `{}` }}" class="button is-block is-primary is-fullwidth is-medium">Submit</button>
    

    I realize there's not a lot of documentation but I went off of the examples and this seems right.

  • Using ParseFS and embed.FS

    Using ParseFS and embed.FS

    I've been trying to figure out a pattern to use embed.FS and template.ParseFS for delivering the HTML files

    Do you have any examples for this? I've been struggling with a couple of different errors and including the Funcs with the parsed templates

    template: \"templates/pages.home.html\" is an incomplete or empty template

    	//go:embed templates
    	Templates embed.FS
    
    	return template.Must(
    		template.New("templates/pages.home.html").Funcs(kyoto.TFuncMap()).ParseFS(assets.Templates, "templates/pages.home.html"),
    	)
    
  • Up-to-date on Using the UIKit

    Up-to-date on Using the UIKit

    Hi guys,

    First of all, just wanted to take a moment and express my gratitude to your guys for making Kyoto a reality. It's awesome and really makes a lot sense for gophers to build out frontends on the fly. That said, I was wondering if you guys had any updated docs on using the UIKit. So far all the docs and demo projects I've come across were on an older version of the codebase and the apis have changed quite a bit since then. Would love some guidance here. Thanks in advance.

    Best, Jay

  • It is not possible to use `render.Custom` per component

    It is not possible to use `render.Custom` per component

    Each render.Custom call overrides global context. With this approach it wouldn't be possible to define custom rendering on a component level.
    As an option, we can store this in the state as internal, helpers.ComponentSerialize will remove it on state serialization.

  • Better Actions protocol implementation

    Better Actions protocol implementation

    Current approach is far from ideal. State and arguments are passed in SSE query, response must to be base64 encoded to avoid newlines issue (which increases the size of the response).

    Websockets cannot be considered:

    • They don't scale well (in case of keeping connection alive all the time)
    • Sometimes it requires special configuration of networking, especially on big projects
    • In some countries/hotels/public places you can see ports/protocols whitelisting, which immediately kills this approach
  • Support `tinygo` compiler

    Support `tinygo` compiler

    tinygo may provide a lot of benefits to the project, like compiling to smaller wasm payload and using in places where payload size is critical. I also consider as an option using kyoto for creating frontend, rendered on the Edge Network (f.e. Cloudflare Workers). Workers have a lot of limitations and tinygo may satisfy them.

  • Passing Go packages to Pages/Components

    Passing Go packages to Pages/Components

    Been playing around with Kyoto and really liking the pattern once I managed to get my head around it

    But now I'm starting to wonder what are the best practices for passing Go packages to Pages/Components seen as the handlers create a new instance of the page on each page load it's not possible to pass in a dependency inside the Page/Component structs

    One way I've managed to do this is passing it into the Context but I don't really want to fill up my context with lots of dependencies but I feel like there must be a nicer way of doing this that is more scalable?

  • Question - set cookie on response to SSA call

    Question - set cookie on response to SSA call

    How is it possible to access the request context from a call to a Server-Side Action. Say, for instance you want to set a cookie in the response to an SSA call.

    In the example demo app for the form submission example (email validator) you have the following:

    type ComponentDemoEmailValidator struct {
    	Email   string
    	Message string
    	Color   string
    }
    
    func (c *ComponentDemoEmailValidator) Actions() ssc.ActionMap {
    	return ssc.ActionMap{
    		"Submit": func(args ...interface{}) {
    			if emailregex.MatchString(c.Email) {
    				c.Message = "Provided email is valid"
    				c.Color = "green"
    			} else {
    				c.Message = "Provided email is not valid"
    				c.Color = "red"
    			}
    		},
    	}
    }
    

    I am aware you can create an "Init" method function to access the request context i.e.

    func (c *ComponentDemoEmailValidator) Init(p ssc.Page) {
        c.Page = p
        r := ssc.GetContext(p, "internal:r").(*http.Request)
        rw := ssc.GetContext(p, "internal:rw").(http.ResponseWriter)
    }
    

    But how do you access the request/response context from within the "Actions()" method so that you can, for example, set a cookie in the response. Is this possible?

  • Interaction with complex state across different adapters is uncomfortable

    Interaction with complex state across different adapters is uncomfortable

    In the struct mode it's much easier to initialize nested components and interact with them in next lifecycle steps. In case of func mode (which is default now) we need to interact with kyoto.Store instance, which is OK for simple state, but interaction with nested components is awful. You need to understand how Core.Component works and use explicit type casting.

    Needs to figure out, how to simplify work with components.

squirrelbyte is a "proof of concept" document / search server backed by sqlite.

??️ squirrelbyte is a "proof of concept" document / search server backed by sqlite.

May 20, 2022
GoDynamic can load and run Golang dynamic library compiled by -buildmode=shared -linkshared

GoDynamic can load and run Golang dynamic library compiled by -buildmode=shared -linkshared How does it work? GoDynamic works like a dynamic

Sep 30, 2022
Dynamic limit golang

uses channel and a timer goroutine to set a dynamic limit for a process (using timeout rather than hard limit)

Jan 30, 2022
Simple HTML Modification in Go

Simple HTML Modification in Go Do you grin at the sight of html.Node ? Me too. Modifying HTML in Go should be simple. ???? Human friendly: query langu

Sep 29, 2021
hdq - HTML DOM Query Language for Go+

hdq - HTML DOM Query Language for Go+ Summary about hdq hdq is a Go+ package for processing HTML documents. Tutorials Collect links of a html page How

Dec 13, 2022
Simple expression evaluation engine for Go

??️ chili Currently in development, Unstable (API may change in future) Simple expression evaluation engine. Expression is one liner that evalutes int

Nov 8, 2022
[mirror] star-tex (or *TeX) is a TeX engine in Go.

star-tex star-tex (or *TeX) is a TeX engine in Go. cmd/star-tex star-tex provides a TeX to DVI typesetter. $> star-tex ./testdata/hello.tex out.div $>

Dec 26, 2022
A pluggable linear task engine

noscript A pluggable linear task engine. Great for providing flexible configuration pattern to end users or providing scripting-like functionality to

Oct 23, 2021
JIN the coolest fighting game ever made that uses the M.U.G.E.N engine so heres how you can build it

JIN the coolest fighting game ever made that uses the M.U.G.E.N engine so heres how you can build it

Jan 24, 2022
Goridge is high performance PHP-to-Golang codec library which works over native PHP sockets and Golang net/rpc package.
Goridge is high performance PHP-to-Golang codec library which works over native PHP sockets and Golang net/rpc package.

Goridge is high performance PHP-to-Golang codec library which works over native PHP sockets and Golang net/rpc package. The library allows you to call Go service methods from PHP with a minimal footprint, structures and []byte support.

Dec 28, 2022
A Go (golang) library for parsing and verifying versions and version constraints.

go-version is a library for parsing versions and version constraints, and verifying versions against a set of constraints. go-version can sort a collection of versions properly, handles prerelease/beta versions, can increment versions, etc.

Jan 9, 2023
Golang: unify nil and empty slices and maps

unifynil, unify nil and empty slices and maps in Golang Empty slices and maps can be nil or not nil in Go. It may become a nightmare in tests and JSON

Jan 16, 2022
memresolver is an in-memory golang resolver that allows to override current golang Lookup func literals

mem-resolver memresolver is an in-memory golang resolver that allows to override current golang Lookup func literals How to use it Create your custom

Jun 23, 2022
Code Generation for Functional Programming, Concurrency and Generics in Golang

goderive goderive derives mundane golang functions that you do not want to maintain and keeps them up to date. It does this by parsing your go code fo

Dec 25, 2022
Copier for golang, copy value from struct to struct and more

Copier I am a copier, I copy everything from one to another Features Copy from field to field with same name Copy from method to field with same name

Jan 8, 2023
Extremely flexible golang deep comparison, extends the go testing package, tests HTTP APIs and provides tests suite
Extremely flexible golang deep comparison, extends the go testing package, tests HTTP APIs and provides tests suite

go-testdeep Extremely flexible golang deep comparison, extends the go testing package. Latest news Synopsis Description Installation Functions Availab

Jan 5, 2023
A well tested and comprehensive Golang statistics library package with no dependencies.

Stats - Golang Statistics Package A well tested and comprehensive Golang statistics library / package / module with no dependencies. If you have any s

Dec 30, 2022
Golang io.Reader and io.Writer but with limits

LimitIO io.Reader and io.Writer with limit.

Dec 14, 2022
Cogger is a standalone binary and a golang library that reads an internally tiled geotiff

Cogger is a standalone binary and a golang library that reads an internally tiled geotiff (optionally with overviews and masks) and rewrites it

Dec 12, 2022