gui/docs/ref/wails-v3/concepts/architecture.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

655 lines
16 KiB
Text

---
title: How Wails Works
description: Understanding the Wails architecture and how it achieves native performance
sidebar:
order: 1
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails is a framework for building desktop applications using **Go for the backend** and **web technologies for the frontend**. But unlike Electron, Wails doesn't bundle a browser—it uses the **operating system's native WebView**.
```d2
direction: right
User: "User" {
shape: person
style.fill: "#3B82F6"
}
Application: "Your Wails Application" {
Frontend: "Frontend\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
Runtime: "Wails Runtime" {
Bridge: "Message Bridge" {
shape: diamond
style.fill: "#10B981"
}
Bindings: "Type-Safe Bindings" {
shape: rectangle
style.fill: "#10B981"
}
}
Backend: "Go Backend" {
Services: "Your Services" {
shape: rectangle
style.fill: "#00ADD8"
}
NativeAPIs: "OS APIs" {
shape: rectangle
style.fill: "#00ADD8"
}
}
}
OS: "Operating System" {
WebView: "Native WebView\n(WebKit/WebView2/WebKitGTK)" {
shape: rectangle
style.fill: "#6B7280"
}
SystemAPIs: "System APIs\n(Windows/macOS/Linux)" {
shape: rectangle
style.fill: "#6B7280"
}
}
User -> Application.Frontend: "Interacts with UI"
Application.Frontend <-> Application.Runtime.Bridge: "JSON messages"
Application.Runtime.Bridge <-> Application.Backend.Services: "Direct function calls"
Application.Runtime.Bindings -> Application.Frontend: "TypeScript definitions"
Application.Frontend -> OS.WebView: "Renders in"
Application.Backend.NativeAPIs -> OS.SystemAPIs: "Native calls"
```
**Key differences from Electron:**
| Aspect | Wails | Electron |
|--------|-------|----------|
| **Browser** | OS-provided WebView | Bundled Chromium (~100MB) |
| **Backend** | Go (compiled) | Node.js (interpreted) |
| **Communication** | In-memory bridge | IPC (inter-process) |
| **Bundle Size** | ~15MB | ~150MB |
| **Memory** | ~10MB | ~100MB+ |
| **Startup** | &lt;0.5s | 2-3s |
## Core Components
### 1. Native WebView
Wails uses the operating system's built-in web rendering engine:
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
**WebView2** (Microsoft Edge WebView2)
- Based on Chromium (same as Edge browser)
- Pre-installed on Windows 10/11
- Automatic updates via Windows Update
- Full modern web standards support
</TabItem>
<TabItem label="macOS" icon="apple">
**WebKit** (Safari's rendering engine)
- Built into macOS
- Same engine as Safari browser
- Excellent performance and battery life
- Full modern web standards support
</TabItem>
<TabItem label="Linux" icon="linux">
**WebKitGTK** (GTK port of WebKit)
- Installed via package manager
- Same engine as GNOME Web (Epiphany)
- Good standards support
- Lightweight and performant
</TabItem>
</Tabs>
**Why this matters:**
- **No bundled browser** → Smaller app size
- **OS-native** → Better integration and performance
- **Auto-updates** → Security patches from OS updates
- **Familiar rendering** → Same as system browser
### 2. The Wails Bridge
The bridge is the heart of Wails—it enables **direct communication** between Go and JavaScript.
```d2
direction: down
Frontend: "Frontend (JavaScript)" {
shape: rectangle
style.fill: "#8B5CF6"
}
Bridge: "Wails Bridge" {
Encoder: "JSON Encoder" {
shape: rectangle
}
Router: "Method Router" {
shape: diamond
style.fill: "#10B981"
}
Decoder: "JSON Decoder" {
shape: rectangle
}
}
Backend: "Backend (Go)" {
Services: "Registered Services" {
shape: rectangle
style.fill: "#00ADD8"
}
}
Frontend -> Bridge.Encoder: "1. Call Go method\nGreet('Alice')"
Bridge.Encoder -> Bridge.Router: "2. Encode to JSON\n{method: 'Greet', args: ['Alice']}"
Bridge.Router -> Backend.Services: "3. Route to service\nGreetService.Greet('Alice')"
Backend.Services -> Bridge.Decoder: "4. Return result\n'Hello, Alice!'"
Bridge.Decoder -> Frontend: "5. Decode to JS\nPromise resolves"
```
**How it works:**
1. **Frontend calls a Go method** (via auto-generated binding)
2. **Bridge encodes the call** to JSON (method name + arguments)
3. **Router finds the Go method** in registered services
4. **Go method executes** and returns a value
5. **Bridge decodes the result** and sends back to frontend
6. **Promise resolves** in JavaScript with the result
**Performance characteristics:**
- **In-memory**: No network overhead, no HTTP
- **Zero-copy** where possible (for large data)
- **Async by default**: Non-blocking on both sides
- **Type-safe**: TypeScript definitions auto-generated
### 3. Service System
Services are the recommended way to expose Go functionality to the frontend.
```go
// Define a service (just a regular Go struct)
type GreetService struct {
prefix string
}
// Methods with exported names are automatically available
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
func (g *GreetService) GetTime() time.Time {
return time.Now()
}
// Register the service
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{prefix: "Hello, "}),
},
})
```
**Service discovery:**
- Wails **scans your struct** at startup
- **Exported methods** become callable from frontend
- **Type information** is extracted for TypeScript bindings
- **Error handling** is automatic (Go errors → JS exceptions)
**Generated TypeScript binding:**
```typescript
// Auto-generated in frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function GetTime(): Promise<Date>
```
**Why services?**
- **Type-safe**: Full TypeScript support
- **Auto-discovery**: No manual registration of methods
- **Organised**: Group related functionality
- **Testable**: Services are just Go structs
[Learn more about services →](/features/bindings/services)
### 4. Event System
Events enable **pub/sub communication** between components.
```d2
direction: right
GoService: "Go Service" {
shape: rectangle
style.fill: "#00ADD8"
}
EventBus: "Event Bus" {
shape: cylinder
style.fill: "#10B981"
}
Frontend1: "Window 1" {
shape: rectangle
style.fill: "#8B5CF6"
}
Frontend2: "Window 2" {
shape: rectangle
style.fill: "#8B5CF6"
}
GoService -> EventBus: "Emit('data-updated', data)"
EventBus -> Frontend1: "Notify subscribers"
EventBus -> Frontend2: "Notify subscribers"
Frontend1 -> EventBus: "On('data-updated', handler)"
Frontend2 -> EventBus: "On('data-updated', handler)"
```
**Use cases:**
- **Window communication**: One window notifies others
- **Background tasks**: Go service notifies UI of progress
- **State synchronisation**: Keep multiple windows in sync
- **Loose coupling**: Components don't need direct references
**Example:**
```go
// Go: Emit an event
app.Event.Emit("user-logged-in", user)
```
```javascript
// JavaScript: Listen for event
import { Events } from '@wailsio/runtime'
Events.On('user-logged-in', (user) => {
console.log('User logged in:', user)
})
```
[Learn more about events →](/features/events/system)
## Application Lifecycle
Understanding the lifecycle helps you know when to initialise resources and clean up.
```d2
direction: down
Start: "Application Start" {
shape: oval
style.fill: "#10B981"
}
Init: "Initialisation" {
Create: "Create Application" {
shape: rectangle
}
Register: "Register Services" {
shape: rectangle
}
Setup: "Setup Windows/Menus" {
shape: rectangle
}
}
Run: "Event Loop" {
Events: "Process Events" {
shape: rectangle
}
Messages: "Handle Messages" {
shape: rectangle
}
Render: "Update UI" {
shape: rectangle
}
}
Shutdown: "Shutdown" {
Cleanup: "Cleanup Resources" {
shape: rectangle
}
Save: "Save State" {
shape: rectangle
}
}
End: "Application End" {
shape: oval
style.fill: "#EF4444"
}
Start -> Init.Create
Init.Create -> Init.Register
Init.Register -> Init.Setup
Init.Setup -> Run.Events
Run.Events -> Run.Messages
Run.Messages -> Run.Render
Run.Render -> Run.Events: "Loop"
Run.Events -> Shutdown.Cleanup: "Quit signal"
Shutdown.Cleanup -> Shutdown.Save
Shutdown.Save -> End
```
**Lifecycle hooks:**
```go
app := application.New(application.Options{
Name: "My App",
// Called before windows are created
OnStartup: func(ctx context.Context) {
// Initialise database, load config, etc.
},
// Called when app is about to quit
OnShutdown: func() {
// Save state, close connections, etc.
},
})
```
[Learn more about lifecycle →](/concepts/lifecycle)
## Build Process
Understanding how Wails builds your application:
```d2
direction: down
Source: "Source Code" {
Go: "Go Code\n(main.go, services)" {
shape: rectangle
style.fill: "#00ADD8"
}
Frontend: "Frontend Code\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
}
Build: "Build Process" {
AnalyseGo: "Analyse Go Code" {
shape: rectangle
}
GenerateBindings: "Generate Bindings" {
shape: rectangle
}
BuildFrontend: "Build Frontend" {
shape: rectangle
}
CompileGo: "Compile Go" {
shape: rectangle
}
Embed: "Embed Assets" {
shape: rectangle
}
}
Output: "Output" {
Binary: "Native Binary\n(myapp.exe/.app)" {
shape: rectangle
style.fill: "#10B981"
}
}
Source.Go -> Build.AnalyseGo
Build.AnalyseGo -> Build.GenerateBindings: "Extract types"
Build.GenerateBindings -> Source.Frontend: "TypeScript bindings"
Source.Frontend -> Build.BuildFrontend: "Compile (Vite/webpack)"
Build.BuildFrontend -> Build.Embed: "Bundled assets"
Source.Go -> Build.CompileGo
Build.CompileGo -> Build.Embed
Build.Embed -> Output.Binary
```
**Build steps:**
1. **Analyse Go code**
- Scan services for exported methods
- Extract parameter and return types
- Generate method signatures
2. **Generate TypeScript bindings**
- Create `.ts` files for each service
- Include full type definitions
- Add JSDoc comments
3. **Build frontend**
- Run your bundler (Vite, webpack, etc.)
- Minify and optimise
- Output to `frontend/dist/`
4. **Compile Go**
- Compile with optimisations (`-ldflags="-s -w"`)
- Include build metadata
- Platform-specific compilation
5. **Embed assets**
- Embed frontend files into Go binary
- Compress assets
- Create single executable
**Result:** A single native executable with everything embedded.
[Learn more about building →](/guides/build/building)
## Development vs Production
Wails behaves differently in development and production:
<Tabs syncKey="mode">
<TabItem label="Development (wails3 dev)" icon="seti:config">
**Characteristics:**
- **Hot reload**: Frontend changes reload instantly
- **Source maps**: Debug with original source
- **DevTools**: Browser DevTools available
- **Logging**: Verbose logging enabled
- **External frontend**: Served from dev server (Vite)
**How it works:**
```d2
direction: right
WailsApp: "Wails App" {
shape: rectangle
style.fill: "#00ADD8"
}
DevServer: "Vite Dev Server\n(localhost:5173)" {
shape: rectangle
style.fill: "#8B5CF6"
}
WebView: "WebView" {
shape: rectangle
style.fill: "#6B7280"
}
WailsApp -> DevServer: "Proxy requests"
DevServer -> WebView: "Serve with HMR"
WebView -> WailsApp: "Call Go methods"
```
**Benefits:**
- Instant feedback on changes
- Full debugging capabilities
- Faster iteration
</TabItem>
<TabItem label="Production (wails3 build)" icon="rocket">
**Characteristics:**
- **Embedded assets**: Frontend built into binary
- **Optimised**: Minified, compressed
- **No DevTools**: Disabled by default
- **Minimal logging**: Errors only
- **Single file**: Everything in one executable
**How it works:**
```d2
direction: right
Binary: "Single Binary\n(myapp.exe)" {
GoCode: "Compiled Go" {
shape: rectangle
style.fill: "#00ADD8"
}
Assets: "Embedded Assets\n(HTML/CSS/JS)" {
shape: rectangle
style.fill: "#8B5CF6"
}
}
WebView: "WebView" {
shape: rectangle
style.fill: "#6B7280"
}
Binary.Assets -> WebView: "Serve from memory"
WebView -> Binary.GoCode: "Call Go methods"
```
**Benefits:**
- Single file distribution
- Smaller size (minified)
- Better performance
- No external dependencies
</TabItem>
</Tabs>
## Memory Model
Understanding memory usage helps you build efficient applications.
{/* VISUAL PLACEHOLDER: Memory Diagram
Description: Memory layout diagram showing:
1. Go Heap (services, application state)
2. WebView Memory (DOM, JavaScript heap)
3. Shared Memory (bridge communication)
4. Arrows showing data flow between regions
5. Annotations for zero-copy optimisations
Style: Technical diagram with memory regions as boxes, clear labels, size indicators
*/}
**Memory regions:**
1. **Go Heap**
- Your services and application state
- Managed by Go garbage collector
- Typically 5-10MB for simple apps
2. **WebView Memory**
- DOM, JavaScript heap, CSS
- Managed by WebView's engine
- Typically 10-20MB for simple apps
3. **Bridge Memory**
- Message buffers for communication
- Minimal overhead (\<1MB)
- Zero-copy for large data where possible
**Optimisation tips:**
- **Avoid large data transfers**: Pass IDs, fetch details on demand
- **Use events for updates**: Don't poll from frontend
- **Stream large files**: Don't load entirely into memory
- **Clean up listeners**: Remove event listeners when done
[Learn more about performance →](/guides/advanced/performance)
## Security Model
Wails provides a secure-by-default architecture:
```d2
direction: down
Frontend: "Frontend (Untrusted)" {
shape: rectangle
style.fill: "#EF4444"
}
Bridge: "Wails Bridge (Validation)" {
shape: diamond
style.fill: "#F59E0B"
}
Backend: "Backend (Trusted)" {
shape: rectangle
style.fill: "#10B981"
}
Frontend -> Bridge: "Call method"
Bridge -> Bridge: "Validate:\n- Method exists?\n- Types correct?\n- Access allowed?"
Bridge -> Backend: "Execute if valid"
Backend -> Bridge: "Return result"
Bridge -> Frontend: "Send response"
```
**Security features:**
1. **Method whitelisting**
- Only exported methods are callable
- Private methods are inaccessible
- Explicit service registration required
2. **Type validation**
- Arguments checked against Go types
- Invalid types rejected
- Prevents injection attacks
3. **No eval()**
- Frontend can't execute arbitrary Go code
- Only predefined methods callable
- No dynamic code execution
4. **Context isolation**
- Each window has its own context
- Services can check caller context
- Permissions per window possible
**Best practices:**
- **Validate user input** in Go (don't trust frontend)
- **Use context** for authentication/authorisation
- **Sanitise file paths** before file operations
- **Rate limit** expensive operations
[Learn more about security →](/guides/advanced/security)
## Next Steps
**Application Lifecycle** - Understand startup, shutdown, and lifecycle hooks
[Learn More →](/concepts/lifecycle)
**Go-Frontend Bridge** - Deep dive into how the bridge works
[Learn More →](/concepts/bridge)
**Build System** - Understand how Wails builds your application
[Learn More →](/concepts/build-system)
**Start Building** - Apply what you've learned in a tutorial
[Tutorials →](/tutorials/03-notes-vanilla)
---
**Questions about architecture?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [API reference](/reference/overview).