The first component slice is implemented for SPA build output.
Implemented today:
- Explicit or discovered
.cmp.gwdkbuild inputs with@component Name. - Optional
props { name string }declarations. - Component-local Go imports using normal module import paths, such as
import ui "github.com/acme/app/ui". - Typed props contracts that reference imported Go structs, such as
props ui.CounterProps. - Typed state contracts that reference imported Go structs and a no-argument
init function, such as
state ui.CounterState = ui.NewCounterState(). - Typed exports metadata blocks such as
exports { selectedID string }. - Typed emits metadata blocks such as
emits { select(id string) }. - One
view {}block per component. - Self-closing component calls such as
<Hero title="GOWDK" />. - Wrapper component calls such as
<Panel>...</Panel>with child content rendered into<slot />in the component view. - Named slots with caller-side
<template g:slot="name">...</template>and component-side<slot name="name">fallback</slot>. - Scalar scoped slots with component-side slot props and caller-side
let:bindings, for example<slot name="item" label={Title} />consumed by<template g:slot="item" let:label>...</template>. - Page-level cross-package component imports with
use ui "components"and qualified calls such as<ui.Hero />. - Component-scoped cross-package component imports with local aliases such as
use icons "icons"and<icons.Badge />inside the declaring component. - Escaped
{prop}text and attribute interpolation inside component views. - Component prop values can interpolate page build data, such as a route param
from literal
paths {}. - Slot children render in the caller scope, so page build data and route params used inside the child content are resolved before being inserted into the component slot. Scoped slot values are injected into that caller scope for the slot body.
- A component with
state ...renders initial state at build time and emits a generated JavaScript island by default when called withoutg:island. - Component-local
client { func Name(...) { ... } }handlers can group the current safe typed expression subset and be called fromg:on:*with scalar expressions, such asAdd(Count + 1). The olderfn Name(...)spelling remains accepted. - Components can dispatch declared events from
client {}handlers withemit name(Field). Parent components can listen on component calls withg:on:name={...}and receive typed event fields through the compiler-ownedeventscope. - Component-local
computed Name Type { return expr }values can derive read-only browser state from props, state, and other computed values. Computed values may also use one Go-styleifreturn followed by a fallback return. The compiler orders computed values by dependency and rejects cycles. - Pages can declare first-slice page-scoped stores with
store cart ui.CartState = ui.NewCartState(). Componentclient {}blocks can declare explicit dependencies withuse cart; the compiler validates store type/init contracts and rejects unknown store uses. Runtime shared-state subscriptions are still planned. - A component can declare
@wasm ./browser/counterto compile a browser-side Go package and make normal calls to that component use the WASM island runtime by default. The package is built withGOOS=js GOARCH=wasm, must produce a real browser WASM module, and must not import server/process/network packages such asnet/http,os/exec,database/sql, rawsyscall,plugin, orunsafe. - A component call can explicitly request the WASM island artifact with
<Counter g:island="wasm" />when a call-site override is needed. If the component has no@wasmpackage, GOWDK still emits the first-slice placeholder module plus loader shape for that explicit call. - Duplicate component names are rejected during manifest validation.
- Redundant component implementations are rejected during manifest validation
with
redundant_component_implementation, even when their names differ. - Component
@cssdeclarations are parsed, lowered into compiler metadata, emitted as scoped component CSS, linked from generated pages, content-hashed, manifest-mapped, and served with immutable cache headers. Component@assetdeclarations are emitted as content-hashed files underassets/gowdk/components/<package>/<component>/, manifest-mapped, and served with immutable cache headers. - Component
js "./file.js"andjs "./file.ts"declarations emit scoped browser module files and link them only from pages that call that component. Inlinejs {}blocks are supported for small cases, but path-based modules are preferred. GOWDK transforms TypeScript without type-checking and does not yet bundle or follow JavaScript imports. - Component files are compiler inputs, not Go imports. A page can call a
same-package component by name, such as
<Hero />, when that component file is part of the same build/module input set. Cross-package page calls must use a GOWDK source import and qualified component tag:
package pages
use ui "components"
view {
<main><ui.Hero title="GOWDK" /></main>
}
The quoted use target is a discovered .gwdk package name, not a Go import
path. Imported components can call sibling components in their own package by
bare name inside the component body. Components can also declare their own
scoped use aliases for qualified child component calls:
package marketing
use icons "icons"
@component Hero
view {
<section><icons.Badge /></section>
}
Examples
Default slot:
@component Card
view {
<section><slot /></section>
}
Named slot:
@component Panel
view {
<section>
<header><slot name="actions"><span>No actions</span></slot></header>
<slot />
</section>
}
Scoped slot:
@component Row
props {
label string
}
view {
<slot name="item" value={label} />
}
view {
<Row label="Ada">
<template g:slot="item" let:value>
<strong>{value}</strong>
</template>
</Row>
}
Typed emits:
@component Option
props {
ID string
}
emits {
select(id string)
}
client {
fn Pick() {
emit select(ID)
}
}
Bindable child state is not a stable public contract. Use typed emits plus parent-owned state instead:
view {
<Option g:on:select={SelectedID = event.id} />
}
Typed exports are metadata today:
exports {
selectedID string
}
Stores are explicit page-scoped UI state:
@route "/cart"
@guard public
store cart ui.CartState = ui.NewCartState()
@component CartButton
client {
use cart
}
WASM islands are declared on the component:
@component Counter
@wasm ./browser/counter
view {
<button>Count</button>
}
view {
<Counter />
}
Component Contract
Component files are GOWDK compiler inputs. They are not imported by Go code and
must not import generated app output. Go import declarations inside .gwdk
files import normal Go packages for typed contracts and build-time helpers.
GOWDK use declarations import discovered .gwdk source packages; today that
contract is implemented for qualified component calls.
Props are caller-provided inputs. Inline props {} declarations are string-only
in the current slice, while imported Go struct contracts can provide typed
props metadata. Parent calls can pass literal strings and the implemented
build-data interpolation subset. Props are read-only to client {} code; mutable
browser state belongs in state or in an explicit page store.
Imported Go structs are the stable typed prop path today. Non-string inline
props are planned, but inline props {} blocks currently accept only string.
Defaults should be expressed in normal Go init/build data or by rendering a
fallback in the component view {}. There is no rest/spread prop syntax, prop
renaming syntax, or implicit global prop lookup in the current contract.
State is component-local UI state. A state Type = Init() declaration runs the
no-argument Go init function at build time for SPA/static output and serializes
the JSON-compatible initial value into the component island. State is visible to
the browser and must not carry secrets, trusted authorization state, database
state, or server validation results that the server still needs to enforce.
Bindable child state is not stable as a parent/child contract. Parent-child coordination should use typed emits plus parent-owned state, or server actions for trusted behavior.
Computed values are read-only derived state. They can depend on props, state, and other computed values. The compiler builds a dependency graph for declared computed values, emits them in dependency order, and rejects cycles before generated JavaScript is written.
Stores are page-scoped UI state. A page store declaration names the store,
the Go type, and the build-time init function. A component client { use name }
declares that the island may use that store. Generated browser store state is a
page-local enhancement contract; it is not global application authority and it
does not replace server-side routing, auth, validation, persistence, or action
behavior.
Store use is explicit. Same-page stores use client { use cart }; stores from
another discovered .gwdk package require a GOWDK use alias and a qualified
client store reference such as client { use stores.cart }. Cross-package
stores are validated by alias and store name, not discovered globally.
App-global stores and cross-route persistence are deferred.
Exports are typed component metadata today. They document values a component intends to expose, but parent pages/components do not yet have a stable runtime API for consuming exported component values. Until that contract is generated and documented, use props, typed emits, stores, actions, or build/load data for actual data flow.
Slots are the reusable-markup primitive. A default slot uses <slot />, named
slots use <slot name="name">, and scoped slots pass scalar values through
slot props plus caller-side let: bindings. GOWDK does not currently have a
separate snippet/render value model.
Recursive component rendering is rejected to prevent unbounded build-time
rendering. Dynamic component selection is deferred; component calls must name a
known component directly or through an explicit use alias.
client {} is a compiler-owned UI language, not arbitrary JavaScript. The
supported handlers, helpers, lifecycle blocks, effects, refs, list built-ins,
bindings, conditionals, computed values, and scalar expressions are defined in
syntax.md. Generated island JavaScript interprets that bounded
subset instead of evaluating arbitrary user JavaScript source.
Client handlers run in source order. The generated runtime batches state
updates, recomputes computed values in dependency order, runs cleanup before
effects rerun or unload, and then updates DOM bindings. Async client handlers
are limited to compiler-owned async helpers such as validated
await fetchJSON[T](urlExpr) assignments; they cannot return values and do not
change the ownership boundary.
Generated browser runtime behavior is scoped to the island or page enhancement that requested it. JavaScript may update text, attributes, classes, styles, form bindings, list rows, local state, page stores, partial responses, and SPA link transitions. It must not own route existence, auth, business rules, database access, trusted server validation, action behavior, or cache/loading policy. Real routes, direct browser refresh, and server behavior stay owned by the compiler manifest, generated Go, and user Go handlers.
Production WASM islands are declared with component-level @wasm. Normal calls
to that component use the WASM island runtime by default. The referenced package
is browser-side Go compiled for GOOS=js GOARCH=wasm with server and process
packages rejected. GOWDK validates the required component-scoped ABI entrypoints,
ships Go's browser wasm_exec.js runtime asset for declared Go WASM packages,
and keeps DOM mutation in the generated host loader. WASM islands are not a
replacement for backend handlers.
Not implemented yet:
- Non-string props in inline
props {}blocks. - Stable parent consumption of typed
exports {}values. - Rest/spread props, prop renaming, recursive component rendering, dynamic component selection, and bindable child state.
- Full runtime validation for user browser logic in WASM islands, including required Go/JS entrypoint registration and export checks.
- Wiring generated Go component packages into the generated app layout.
- Cross-package store and asset use syntax.
- Emitting and rewriting component-scoped CSS and component-level assets from
the existing component
@cssand@assetmetadata.
Component design rules:
- Components must stay portable and must not derive route identity from folder location.
- Component resolution should be explicit enough for diagnostics, editor navigation, and generated code.
- Generated component output must escape untrusted text by default.
- Public generated-runtime contracts belong under
runtime/.