goscript client
The <goscript client> block contains Go logic compiled into WebAssembly via TinyGo. It executes within the browser, without any server requests.
β οΈ Note on Compilation Performance
Unlike server-side code, which is compiled almost instantaneously, WebAssembly compilation by TinyGo can take between 1 and 5 seconds depending on the complexity of your scripts and imports.
In development mode (stew run dev), it is normal to experience a slight delay between saving your file and the changes taking effect in the browser. If your modifications don't seem to be reflected immediately, wait a few seconds for the Wasm binary to be ready.
Basic Syntax (Signals)
<goscript client>
import "stew/state" // Import the Signals package
// Reactive variables (Signals)
count := state.New(0)
// Computed / Effect: Automatically recalculated (Auto-Tracking)
state.Effect(func() {
console.Log("The counter is at:", count.Get())
})
</goscript>
<div bind:content={{ count.Get() }}>0</div>
<button on:click={{ count.Set(count.Get() + 1) }}>+</button>
<button on:click={{ count.Set(count.Get() - 1) }}>-</button>
stew/state, the DOM only refreshes when a specific Signal is updated, greatly optimizing CPU performance.
What the Compiler Generates
From the page above, Stew generates a temporary file /tmp/stew_wasm/stew_main_wasm_pages_page.go:
package main
import (
"github.com/ZiplEix/stew/sdk/wasm"
"github.com/ZiplEix/stew/sdk/wasm/state"
)
func main() {
// Code from the <goscript client> block
count := state.New(0)
state.Effect(func() {
console.Log("The counter is at:", count.Get())
})
// Automatically generated bindings
wasm.BindBlock("stew-bind-page-0", func() string { return fmt.Sprint(count.Get()) })
wasm.OnEvent("stew-bind-page-1", "click", func() { count.Set(count.Get() + 1) })
wasm.StartReactivityLoop() // Keeps the Wasm main thread open
_ = count
}
This file is then compiled with:
tinygo build -target wasm -no-debug -o ./static/wasm/pages_page.wasm ...
Wasm Binary Naming
The .wasm file is named after the relative path of the source .stew file to avoid collisions:
| Source File | Generated Wasm Binary |
|---|---|
| pages/@page.stew | static/wasm/pages_page.wasm |
| pages/blog/@page.stew | static/wasm/pages_blog_page.wasm |
| pages/users/__id__/@page.stew | static/wasm/pages_users___id___page.wasm |
Retrieving Server Data (DX)
To avoid JSON unmarshaling boilerplate, Stew offers a virtual import stew/data which automatically exposes a global data variable.
<goscript client>
import "stew/data"
console.Log("Page URL:", data.URL)
userId := data.Params["id"]
</goscript>
Performance and Reactivity
Push-Based Architecture
Unlike some frameworks that check 60 times per second if your variables have changed (Dirty Checking / Polling), Stew's Signal system (stew/state) is completely inert and passive.
- The DOM is only repainted when a
.Set()is called. - Absolutely 0% CPU is wasted at rest.
- Partial diff calls (via Stew's internal Idiomorph integration) are granular and limited to the blocks impacted by a specific Signal.
Isolation and Scopes
Stew hermetically separates server-side execution (SSR) from client-side execution (Wasm). Here are the golden rules:
π₯οΈ goscript server (SSR)
- Binary: Native (Linux/macOS/Windows)
- Capabilities: File access, DB, full network access
- Lifecycle: Executed once per request
- Scope: Not accessible by
on:/bind:bindings
π goscript client (Wasm)
- Binary: WebAssembly (W3C Standard)
- Capabilities: Sandboxed, DOM, Navigation/IO SDKs
- Lifecycle: Infinite reactivity loop
- Scope: The exclusive scope for bindings
Why are my server variables "undefined"?
If you declare user := ... in a server block and try to use on:click={{ user.Save() }}, the Wasm compilation will fail. The bind: and on: bindings are compiled directly into the Wasm binary. They can only see what is imported or declared within the <goscript client> block.
β Recommendation: Communication
To pass data from the server to a client-side binding:
1. Store it in data.Store on the server side.
2. Retrieve it via import "stew/data" on the client side.
3. Bind the resulting client variable to your HTML.