Comment fonctionne Wails
Comprendre l'architecture de Wails et comment elle atteint des performances natives
Wails est un framework pour créer des applications de bureau en utilisant Go pour le backend et les technologies web pour le frontend. Mais contrairement à Electron, Wails n’inclut pas de navigateur ; il utilise le WebView natif du système d’exploitation.
direction: left
Wails App: {
shape: sequence_diagram
label: "Wails App"
frontend: Frontend
backend: Go Backend
os: Operating System
Initialisation: {
shape: sequence_diagram
backend."Serves Static Web App"
backend -> frontend: HTML / JS / CSS
frontend."Render Site via OS-native WebView"
}
Regular Communication: {
shape: sequence_diagram
frontend."Make API-style call"
frontend -> backend.a: JSON
backend.a."Service processes request"
backend.a -> os: Call System APIs
backend.a."Generate Response"
backend.a -> frontend: JSON
frontend."Process response"
}
}Différences clés par rapport à Electron :
| Aspect | Wails | Electron |
|---|---|---|
| Navigateur | WebView fourni par le OS | Chromium intégré (~100 Mo) |
| Backend | Go (compilé) | Node.js (interprété) |
| Communication | Pont en mémoire | IPC (inter-processus) |
| Taille du bundle | ~15 Mo | ~150 Mo |
| Mémoire | ~10 Mo | ~100 Mo+ |
| Démarrage | <0,5 s | 2-3 s |
Composants principaux
1. WebView natif
Wails utilise le moteur de rendu web intégré au système d’exploitation :
WebView2 (Microsoft Edge WebView2)
- Basé sur Chromium (identique au navigateur Edge)
- Préinstallé sur Windows 10/11
- Mises à jour automatiques via Windows Update
- Prise en charge complète des normes web modernes
WebKit (moteur de rendu de Safari)
- Intégré à macOS
- Même moteur que le navigateur Safari
- Excellentes performances et autonomie
- Prise en charge complète des normes web modernes
WebKitGTK (port GTK de WebKit)
- Installé via le gestionnaire de paquets
- Même moteur que GNOME Web (Epiphany)
- Bonne prise en charge des normes
- Léger et performant
Pourquoi cela compte :
- Pas de navigateur intégré → Taille d’application réduite
- Natif au système → Meilleure intégration et performances
- Mises à jour automatiques correctifs de sécurité via les mises à jour du système
- Rendu familier → Identique au navigateur du système
2. Le pont Wails
Le pont est le cœur de Wails ; il permet une communication directe entre Go et JavaScript.
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"Fonctionnement :
- Le frontend appelle une méthode Go (via le binding auto-généré)
- Le pont encode l’appel en JSON (nom de la méthode + arguments)
- Le routeur trouve la méthode Go dans les services enregistrés
- La méthode Go s’exécute et retourne une valeur
- Le pont décode le résultat et l’envoie au frontend
- La Promise se résout en JavaScript avec le résultat
Caractéristiques de performance :
- En mémoire : Pas de surcharge réseau, pas de HTTP
- Zero-copy lorsque cela est possible (pour les gros volumes de données)
- Asynchrone par défaut : Non-bloquant des deux côtés
- Typé de manière sûre : Définitions TypeScript auto-générées
3. Système de services
Les services sont la méthode recommandée pour exposer la fonctionnalité Go au frontend.
// 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, "}),
},
})Découverte des services :
- Wails analyse votre struct au démarrage
- Les méthodes exportées deviennent appelables depuis le frontend
- Les informations de type sont extraites pour les bindings TypeScript
- La gestion des erreurs est automatique (erreurs Go → exceptions JS)
Binding TypeScript généré :
// Auto-generated in frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function GetTime(): Promise<Date>Pourquoi utiliser des services ?
- Typé de manière sûre : Prise en charge complète de TypeScript
- Découverte automatique : Pas d’enregistrement manuel des méthodes
- Organisé : Regroupez les fonctionnalités liées
- Testable : Les services sont de simples structs Go
En savoir plus sur les services →
4. Système d’événements
Les événements permettent une communication pub/sub entre les composants.
direction: left
Wails Event System: {
shape: sequence_diagram
window1: Window 1
window2: Window 2
backend: Go Backend
Event Driver: {
shape: sequence_diagram
window1."Subscribe to 'data-updated' events"
window2."Subscribe to 'data-updated' events"
backend.a."App Emit('data-updated', data)"
backend.a -> window1.a:"JSON Event Bus"
backend.a -> window2:"JSON Event Bus"
window1.a."Subscriber processes On('data-updated', handler)"
window2."Subscriber processes On('data-updated', handler)"
}
}Cas d’utilisation :
- Communication entre fenêtres : Une fenêtre notifie les autres
- Tâches en arrière-plan : Le service Go notifie l’UI de la progression
- Synchronisation d’état : Garder plusieurs fenêtres synchronisées
- Découplage lâche : Les composants n’ont pas besoin de références directes
Exemple :
// Go: Emit an event
app.Event.Emit("user-logged-in", user)// JavaScript: Listen for event
import { Events } from '@wailsio/runtime'
Events.On('user-logged-in', (user) => {
console.log('User logged in:', user)
})En savoir plus sur les événements →
Cycle de vie de l’application
Comprendre le cycle de vie vous aide à savoir quand initialiser les ressources et les nettoyer.
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 -> EndLifecycle hooks:
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.
},
})Build Process
Understanding how Wails builds your application:
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.BinaryBuild steps:
- Analyse Go code
- Scan services for exported methods
- Extract parameter and return types
- Generate method signatures
- Generate TypeScript bindings
- Create
.tsfiles for each service - Include full type definitions
- Add JSDoc comments
- Build frontend
- Run your bundler (Vite, webpack, etc.)
- Minify and optimise
- Output to
frontend/dist/
- Compile Go
- Compile with optimisations (
-ldflags="-s -w") - Include build metadata
- Platform-specific compilation
- Embed assets
- Embed frontend files into Go binary
- Compress assets
- Create single executable
Result: A single native executable with everything embedded.
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:
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:
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.
Memory regions:
- Go Heap
- Your services and application state
- Managed by Go garbage collector
- Typically 5-10MB for simple apps
- WebView Memory
- DOM, JavaScript heap, CSS
- Managed by WebView’s engine
- Typically 10-20MB for simple apps
- 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 →
Security Model
Wails provides a secure-by-default architecture:
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:
- Method whitelisting
- Only exported methods are callable
- Private methods are inaccessible
- Explicit service registration required
- Type validation
- Arguments checked against Go types
- Invalid types rejected
- Prevents injection attacks
- No eval()
- Frontend can’t execute arbitrary Go code
- Only predefined methods callable
- No dynamic code execution
- 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
Next Steps
Application Lifecycle - Understand startup, shutdown, and lifecycle hooks
Learn More →
Go-Frontend Bridge - Deep dive into how the bridge works
Learn More →
Build System - Understand how Wails builds your application
Learn More →
Start Building - Apply what you’ve learned in a tutorial
Tutorials →
Des questions sur l’architecture ? Posez-les sur Discord ou consultez la référence de l’API.