feat: introduce support for plugins
This commit is contained in:
@@ -11,8 +11,10 @@ const (
|
|||||||
LogLevelDebug
|
LogLevelDebug
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Plugin func(v *V)
|
||||||
|
|
||||||
// Config defines configuration options for the via application
|
// Config defines configuration options for the via application
|
||||||
type Configuration struct {
|
type Options struct {
|
||||||
// Level of the logs to write to stdout.
|
// Level of the logs to write to stdout.
|
||||||
// Options: Error, Warn, Info, Debug.
|
// Options: Error, Warn, Info, Debug.
|
||||||
LogLvl LogLevel
|
LogLvl LogLevel
|
||||||
@@ -27,4 +29,8 @@ type Configuration struct {
|
|||||||
// Elements to include in the bottom of the body of the base
|
// Elements to include in the bottom of the body of the base
|
||||||
// HTML document.Useful for including JS scripts or a footer.
|
// HTML document.Useful for including JS scripts or a footer.
|
||||||
DocumentBodyIncludes []h.H
|
DocumentBodyIncludes []h.H
|
||||||
|
|
||||||
|
// Plugins to extend the capabilities of the `Via` application.
|
||||||
|
// Check `https://github.com/go-via/plugins` for a list of available plugins.
|
||||||
|
Plugins []Plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
// It binds user state and actions, manages reactive signals, and defines UI through View.
|
// It binds user state and actions, manages reactive signals, and defines UI through View.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
id string
|
id string
|
||||||
app *via
|
app *V
|
||||||
view func() h.H
|
view func() h.H
|
||||||
componentRegistry map[string]*Context
|
componentRegistry map[string]*Context
|
||||||
parentPageCtx *Context
|
parentPageCtx *Context
|
||||||
@@ -302,7 +302,7 @@ func (c *Context) ExecScript(s string) {
|
|||||||
_ = sse.ExecuteScript(s)
|
_ = sse.ExecuteScript(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContext(id string, a *via) *Context {
|
func newContext(id string, a *V) *Context {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
log.Fatalf("create context failed: app pointer is nil")
|
log.Fatalf("create context failed: app pointer is nil")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
v := via.New()
|
v := via.New()
|
||||||
|
|
||||||
v.Config(via.Configuration{
|
v.Config(via.Options{
|
||||||
DocumentTitle: "Via",
|
DocumentTitle: "Via",
|
||||||
DocumentHeadIncludes: []h.H{
|
DocumentHeadIncludes: []h.H{
|
||||||
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
||||||
|
|||||||
49
internal/examples/plugins/main.go
Normal file
49
internal/examples/plugins/main.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-via/via"
|
||||||
|
"github.com/go-via/via/h"
|
||||||
|
)
|
||||||
|
|
||||||
|
// In this example we create a Via application with a plugin that adds PicoCSS. The plugin
|
||||||
|
// is handed to Via in the app Configuration.
|
||||||
|
|
||||||
|
//go:embed pico.yellow.min.css
|
||||||
|
var picoCSSFile []byte
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := via.New()
|
||||||
|
v.Config(via.Options{
|
||||||
|
DocumentTitle: "Via With PicoCSS Plugin",
|
||||||
|
Plugins: []via.Plugin{PicoCSSPlugin},
|
||||||
|
})
|
||||||
|
|
||||||
|
v.Page("/", func(c *via.Context) {
|
||||||
|
c.View(func() h.H {
|
||||||
|
return h.Section(h.Class("container"),
|
||||||
|
|
||||||
|
h.H1(h.Text("Hello from ⚡Via")),
|
||||||
|
h.Div(h.Class("grid"),
|
||||||
|
h.Button(h.Text("Primary")),
|
||||||
|
h.Button(h.Class("secondary"), h.Text("Secondary")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
v.Start(":3000")
|
||||||
|
}
|
||||||
|
|
||||||
|
func PicoCSSPlugin(v *via.V) {
|
||||||
|
v.HandleFunc("GET /_plugins/picocss/assets/style.css", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/css")
|
||||||
|
_, _ = w.Write(picoCSSFile)
|
||||||
|
})
|
||||||
|
v.Config(via.Options{
|
||||||
|
DocumentHeadIncludes: []h.H{
|
||||||
|
h.Link(h.Rel("stylesheet"), h.Href("/_plugins/picocss/assets/style.css")),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
4
internal/examples/plugins/pico.yellow.min.css
vendored
Normal file
4
internal/examples/plugins/pico.yellow.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
v := via.New()
|
v := via.New()
|
||||||
|
|
||||||
v.Config(via.Configuration{
|
v.Config(via.Options{
|
||||||
DocumentTitle: "Via",
|
DocumentTitle: "Via",
|
||||||
DocumentHeadIncludes: []h.H{
|
DocumentHeadIncludes: []h.H{
|
||||||
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
h.Link(h.Rel("stylesheet"), h.Href("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css")),
|
||||||
|
|||||||
39
via.go
39
via.go
@@ -22,17 +22,17 @@ import (
|
|||||||
//go:embed datastar.js
|
//go:embed datastar.js
|
||||||
var datastarJS []byte
|
var datastarJS []byte
|
||||||
|
|
||||||
// via is the root application.
|
// V is the root application.
|
||||||
// It manages page routing, user sessions, and SSE connections for live updates.
|
// It manages page routing, user sessions, and SSE connections for live updates.
|
||||||
type via struct {
|
type V struct {
|
||||||
cfg Configuration
|
cfg Options
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
contextRegistry map[string]*Context
|
contextRegistry map[string]*Context
|
||||||
contextRegistryMutex sync.RWMutex
|
contextRegistryMutex sync.RWMutex
|
||||||
baseLayout func(h.HTML5Props) h.H
|
baseLayout func(h.HTML5Props) h.H
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *via) logErr(c *Context, format string, a ...any) {
|
func (v *V) logErr(c *Context, format string, a ...any) {
|
||||||
cRef := ""
|
cRef := ""
|
||||||
if c != nil && c.id != "" {
|
if c != nil && c.id != "" {
|
||||||
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
||||||
@@ -40,7 +40,7 @@ func (v *via) logErr(c *Context, format string, a ...any) {
|
|||||||
log.Printf("[error] %smsg=%q", cRef, fmt.Sprintf(format, a...))
|
log.Printf("[error] %smsg=%q", cRef, fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *via) logWarn(c *Context, format string, a ...any) {
|
func (v *V) logWarn(c *Context, format string, a ...any) {
|
||||||
cRef := ""
|
cRef := ""
|
||||||
if c != nil && c.id != "" {
|
if c != nil && c.id != "" {
|
||||||
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
||||||
@@ -50,7 +50,7 @@ func (v *via) logWarn(c *Context, format string, a ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *via) logInfo(c *Context, format string, a ...any) {
|
func (v *V) logInfo(c *Context, format string, a ...any) {
|
||||||
cRef := ""
|
cRef := ""
|
||||||
if c != nil && c.id != "" {
|
if c != nil && c.id != "" {
|
||||||
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
||||||
@@ -60,7 +60,7 @@ func (v *via) logInfo(c *Context, format string, a ...any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *via) logDebug(c *Context, format string, a ...any) {
|
func (v *V) logDebug(c *Context, format string, a ...any) {
|
||||||
cRef := ""
|
cRef := ""
|
||||||
if c != nil && c.id != "" {
|
if c != nil && c.id != "" {
|
||||||
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
cRef = fmt.Sprintf("via-ctx=%q ", c.id)
|
||||||
@@ -71,7 +71,7 @@ func (v *via) logDebug(c *Context, format string, a ...any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Config overrides the default configuration with the given configuration options.
|
// Config overrides the default configuration with the given configuration options.
|
||||||
func (v *via) Config(cfg Configuration) {
|
func (v *V) Config(cfg Options) {
|
||||||
if cfg.LogLvl != v.cfg.LogLvl {
|
if cfg.LogLvl != v.cfg.LogLvl {
|
||||||
v.cfg.LogLvl = cfg.LogLvl
|
v.cfg.LogLvl = cfg.LogLvl
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,13 @@ func (v *via) Config(cfg Configuration) {
|
|||||||
if cfg.DocumentBodyIncludes != nil {
|
if cfg.DocumentBodyIncludes != nil {
|
||||||
v.cfg.DocumentBodyIncludes = cfg.DocumentBodyIncludes
|
v.cfg.DocumentBodyIncludes = cfg.DocumentBodyIncludes
|
||||||
}
|
}
|
||||||
|
if cfg.Plugins != nil {
|
||||||
|
for _, plugin := range cfg.Plugins {
|
||||||
|
if plugin != nil {
|
||||||
|
plugin(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page registers a route and its associated page handler.
|
// Page registers a route and its associated page handler.
|
||||||
@@ -93,7 +100,7 @@ func (v *via) Config(cfg Configuration) {
|
|||||||
// return h.H1(h.Text("Hello, Via!"))
|
// return h.H1(h.Text("Hello, Via!"))
|
||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
func (v *via) Page(route string, composeContext func(c *Context)) {
|
func (v *V) Page(route string, composeContext func(c *Context)) {
|
||||||
v.mux.HandleFunc("GET "+route, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
v.mux.HandleFunc("GET "+route, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if strings.Contains(r.URL.Path, "favicon") {
|
if strings.Contains(r.URL.Path, "favicon") {
|
||||||
return
|
return
|
||||||
@@ -119,7 +126,7 @@ func (v *via) Page(route string, composeContext func(c *Context)) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *via) registerCtx(id string, c *Context) {
|
func (v *V) registerCtx(id string, c *Context) {
|
||||||
v.contextRegistryMutex.Lock()
|
v.contextRegistryMutex.Lock()
|
||||||
defer v.contextRegistryMutex.Unlock()
|
defer v.contextRegistryMutex.Unlock()
|
||||||
v.contextRegistry[id] = c
|
v.contextRegistry[id] = c
|
||||||
@@ -134,7 +141,7 @@ func (v *via) registerCtx(id string, c *Context) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (v *via) getCtx(id string) (*Context, error) {
|
func (v *V) getCtx(id string) (*Context, error) {
|
||||||
if c, ok := v.contextRegistry[id]; ok {
|
if c, ok := v.contextRegistry[id]; ok {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
@@ -143,23 +150,23 @@ func (v *via) getCtx(id string) (*Context, error) {
|
|||||||
|
|
||||||
// HandleFunc registers the HTTP handler function for a given pattern. The handler function panics if
|
// HandleFunc registers the HTTP handler function for a given pattern. The handler function panics if
|
||||||
// in conflict with another registered handler with the same pattern.
|
// in conflict with another registered handler with the same pattern.
|
||||||
func (v *via) HandleFunc(pattern string, f http.HandlerFunc) {
|
func (v *V) HandleFunc(pattern string, f http.HandlerFunc) {
|
||||||
v.mux.HandleFunc(pattern, f)
|
v.mux.HandleFunc(pattern, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the Via HTTP server on the given address.
|
// Start starts the Via HTTP server on the given address.
|
||||||
func (v *via) Start(addr string) {
|
func (v *V) Start(addr string) {
|
||||||
v.logInfo(nil, "via started")
|
v.logInfo(nil, "via started")
|
||||||
log.Fatalf("via failed: %v", http.ListenAndServe(addr, v.mux))
|
log.Fatalf("via failed: %v", http.ListenAndServe(addr, v.mux))
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Via application with default configuration.
|
// New creates a new Via application with default configuration.
|
||||||
func New() *via {
|
func New() *V {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
app := &via{
|
app := &V{
|
||||||
mux: mux,
|
mux: mux,
|
||||||
contextRegistry: make(map[string]*Context),
|
contextRegistry: make(map[string]*Context),
|
||||||
cfg: Configuration{
|
cfg: Options{
|
||||||
LogLvl: LogLevelDebug,
|
LogLvl: LogLevelDebug,
|
||||||
DocumentTitle: "Via Application",
|
DocumentTitle: "Via Application",
|
||||||
DocumentHeadIncludes: make([]h.H, 0),
|
DocumentHeadIncludes: make([]h.H, 0),
|
||||||
|
|||||||
Reference in New Issue
Block a user