655 lines
16 KiB
Text
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** | <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).
|