gui/docs/ref/wails-v3/contributing/binding-system.mdx
Snider 4bdbb68f46
Some checks failed
Security Scan / security (push) Failing after 9s
Test / test (push) Failing after 1m21s
refactor: update import path from go-config to core/config
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-14 10:26:36 +00:00

240 lines
6.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Binding System
description: How Wails v3 lets Go and JavaScript call each other with zero boilerplate
sidebar:
order: 5
---
> “Bindings” are the **type-safe contract** that lets you write:
```go
msg, err := chatService.Send("Hello")
```
in Go *and*
```ts
const msg = await window.backend.ChatService.Send("Hello")
```
in TypeScript **without manual glue code**.
This document breaks down *how* that magic happens, from **static analysis** at
build time, through **code generation**, to the **runtime bridge** that moves
bytes over the WebView.
---
## 1. 30-Second Overview
| Stage | Component | Output |
|-------|-----------|--------|
| **Analysis** | `internal/generator/analyse.go` | In-memory AST of exported Go structs, methods, params, return types |
| **Generation** | `internal/generator/render/*.tmpl` | • `pkg/application/bindings.go` (Go)<br />• `frontend/src/wailsjs/**.ts` (TS defs)<br />• `runtime/desktop/@wailsio/runtime/internal/bindings.json` |
| **Runtime** | `pkg/application/messageprocessor_call.go` + JS runtime (`invoke.ts`) | JSON messages over WebView native bridge |
The flow is orchestrated by the `wails3` CLI:
```
wails3 generate ─┬─> generator.Collect() // parse Go packages
├─> generator.Analyse() // build semantic model
└─> generator.Render() // write files + `go fmt`
```
---
## 2. Static Analysis
### Entry Point
```
internal/generator/analyse.go
```
`Analyse(cfg generator.Config)`:
1. Recursively loads provided Go packages with `go/packages`.
2. Walks the *type* and *value* specs of every AST file.
3. Filters **exported** symbols (capitalised) outside `internal/` packages.
4. Records:
* Receiver type (`struct`, `interface`)
* Method name
* Parameter list (name, type, pointer, variadic)
* Return tuple (values + error presence)
5. Normalises types to a **serialisable set**
(`int`, `float64`, `string`, `[]byte`, slices, maps, structs, pointers).
Unsupported types produce a **compile-time error** so mistakes are caught early.
### Model
```go
type Method struct {
ID uint32 // stable hash for runtime lookup
Name string
Params []Param
Results []Result
Receiver *Struct // nil for package funcs
}
```
A deterministic **FNV-1a** hash (`internal/hash/fnv.go`) of
`<Receiver>.<Name>(<Signature>)` becomes the *method ID* used at runtime.
---
## 3. Code Generation
### Templates
`internal/generator/render/` contains text/template files:
| Template | Target |
|----------|--------|
| `go_bindings.tmpl` | `pkg/application/bindings.go` |
| `ts_bindings.tmpl` | `frontend/wailsjs/go/models.d.ts` |
| `ts_index.tmpl` | `frontend/wailsjs/index.ts` |
Add or adjust templates here to customise generation.
### Go Output
`bindings.go` registers a lookup table:
```go
var bindings = []application.BoundMethod{
{ID: 0x7A1201D3, Name: "ChatService.Send", Call: chatServiceSend},
}
func chatServiceSend(ctx context.Context, in []byte) ([]byte, error) {
var req struct{ Msg string }
if err := json.Unmarshal(in, &req); err != nil { return nil, err }
res, err := chatService.Send(req.Msg)
return json.Marshal(res), err
}
```
Key points:
* **Zero reflection** at runtime → performance.
* Marshal/Unmarshal is **per-method** with generated struct wrappers.
### TypeScript Output
```ts
export namespace backend {
export namespace ChatService {
function Send(msg: string): Promise<string>;
}
}
```
* Emitted as **ES modules** so any bundler can tree-shake.
* Method IDs are embedded in an auto-generated `bindings.json` for the JS
runtime.
---
## 4. Runtime Invocation Protocol
### JavaScript Side
```ts
import { System } from "@wailsio/runtime";
await System.invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
```
Implementation: `runtime/desktop/@wailsio/runtime/invoke.ts`
1. Packs `{t:"c", id:<methodID>, p:[...args]}` into JSON.
2. Calls `window.external.invoke(payload)` (WebView2) or equivalent.
3. Returns a `Promise` that resolves/rejects based on the reply.
### Go Side
1. `messageprocessor_call.go` receives the JSON.
2. Looks up `methodID` in the `bindings` slice.
3. Executes the generated stub (`chatServiceSend`).
4. Serialises `{result, error}` back to JS.
### Error Mapping
| Go | JavaScript |
|----|------------|
| `error == nil` | `Promise` resolves with result |
| `errors.New(...)` | `Promise` rejects with `{message, stack, code}` |
The mapping code lives in `runtime/desktop/@wailsio/runtime/errors.ts`.
---
## 5. Calling JavaScript from Go
*Browser → Go* is covered above.
*Go → Browser* uses **Events** or **Eval**:
```go
window.Eval(`alert("Hi")`)
app.Publish("chat:new-message", msg)
```
Binding generation is one-way (Go methods).
For JS-exposed functions use `runtime.EventsOn` in JS and `application.Publish`
from Go.
---
## 6. Extending & Troubleshooting
### Adding Custom Serialisers
* Implement `generator.TypeConverter` interface.
* Register in `generator.Config.Converters`.
* Update JS runtime deserialisation if needed.
### Unsupported Type Error
```
error: field "Client" uses unsupported type: chan struct{}
```
→ wrap the channel in a struct with an exposed API or redesign.
### Version Skew
Bindings are regenerated on **every** `wails3 dev` / `wails3 build`.
If IDE intellisense shows stale stubs, delete `frontend/wailsjs` and rebuild.
### Performance Tips
* Prefer **value** receivers for small structs to reduce allocations.
* Avoid large byte slices over the bridge; use `application.FileServer` instead.
* Batch multiple quick calls into one method to minimise bridge latency.
---
## 7. Key Files Map
| Concern | File |
|---------|------|
| Static analysis entry | `internal/generator/analyse.go` |
| Render pipeline | `internal/generator/generate.go` |
| Template assets | `internal/generator/render/*.tmpl` |
| Go binding table | `pkg/application/bindings.go` (generated) |
| Call processor | `pkg/application/messageprocessor_call.go` |
| JS runtime | `runtime/desktop/@wailsio/runtime/invoke.ts` |
| Errors mapping | `runtime/desktop/@wailsio/runtime/errors.ts` |
Keep this cheat-sheet handy when you trace a bridge bug.
---
## 8. Recap
1. **Generator** scans your Go code → semantic model.
2. **Templates** emit **Go stubs** + **TypeScript definitions**.
3. **Message Processor** executes stubs at runtime.
4. **JS Runtime** wraps it all in idiomatic promises.
All without reflection, without IPC servers, and without you writing a single
line of boilerplate. Thats the Wails v3 binding system. Go forth and bind!