feat: replace Handler() method for HTTPServeMux() for better plugin and testing integration; remove via-plugin-picocss dependency from examples; add datastar h.H nodes for data-init, data-effect, and data-ignore-morph; update realtimechart example; other small improvements

This commit is contained in:
Joao Goncalves
2025-12-17 17:11:59 -01:00
parent 6da518d990
commit 20dad802a1
11 changed files with 162 additions and 179 deletions

3
go.mod
View File

@@ -6,8 +6,6 @@ require maragu.dev/gomponents v1.2.0
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/fsnotify/fsnotify v1.9.0
github.com/go-via/via-plugin-picocss v0.1.0
github.com/mattn/go-sqlite3 v1.14.32
github.com/starfederation/datastar-go v1.0.3
github.com/stretchr/testify v1.10.0
@@ -21,7 +19,6 @@ require (
github.com/kr/pretty v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/sys v0.38.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

6
go.sum
View File

@@ -8,10 +8,6 @@ github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUS
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-via/via-plugin-picocss v0.1.0 h1:ytVtBlfYBhidos5ub4a8liYqadz1AkeHhh7e7Paz620=
github.com/go-via/via-plugin-picocss v0.1.0/go.mod h1:5LEnLE7q8YfYY7jtH/TLPvfquB7Qt9WZ7TbKrskUW+0=
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k=
github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
@@ -47,8 +43,6 @@ github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g
github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -1,9 +1,13 @@
package h
import "fmt"
type OnClickOpts string
func OnClick(actionid string, opt ...OnClickOpts) H {
return Data("on:click", fmt.Sprintf("@get('/_action/%s')", actionid))
func DataInit(expression string) H {
return Data("init", expression)
}
func DataEffect(expression string) H {
return Data("effect", expression)
}
func DataIgnoreMorph() H {
return Attr("data-ignore-morph")
}

View File

@@ -1,10 +1,10 @@
## ADR
# ADR
- Support Multiple Rooms
Not single chat room toy problem.
Not single chat room toy problem.
- Rooms are generic
They know nothing of their data. Just store it. Reusable for different usecases.
They know nothing of their data. Just store it. Reusable for different usecases.
- Server controls push frequency
Debounce to every 400ms, if dirty.
Debounce to every 400ms, if dirty.

View File

@@ -2,7 +2,7 @@ package main
import (
"github.com/go-via/via"
"github.com/go-via/via-plugin-picocss/picocss"
// "github.com/go-via/via-plugin-picocss/picocss"
"github.com/go-via/via/h"
)
@@ -15,7 +15,9 @@ func main() {
DocumentTitle: "Live Reload Demo",
DevMode: true,
LogLvl: via.LogLevelDebug,
Plugins: []via.Plugin{picocss.Default},
Plugins: []via.Plugin{
// picocss.Default
},
})
v.Page("/", func(c *via.Context) {

View File

@@ -4,7 +4,7 @@ import (
"strconv"
"github.com/go-via/via"
"github.com/go-via/via-plugin-picocss/picocss"
// "github.com/go-via/via-plugin-picocss/picocss"
. "github.com/go-via/via/h"
)
@@ -12,7 +12,7 @@ func main() {
v := via.New()
v.Config(via.Options{
Plugins: []via.Plugin{picocss.Default},
// Plugins: []via.Plugin{picocss.Default},
})
v.Page("/counters/{counter_id}/{start_at_step}", func(c *via.Context) {

Binary file not shown.

View File

@@ -2,7 +2,7 @@ package main
import (
"github.com/go-via/via"
"github.com/go-via/via-plugin-picocss/picocss"
// "github.com/go-via/via-plugin-picocss/picocss"
"github.com/go-via/via/h"
)
@@ -15,9 +15,9 @@ func main() {
DocumentTitle: "Via Counter",
// Plugin is placed here. Use picocss.WithOptions(pococss.Options) to add the plugin
// with a different color theme or to enable a classes for a wide range of colors.
Plugins: []via.Plugin{
picocss.Default,
},
// Plugins: []via.Plugin{
// picocss.Default,
// },
})
v.Page("/", func(c *via.Context) {

View File

@@ -37,7 +37,7 @@ func main() {
}
func PicoCSSPlugin(v *via.V) {
v.HandleFunc("GET /_plugins/picocss/assets/style.css", func(w http.ResponseWriter, r *http.Request) {
v.HTTPServeMux().HandleFunc("GET /_plugins/picocss/assets/style.css", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/css")
_, _ = w.Write(picoCSSFile)
})

View File

@@ -6,7 +6,7 @@ import (
"time"
"github.com/go-via/via"
"github.com/go-via/via-plugin-picocss/picocss"
// "github.com/go-via/via-plugin-picocss/picocss"
"github.com/go-via/via/h"
)
@@ -17,7 +17,7 @@ func main() {
LogLvl: via.LogLevelDebug,
DevMode: true,
Plugins: []via.Plugin{
picocss.Default,
// picocss.Default,
},
})
@@ -26,29 +26,7 @@ func main() {
)
v.Page("/", func(c *via.Context) {
chartComp := c.Component(chartCompFn)
c.View(func() h.H {
return h.Div(h.Style("overflow-x:hidden"),
h.Section(h.Class("container"),
h.Nav(
h.Ul(h.Li(h.H3(h.Text("⚡Via")))),
h.Ul(
h.Li(h.A(h.H5(h.Text("About")), h.Href("https://github.com/go-via/via"))),
h.Li(h.A(h.H5(h.Text("Resources")), h.Href("https://github.com/orgs/go-via/repositories"))),
h.Li(h.A(h.H5(h.Text("Say hi!")), h.Href("http://github.com/go-via/via/discussions"))),
),
),
),
chartComp(),
)
})
})
v.Start()
}
func chartCompFn(c *via.Context) {
isLive := true
isLiveSig := c.Signal("on")
@@ -66,11 +44,7 @@ func chartCompFn(c *via.Context) {
c.ExecScript(fmt.Sprintf(`
if (myChart) {
myChart.appendData({seriesIndex: 0, data: [[%d, %f]]});
myChart.setOption({
},{
notMerge:false,
lazyUpdate:true
});
myChart.setOption({},{notMerge:false,lazyUpdate:true});
};
`, ts, val))
})
@@ -88,9 +62,20 @@ func chartCompFn(c *via.Context) {
updateData.Stop()
}
})
c.View(func() h.H {
return h.Div(h.Div(h.ID("chart"), h.Style("width:100%;height:400px;"),
return h.Div(h.Style("overflow-x:hidden"),
h.Section(h.Class("container"),
h.Nav(
h.Ul(h.Li(h.H3(h.Text("⚡Via")))),
h.Ul(
h.Li(h.A(h.H5(h.Text("About")), h.Href("https://github.com/go-via/via"))),
h.Li(h.A(h.H5(h.Text("Resources")), h.Href("https://github.com/orgs/go-via/repositories"))),
h.Li(h.A(h.H5(h.Text("Say hi!")), h.Href("http://github.com/go-via/via/discussions"))),
),
),
),
h.Div(
h.Div(h.ID("chart"), h.DataIgnoreMorph(), h.Style("width:100%;height:400px;"),
h.Script(h.Raw(`
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
var myChart = echarts.init(document.getElementById('chart'), prefersDark.matches ? 'dark' : 'light');
@@ -162,7 +147,8 @@ func chartCompFn(c *via.Context) {
]
};
option && myChart.setOption(option);
`))),
`)),
),
h.Section(
h.Article(
h.H5(h.Text("Controls")),
@@ -178,6 +164,10 @@ func chartCompFn(c *via.Context) {
),
),
),
),
)
})
})
v.Start()
}

22
via.go
View File

@@ -1,5 +1,6 @@
// Package via provides a reactive web framework for Go.
// It lets you build live, type-safe web interfaces without JavaScript.
// Package via provides a reactive, real-time engine for creating Go web
// applications. It lets you build live, type-safe web interfaces without
// JavaScript.
//
// Via unifies routing, state, and UI reactivity through a simple mental model:
// Go on the server — HTML in the browser — updated in real time via Datastar.
@@ -231,21 +232,18 @@ func (v *V) getCtx(id string) (*Context, error) {
return nil, fmt.Errorf("ctx '%s' not found", id)
}
// 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.
func (v *V) HandleFunc(pattern string, f http.HandlerFunc) {
v.mux.HandleFunc(pattern, f)
}
// Start starts the Via HTTP server on the given address.
func (v *V) Start() {
v.logInfo(nil, "via started at [%s]", v.cfg.ServerAddress)
log.Fatalf("[fatal] %v", http.ListenAndServe(v.cfg.ServerAddress, v.mux))
}
// Handler returns the underlying http.Handler for use with custom servers or testing.
// This enables integration with test frameworks like gost-dom/browser for SSE/Datastar testing.
func (v *V) Handler() http.Handler {
// HTTPServeMux returns the underlying HTTP request multiplexer to enable user extentions, middleware and
// plugins. It also enables integration with test frameworks like gost-dom/browser for SSE/Datastar testing.
//
// IMPORTANT. The returned *http.ServeMux can only be modified during initialization, before calling via.Start().
// Concurrent handler registration is not safe.
func (v *V) HTTPServeMux() *http.ServeMux {
return v.mux
}
@@ -423,14 +421,12 @@ func New() *V {
if sse.Context().Err() == nil {
v.logErr(c, "PatchElements failed: %v", err)
}
continue
}
case patchTypeSignals:
if err := sse.PatchSignals([]byte(patch.content)); err != nil {
if sse.Context().Err() == nil {
v.logErr(c, "PatchSignals failed: %v", err)
}
continue
}
case patchTypeScript:
if err := sse.ExecuteScript(patch.content, datastar.WithExecuteScriptAutoRemove(true)); err != nil {