--- 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: **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 **WebKit** (Safari's rendering engine) - Built into macOS - Same engine as Safari browser - Excellent performance and battery life - Full modern web standards support **WebKitGTK** (GTK port of WebKit) - Installed via package manager - Same engine as GNOME Web (Epiphany) - Good standards support - Lightweight and performant **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 export function GetTime(): Promise ``` **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: **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 **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 ## 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).