GoFrame is an experimental Go-first framework and toolchain for interactive
browser/WASM apps. It combines a small Go runtime, JSX-like .gox markup, the
goxc generate/build/package workflow, and standard Go or TinyGo WebAssembly
output.
The current validated surface is browser/WASM applications. GoFrame is not
production-ready, does not claim stable 1.0 APIs, and does not ship
SSR/hydration, fullstack/server APIs, Player/Engine, .gfapp, formatter, or
LSP support.
Quick Start
Install goxc, add its install directory to PATH, check your local
toolchain, package the counter example, and serve it locally:
go install github.com/graybuton/goframe/cmd/goxc@latest
goxc_bin="$(go env GOBIN)"
if [ -z "$goxc_bin" ]; then
goxc_bin="$(go env GOPATH)/bin"
fi
export PATH="$goxc_bin:$PATH"
goxc doctor
goxc package ./examples/counter --compiler=tinygo
goxc serve ./examples/counter --port=8080
Open http://127.0.0.1:8080.
TinyGo gives the smallest browser bundles when available. Standard Go remains
useful for compatibility and local inspection:
goxc package ./examples/counter --compiler=go
goxc serve is a development server for packaged examples, not production
hosting infrastructure.
Tiny GOX Example
package main
func App(props AppProps) gf.Node {
count, setCount := gf.UseState(0)
return (
<main>
<h1>Counter</h1>
<p>Count: {count}</p>
<button onClick={func() { setCount(count + 1) }}>
Increment
</button>
</main>
)
}
goxc check validates authored .gox files without writing generated output.
goxc generate turns .gox files into Go compiler output under .goframe;
it does not write generated .gox.go files next to authored source by default.
What You Get
pkg/goframe: browser/WASM runtime primitives for nodes, components, hooks,
context, events, reconciliation, resources, routing, and fixed-height
virtualization.
pkg/gox: GOX lexer/parser/codegen with source-oriented diagnostics.
cmd/goxc: check, generate, build, package, export, serve, dev, size, clean,
doctor, and version commands.
- Examples and scripts that exercise the runtime, compiler, package workflow,
browser smoke paths, and WASM size budgets.
- A lightweight VS Code GOX extension in
extensions/vscode-gox.
Where To Go Next
- GoFrame Tutorial - guided router-dashboard walkthrough.
- Evaluator guide - short path for trying the
current browser/WASM preview.
- GOX language - syntax, diagnostics, and limits.
- Runtime model - component identity, hooks,
reconciliation, resources, and errors.
- Local development - watched development packaging,
loopback serving, failure recovery, and current non-goals.
- Deployment - package layout, asset hashing, preloads,
export, and cache-safe static delivery.
- API stability and
platform support - maturity and support
boundaries.
- Releases - published
previews and release notes.
- Release process - versioning, validation, and publishing
policy.
- Roadmap - status-qualified direction for the application
model, modular delivery, SSR/hydration research, tooling, and fullstack
contracts beyond the current shipped surface.
For the fastest tour, start with examples/counter, then
examples/components, then examples/router-dashboard.
Examples
Quickstart
| Example |
What it demonstrates |
Command |
examples/counter |
Minimal state, events, TinyGo quickstart, package/serve workflow. |
goxc package ./examples/counter --compiler=tinygo |
examples/components |
Typed props, children, fragments, component composition. |
goxc package ./examples/components --compiler=tinygo |
Application Primitives
| Example |
What it demonstrates |
Command |
examples/todo |
Controlled inputs, events, effects, keys, list helpers, localStorage. |
goxc package ./examples/todo --compiler=tinygo |
examples/context |
Scoped providers, selector consumers, broad UseContext, nested providers. |
goxc package ./examples/context --compiler=tinygo |
examples/virtualized |
gf.VirtualList, gf.VirtualTable, bounded DOM with 10,000 logical rows. |
goxc package ./examples/virtualized --compiler=tinygo |
examples/router |
Hash router, query helpers, route params, not-found route, and stable shell layout. |
goxc package ./examples/router --compiler=tinygo |
examples/resource |
gf.UseResource loading, ready, failed, stale completion, reload, and cleanup behavior. |
goxc package ./examples/resource --compiler=tinygo |
Reference And Pressure Examples
| Example |
What it demonstrates |
Command |
examples/dashboard |
Reducer dispatch, explicit memoization, virtual table, dashboard pressure smoke. |
goxc package ./examples/dashboard --compiler=tinygo |
examples/router-dashboard |
Reference app: hash router, query-state filters, component-scoped resource data, forms, validation, Error Boundary, and Go-first layout. |
goxc package ./examples/router-dashboard --compiler=tinygo |
examples/server-backed |
Browser/WASM app served by plain Go net/http with a same-origin API. |
goxc package ./examples/server-backed --compiler=go |
| Example |
What it demonstrates |
Command |
examples/multipackage |
entry: "." app with root plus internal/... packages. |
goxc package ./examples/multipackage --compiler=tinygo |
examples/cmdapp |
Child entry package with "entry": "./cmd/app" and Go-first layout. |
goxc package ./examples/cmdapp --compiler=tinygo |
Serve any packaged example with:
goxc serve ./examples/<name> --port=8080
Create cache-safe deployment-style artifacts with:
goxc package ./examples/<name> --compiler=tinygo --asset-hash --preload --compress=gzip,br
Use goxc export when you want a visible deploy directory:
goxc export ./examples/counter --out ./dist
Project Layers
| Layer |
Role |
| GOX language |
JSX-like declarative markup embedded in Go source. |
pkg/goframe runtime |
Nodes, component instances, hooks, context, events, reconciliation, resources, routing, and browser mounting. |
pkg/gox compiler |
GOX lexer/parser/codegen plus diagnostics and golden tests. |
cmd/goxc toolchain |
Generate, build, package, export, serve, size, clean, doctor, and version commands. |
| examples and scripts |
Integration probes, browser smoke, size budgets, and package/artifact gates. |
| VS Code extension |
Syntax highlighting, snippets, file icons, and commands over the same goxc workflow. |
Single-package apps can use entry: ".":
app/
├── goframe.json
├── app.gox
├── main.go
└── assets/
├── index.html
└── styles.css
Larger apps can use a Go-style child executable entry package:
app/
├── goframe.json
├── assets/
│ ├── index.html
│ └── styles.css
├── cmd/app/
│ ├── main.go
│ └── app.gox
└── internal/
└── ui/
└── layout.gox
{
"entry": "./cmd/app",
"compiler": "tinygo",
"assets": "./assets"
}
GOX discovery is app-root-wide, so imported internal packages get generated
files too. Cross-package component composition uses normal Go imports with
package-qualified tags such as <ui.Header />.
GOX In One Minute
Lowercase tags create DOM elements:
<button onClick={increment}>Increment</button>
Capitalized tags create runtime component boundaries using
<ComponentName>Props:
<Button Label="Increment" OnClick={increment} />
type ButtonProps struct {
Label string
OnClick func()
}
Package-qualified component tags use ordinary Go import aliases:
import ui "github.com/example/app/internal/ui"
func App() gf.Node {
return (
<ui.Shell Title="Dashboard">
<p>Hello</p>
</ui.Shell>
)
}
The supported form is exactly packageAlias.Component. It is not an XML
namespace system and does not support arbitrary selector chains.
Children use Children []gf.Node. Fragments use <>...</>.
Key={...} is a pseudo-prop for reconciliation and is not passed to component
props or emitted as a DOM attribute.
GOX supports expression-oriented rendering:
{ready && <ReadyView />}
{len(items) == 0 ? (
<EmptyState />
) : (
<ItemList Items={items} />
)}
{gf.Map(items, func(item Item) gf.Node {
return <ItemRow Key={item.ID} Item={item} />
})}
Unsupported syntax is intentionally rejected with source diagnostics, including
XML-style namespace tags, arbitrary selector chains, spread props,
template-block if/for, and arbitrary JavaScript-like expressions.
See GOX language.
Runtime Primitives
State is component-scoped and positional:
count, setCount := gf.UseState(0)
setCount(count + 1)
Reducer dispatch applies an action to the latest state slot at event time,
which avoids stale render captures in retained event handlers:
type CounterAction int
count, dispatch := gf.UseReducer(0, func(state int, action CounterAction) int {
return state + int(action)
})
dispatch(1)
Effects run after DOM patching:
gf.UseEffect(func() gf.Cleanup { return nil })
gf.UseEffect(func() gf.Cleanup { return nil }, gf.Deps(value))
gf.UseUnmount(func() {})
Context is scoped by the component tree:
var PreferencesContext = gf.CreateContext(Preferences{})
gf.ProvideContext(PreferencesContext, preferences)
density := gf.UseContextSelector(PreferencesContext, func(value Preferences) string {
return value.Density
})
Memoization is explicit and opt-in: implement MemoEqual on props when a
component can safely skip render/patch for equivalent props. Large fixed-height
collections should use gf.VirtualList or gf.VirtualTable instead of
mounting hidden offscreen rows.
The small client router is hash-based:
var router = gf.NewHashRouter([]gf.Route{
gf.RoutePath("/", homeRoute),
gf.RoutePath("/issues/:id", issueRoute),
gf.NotFoundRoute(notFoundRoute),
})
func App() gf.Node {
return gf.RouterView(router)
}
Forms use ordinary runtime primitives: controlled inputs, gf.InputEvent,
gf.Event.PreventDefault, and application-owned validation state. GoFrame does
not include a schema validation framework.
Render failures can be contained with an experimental scoped Error Boundary:
<gf.ErrorBoundary ResetKey={routeKey} Fallback={fallback}>
<pages.Content />
</gf.ErrorBoundary>
Boundaries catch descendant render failures, report through
gf.SetErrorHandler, and render fallback UI until reset. They do not catch
event, effect, cleanup, memo comparator, or context update failures.
Toolchain Workflow
Generated, build, and package outputs live under an app-local hidden workspace:
<app>/.goframe/
├── gen/
├── work/
├── build/
└── package/
Use GOFRAME_WORKSPACE=/work/goframe or --workspace /work/goframe when the
source tree is read-only.
Common commands:
| Command |
Responsibility |
goxc check <file-or-directory> |
Validate authored .gox source without writing generated files. |
goxc generate <app> |
Generate .gox.go compiler output under .goframe/gen. |
goxc build <app> |
Compile raw WASM under .goframe/build/<compiler>/dev. |
goxc package <app> |
Build a runnable .goframe/package/standalone bundle. |
goxc export <app> --out <dir> |
Copy the latest package to an explicit deploy directory. |
goxc size <app> |
Report size from .goframe/package/standalone. |
goxc serve <app> |
Serve the local package for development. |
goxc dev <app> |
Package, serve, and rebuild effective authored inputs for local development. |
goxc clean <app> |
Remove tool-owned workspace/build/package artifacts. |
goxc doctor |
Check local Go/TinyGo/compression/runtime-shim tools. |
goxc package normalizes output to:
<app>/.goframe/package/standalone/
├── index.html
├── asset-manifest.json
├── goframe-package.json
└── assets/
├── bundle.wasm
└── wasm_exec.js
Use "assets": "./assets" for app static files. If assets/index.html exists,
it is rewritten as the package root HTML entrypoint; otherwise goxc package
generates a default index.html.
Current Limitations
- Experimental browser/WASM target only.
- Hash router only; no SSR, hydration, path/history-mode server fallback,
file-based routing, route loaders, hot reload, CSS-in-Go, or production
deployment server.
- Player/Engine and
.gfapp are inactive directions outside the current
preview contract.
- No XML-style namespace tags, arbitrary selector chains, spread props,
template-block loops, formatter, or LSP.
- No full multi-module monorepo story.
- State/effect/context hooks are positional and require stable call order.
- Context selectors require comparable selected values.
- Memoization is explicit; there is no automatic deep equality or function-prop
equality.
- Virtualized collections are fixed-height only; no dynamic measurement,
infinite loading, or advanced keyboard/accessibility layer yet.
- Error Boundaries are render-only and recover-based. Default TinyGo
panic=trap builds compile the API but are not the proof path for boundary
containment.
- Resources are component-scoped and explicit-state only.
gf.FetchText is a
low-level experimental browser text loader; there is no global resource
cache, Suspense-style render blocking, route loader framework, JSON/data
framework, or higher-level fetch API.
- Duplicate key diagnostics are debug-only.
- TinyGo compatibility remains version- and feature-dependent.
Development Checks
Core local checks:
go fmt ./...
go test ./...
go test -race ./pkg/... ./cmd/...
go vet ./...
go test -tags=goframe_debug ./...
go test ./pkg/gox -run 'TestGolden|TestErrorGolden'
Full local gate:
scripts/check.sh
scripts/size-budget.sh
scripts/perf-report.sh
scripts/browser-smoke.sh
node --experimental-websocket scripts/dashboard-dom-pressure.mjs
scripts/artifact-check.sh
scripts/module-path-check.sh
CI runs core Go/GOX checks, docs consistency, TinyGo WASM size budgets, browser
smoke, dashboard DOM pressure, artifact/module gates, and VS Code extension
compile checks.
VS Code Support
The MVP .gox extension lives in extensions/vscode-gox. It provides syntax
highlighting, snippets, file icons, and command-palette actions that shell out
to goxc.
Local extension development:
cd extensions/vscode-gox
npm install
npm run compile
code .
Full Documentation Map
Start here:
Runtime topics:
Toolchain and delivery:
Examples:
License
goframe is licensed under the Apache License, Version 2.0.
See LICENSE and NOTICE.