gui/docs/ref/wails-v3/contributing/binding-system.mdx

241 lines
6.5 KiB
Text
Raw Permalink Normal View History

---
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!