240 lines
6.5 KiB
Text
240 lines
6.5 KiB
Text
---
|
||
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. That’s the Wails v3 binding system. Go forth and bind!
|