Git Product home page Git Product logo

kyoto's Introduction

kyoto

Go server side frontend framework

import "go.kyoto.codes/v3"

Kyoto is a library for creating fast, server side frontend avoiding vanilla templating downsides.

It tries to address complexities in frontend domain like responsibility separation, components structure, asynchronous load and hassle-free dynamic layout updates. These issues are common for frontends written with Go.

The library provides you with primitives for pages and components creation, state and rendering management, dynamic layout updates (with client configuration), utility functions and asynchronous components out of the box. Still, it bundles with minimal dependencies and tries to utilize built-ins as much as possible.

You would probably want to opt out from this library in few cases, like, if you're not ready for drastic API changes between major version, you want to develop SPA/PWA and/or complex client-side logic, or you're just feeling OK with your current setup. Please, don't compare kyoto with a popular JS libraries like React, Vue or Svelte. I know you will have such a desire, but most likely you will be wrong. Use cases and underlying principles are just too different.

If you want to get an idea of what a typical static component would look like, here's some sample code. It's very ascetic and simplistic, as we don't want to overload you with implementation details. Markup is also not included here (it's just a well-known `html/template`).

// State is declared separately from component itself
type ComponentState struct {
	// We're providing component with some abilities here
	component.Universal // This component uses universal state, that can be (un)marshalled with both server and client

	// Actual component state is just struct fields
	Foo string
	Bar string
}

// Component is a function, that returns configured state.
// To be able to provide additional arguments to the component on initialization,
// you have to wrap component with additional function that will handle args and return actual component.
// Until then, you may keep component declaration as-is.
func Component(ctx *component.Context) component.State {
	// Initialize state here.
	// As far as component.Universal provided in declaration,
	// we're implementing component.State interface.
	state := &ComponentState{}
	// Do whatever you want with a state
	state.Foo = "foo"
	state.Bar = "bar"
	// Done
	return state
}

For details, please check project's website on https://kyoto.codes. Also, you may check the library index to explore available sub-packages and https://pkg.go.dev for Go'ish documentation style.

kyoto's People

Contributors

bketelsen avatar cejder avatar dhax avatar failingprovince avatar john-g-g avatar opensauce avatar preslavrachev avatar rowdyhcs avatar smoorpal avatar stigkj avatar yznts avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kyoto's Issues

Insights tooling

As a replacement for current BENCH flags.
Insights must to store and provide detailed rendering timings.

Improve JS payload setup

Need a separate payload folder with TS+esbuild setup. We need to build each file separately. JS builds can be included into Go project manually, just for now.

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.

Page SSA

Make SSA, same as in the components

Server Side State

Will be useful in case of large state payloads.
Instead of saving state inline as html tag, store state on server side and inject state hash as html tag.
This feature will decrease amount of data, sent with SSA request and total HTML document size.

Key requirements:

  • Implement at least in-memory storage
  • Support for multiple state storages (with interface)
  • Component-level storage configuration (inline or server-side)

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?

SSA gives an error on cold run

Catching an error while calling SSA on cold run. SSA components store is populated while initial page rendering, which is never triggered on cold run.
Need some optional explicit way to register components on app start.

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?

Multi-layer async handling

In case when additional component are registered on Async stage, call async for newly added component

Add loading attribute

Because Kyoto makes a roundtrip to the server every time an action is triggered on the page, there are cases when the page may not react immediately to a user event (like a click). We need to provide a way to easily display loading states.

Eliminate obligatory using of `html/template` and give control to user

Need to provide something like ImplementsRender interface. This interface will give rendering control to page/component.
Steps to implement:

  • Define new interface for implementing render
  • Remove obligatory Template method in page interface
  • Define new interface for optional Template method
  • Integrate manual rendering control into lifecycle (check for at least one rendering option must to be implemented)

Pros:

  • Giving developer a control over final rendering step
  • Eliminating obligatory html/template usage, developer can choose alternative ways like maragudk/gomponents
  • Keeping components templates/rendering inside of *.go files will allow to distribute component libraries with Go packages

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:

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

Make functions from TFuncMap available outside of FuncMap

It's possible that it will be needed while implementing Render interface (#42).
We can extract functions to be available with preffix "T", like kyoto.TMeta, kyoto.TDynamics, kyoto.TComponentAttrs, etc.
TFuncMap can be refactored just to map to existing public template functions.

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"),
	)

Need a good demo project

Hey guys,

looks pretty cool. Is there a demo of an app running on it anywhere? Plus, what's SSC?

Best

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.