GoFrame is an experimental Go-first web application framework and toolchain for interactive browser/WASM apps. It combines:
- the
goframeruntime library; - JSX-like
.goxmarkup embedded in Go files; - the
goxcgenerate/build/package/serve toolchain; - TinyGo and standard Go WebAssembly targets.
GoFrame is not production-ready. Some APIs are public-candidate, but the GOX
syntax, manifests, packaging details, and runtime internals may still change
between MVPs. The browser/WASM target is the current validated implementation;
Player/Engine and .gfapp are inactive directions, not shipped products or
preview promises.
The first public preview scope is intentionally narrower than the project direction: it should validate the current browser/WASM application layer without removing or hiding working experimental surfaces such as the router, resources, and Error Boundaries. Staged Go backend integration is a direction that requires evidence before it becomes a preview feature claim.
Current baseline includes:
- GOX expression ergonomics and source-oriented diagnostics;
- component boundaries, state, reducer dispatch, effects, context selectors, memoization, keyed reconciliation, fixed-height virtualization, component-scoped resources, and a small hash-based client router with tiny query helpers;
- runtime error reporting plus scoped render-only Error Boundaries for recover-capable builds;
- cache-safe packaging, hidden
.goframeworkspace output, export safety, and clean workspace commands; - multi-package GOX workspaces, child entry packages such as
./cmd/app, and import-aware generated component identity; - documented forms/validation patterns and a resource-backed router-dashboard reference app;
- CI gates for Go/GOX tests, TinyGo size budgets, browser smoke, artifact checks, module path checks, and docs consistency.
Non-goals for the current project surface include SSR, hydration, fullstack
server APIs, Player/Engine, .gfapp, path/history-mode routing, file-based
routing, route loaders, Suspense-style resources, global resource caching,
dynamic virtualization, infinite loading, LSP, formatter, XML-style namespace
tags, spread props, schema validation framework, production deployment server,
and full multi-module monorepo support.
GoFrame lets you write interactive UI in Go while keeping authored source clean:
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 generate turns .gox into Go compiler output under .goframe; it does
not write generated .gox.go files next to source by default.
| Layer | Role |
|---|---|
| GOX language | JSX-like declarative markup embedded in Go source. |
pkg/goframe runtime |
Nodes, component instances, hooks, context, events, reconciliation, 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 DOM pressure gates. |
| VS Code extension | Lightweight syntax highlighting/snippets/commands over the same goxc workflow. |
Install the current published toolchain:
go install github.com/graybuton/goframe/cmd/goxc@latestMake sure $(go env GOPATH)/bin is in PATH, then check the local toolchain:
goxc version
goxc doctorWhen working inside this repository, install the local checkout instead:
go install ./cmd/goxcgo run ./cmd/goxc is useful while editing the CLI itself.
Package and serve the counter example:
goxc doctor
goxc package ./examples/counter --compiler=tinygo
goxc size ./examples/counter
goxc serve ./examples/counter --port=8080Open http://127.0.0.1:8080.
Use standard Go compatibility mode when TinyGo is unavailable or when binary size is not the main concern:
goxc package ./examples/counter --compiler=goRecommended first path:
examples/counter
-> examples/components
-> examples/router-dashboard
examples/router-dashboard is the flagship reference app. It shows how the
current primitives fit together in a small Go-first SPA/dashboard/admin-style
application: stable shell, hash router, query filters, component-scoped
resource data, explicit loading/failed UI, controlled form validation, and a
render Error Boundary.
Focused deep dives:
examples/router: routing only;examples/resource: resource lifecycle, stale completion, failure, and cleanup;examples/context: scoped providers and selector consumers;examples/virtualized: fixed-height bounded DOM;examples/todo: controlled inputs, effects, events, and list helpers.
Toolchain/layout examples: examples/multipackage and examples/cmdapp.
Pressure/performance example: examples/dashboard.
For the guided path, read GoFrame Tutorial.
| 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 |
| 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 |
Focused gf.UseResource lifecycle: loading/ready/failed state, stale completion guards, reload, and cleanup. |
goxc package ./examples/resource --compiler=tinygo |
| 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 |
| 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 |
Flagship reference app: 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 |
Server-backed reference fixture: packaged browser/WASM app served by plain Go net/http with a same-origin API. |
goxc package ./examples/server-backed --compiler=go |
Serve any packaged example with:
goxc serve ./examples/<name> --port=8080For cache-safe deployment-style artifacts:
goxc package ./examples/<name> --compiler=tinygo --asset-hash --preload --compress=gzip,brUse goxc export only when you want a visible deploy directory:
goxc export ./examples/counter --out ./distSingle-package apps can keep entry: ".":
app/
├── goframe.json
├── app.gox
├── main.go
└── assets/
├── index.html
└── styles.css
For larger apps, the recommended Go-first layout is a child executable entry package plus app-private internal packages:
app/
├── goframe.json
├── assets/
│ ├── index.html
│ └── styles.css
├── cmd/app/
│ ├── main.go
│ └── app.gox
└── internal/
├── ui/
│ └── layout.gox
└── features/
└── tasks/list.gox
{
"entry": "./cmd/app",
"compiler": "tinygo",
"assets": "./assets"
}Generic child entries such as ./app or ./src/app work as ordinary Go
package directories, but cmd/app + internal/... is the primary Go convention.
GOX discovery remains app-root-wide, so imported internal packages get
generated files too. Cross-package component composition can use normal Go
imports with package-qualified tags such as <ui.Header />. Ordinary Go
function calls are still useful for helper functions and low-level composition.
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 and keep cross-package composition declarative:
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.
Generated GOX uses typed component identity tokens:
var _goxComponent_app_Button = gf.NewComponentType("main.Button", "Button")
gf.ComponentT(_goxComponent_app_Button, ButtonProps{
Label: "Increment",
OnClick: increment,
}, Button)goxc can emit import-aware ids for generated components when the package path
is known, for example:
github.com/graybuton/goframe/examples/cmdapp/internal/ui.Header
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:
- XML-style namespace tags such as
<ui:Header />; - arbitrary selector chains such as
<foo.bar.Baz />; - spread props such as
<Button {...props} />; - template-block
if/for; - arbitrary JavaScript-like expressions.
See GOX language.
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. The runtime also
protects against dirty descendants hidden under memoized ancestors.
Large fixed-height collections should use gf.VirtualList or
gf.VirtualTable instead of mounting hidden offscreen rows.
Client-side page changes can use the small hash router:
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)
}The MVP router is hash-based. Path/history mode, file-based routing, loaders, route middleware, and automatic route-level boundaries are not part of the current preview contract.
For small URL-driven state, routes expose query helpers:
query := ctx.Query()
status := query.Get("status")
gf.Navigate(gf.WithQuery("/issues", gf.QueryValues{
"status": {"open"},
"q": {"auth"},
}))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.
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 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 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.
goxc serve is development-only. Production compression negotiation, cache
headers, TLS, access control, and static-server hardening belong to deployment
infrastructure.
Start here:
- GoFrame Tutorial
- Evaluator guide
- v0.1.0-preview.1 release notes
- v0.1.0-preview.2 release notes
- v0.2.0-preview.1 release notes
- v0.2.0-preview.2 release notes
- v0.2.0-preview.3 release notes
- Architecture and toolchain boundaries
- Runtime model
- GOX language and diagnostics
- API stability tiers
- Public Preview Readiness
- Pre-preview action plan
- Compatibility and deprecation policy
- Migration note template
- Platform support matrix
- CI and regression gates
Runtime topics:
- Lifecycle and effects
- Runtime error semantics
- Error Boundaries
- Forms and validation patterns
- Explicit memoization
- Context selectors
- Virtualized collections
- Client router
- Component-scoped resources
- Component identity strategy
- Component identity next steps
Toolchain and delivery:
- Multi-package GOX workspace
- Cache-safe package delivery
- Manifest compatibility
- Symlink and file safety policy
- Performance baseline
- Release hygiene
Project audits and future design:
- Foundation audit
- Foundation Audit IV
- Public Surface Audit V
- Public Preview Hardening I
- Post-preview v0.2 technical focus
- Inactive GoFrame Player/Engine note
Examples:
- Counter example
- Components example
- Todo example
- Dashboard pressure-test example
- Context selectors example
- Virtualized collections example
- Multi-package GOX example
- Child entry package example
- Router example
- Router dashboard reference example
- Resource loading example
- Server-backed reference example
- VS Code GOX extension
- 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
.gfappare 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=trapbuilds compile the API but are not the proof path for boundary containment. - Resources are component-scoped and explicit-state only.
gf.FetchTextis 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.
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.shPackage all examples:
for app in examples/*; do
if [ -f "$app/goframe.json" ]; then
compiler=tinygo
case "$app" in
examples/server-backed) compiler=go ;;
esac
goxc package "$app" --compiler="$compiler" --asset-hash --preload --compress=gzip,br
fi
doneCI 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.
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 .goframe is licensed under the Apache License, Version 2.0.