Pont Go-interface utilisateur
Présentation détaillée de la communication directe entre Go et JavaScript rendue possible par Wails
Communication directe entre Go et JavaScript
Wails fournit un pont direct en mémoire entre Go et JavaScript, permettant une communication fluide sans surcharge HTTP, frontière entre processus ni goulot d’étranglement lié à la sérialisation.
Vue d’ensemble
Point essentiel : ni HTTP, ni IPC, ni frontière entre processus. Uniquement des appels directs de fonctions avec sûreté des types.
Fonctionnement étape par étape
1. Enregistrement des services (démarrage)
Au démarrage de votre application, Wails analyse vos services :
type GreetService struct {
prefix string
}
func (g *GreetService) Greet(name string) string {
return g.prefix + name + "!"
}
func (g *GreetService) Add(a, b int) int {
return a + b
}
// Register service
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{prefix: "Hello, "}),
},
})Ce que fait Wails :
- Analyse la structure pour rechercher les méthodes exportées
- Extrait les informations de type (paramètres et types de retour)
- Crée un registre qui associe les noms de méthodes aux fonctions
- Génère les liaisons TypeScript avec les définitions de types complètes
2. Génération des liaisons (compilation)
Wails génère automatiquement les liaisons TypeScript :
// Auto-generated: frontend/bindings/GreetService.ts
export function Greet(name: string): Promise<string>
export function Add(a: number, b: number): Promise<number>Correspondance des types :
| Type Go | Type TypeScript |
|---|---|
string |
string |
int, int32, int64 |
number |
float32, float64 |
number |
bool |
boolean |
[]T |
T[] |
map[string]T |
Record<string, T> |
struct |
interface |
time.Time |
Date |
error |
Exception (levée) |
3. Appel depuis l’interface utilisateur (exécution)
Le développeur appelle la méthode Go depuis JavaScript :
import { Greet, Add } from './bindings/GreetService'
// Call Go from JavaScript
const greeting = await Greet("World")
console.log(greeting) // "Hello, World!"
const sum = await Add(5, 3)
console.log(sum) // 8Ce qui se passe :
- Fonction de liaison appelée —
Greet("World") - Message créé —
{ service: "GreetService", method: "Greet", args: ["World"] } - Envoi au pont — via le pont JavaScript de la WebView
- Promesse renvoyée — attend la réponse
4. Traitement par le pont (exécution)
Le pont reçoit le message et le traite :
Sécurité : seuls les services enregistrés et les méthodes exportées peuvent être appelés.
5. Exécution Go (exécution)
La méthode Go s’exécute :
func (g *GreetService) Greet(name string) string {
// This runs in Go
return g.prefix + name + "!"
}Contexte d’exécution :
- S’exécute dans une goroutine (sans blocage)
- A accès à toutes les fonctionnalités de Go (système de fichiers, réseau, bases de données)
- Peut appeler librement d’autre code Go
- Renvoie un résultat ou une erreur
6. Réponse (exécution)
Le résultat est renvoyé à JavaScript :
// Promise resolves with result
const greeting = await Greet("World")
// greeting = "Hello, World!"Gestion des erreurs :
func (g *GreetService) Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}try {
const result = await Divide(10, 0)
} catch (error) {
console.error("Go error:", error) // "division by zero"
}Caractéristiques de performance
Vitesse
Surcoût habituel d’un appel : <1 ms
Frontend Call → Bridge → Go Execution → Bridge → Frontend Response
↓ ↓ ↓ ↓ ↓
<0.1ms <0.1ms [varies] <0.1ms <0.1msComparaison avec les autres solutions :
- HTTP/REST : 5-50 ms (pile réseau, sérialisation)
- IPC : 1-10 ms (frontières entre processus, marshalling)
- Pont Wails : <1 ms (en mémoire, appel direct)
Mémoire
Surcoût par appel : ~1 Ko (tampon de messages)
Optimisation sans copie : les données volumineuses (>1 Mo) utilisent la mémoire partagée lorsque cela est possible.
Concurrence
Les appels sont concurrents :
- Chaque appel s’exécute dans sa propre goroutine
- Plusieurs appels peuvent s’exécuter simultanément
- Aucun blocage entre les appels
// These run concurrently
const [result1, result2, result3] = await Promise.all([
SlowOperation1(),
SlowOperation2(),
SlowOperation3(),
])Système de types
Types pris en charge
Types primitifs
// Go
func Example(
s string,
i int,
f float64,
b bool,
) (string, int, float64, bool) {
return s, i, f, b
}// TypeScript (auto-generated)
function Example(
s: string,
i: number,
f: number,
b: boolean,
): Promise<[string, number, number, boolean]>Tranches et tableaux
// Go
func Sum(numbers []int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}// TypeScript
function Sum(numbers: number[]): Promise<number>
// Usage
const total = await Sum([1, 2, 3, 4, 5]) // 15Dictionnaires
// Go
func GetConfig() map[string]interface{} {
return map[string]interface{}{
"theme": "dark",
"fontSize": 14,
"enabled": true,
}
}// TypeScript
function GetConfig(): Promise<Record<string, any>>
// Usage
const config = await GetConfig()
console.log(config.theme) // "dark"Structures
// Go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func GetUser(id int) (*User, error) {
return &User{
ID: id,
Name: "Alice",
Email: "alice@example.com",
}, nil
}// TypeScript (auto-generated)
interface User {
id: number
name: string
email: string
}
function GetUser(id: number): Promise<User>
// Usage
const user = await GetUser(1)
console.log(user.name) // "Alice"Balises JSON : utilisez les balises json: pour contrôler les noms des champs dans TypeScript.
Temps
// Go
func GetTimestamp() time.Time {
return time.Now()
}// TypeScript
function GetTimestamp(): Promise<Date>
// Usage
const timestamp = await GetTimestamp()
console.log(timestamp.toISOString())Erreurs
// Go
func Validate(input string) error {
if input == "" {
return errors.New("input cannot be empty")
}
return nil
}// TypeScript
function Validate(input: string): Promise<void>
// Usage
try {
await Validate("")
} catch (error) {
console.error(error) // "input cannot be empty"
}Types non pris en charge
Ces types ne peuvent pas être transmis par le pont :
- Canaux (
chan T) - Fonctions (
func()) - Interfaces (sauf
interface{}/any) - Pointeurs (sauf vers des structures)
- Champs non exportés (en minuscules)
Solution de contournement : utilisez des identifiants ou des handles :
// ❌ Can't pass file handle
func OpenFile(path string) (*os.File, error) {
return os.Open(path)
}
// ✅ Return file ID instead
var files = make(map[string]*os.File)
func OpenFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
id := generateID()
files[id] = file
return id, nil
}
func ReadFile(id string) ([]byte, error) {
file := files[id]
return io.ReadAll(file)
}
func CloseFile(id string) error {
file := files[id]
delete(files, id)
return file.Close()
}Modèles avancés
Transmission du contexte
Les services peuvent accéder au contexte de l’appel :
type UserService struct{}
func (s *UserService) GetCurrentUser(ctx context.Context) (*User, error) {
// Access the calling window via the context value
window, _ := ctx.Value(application.WindowKey).(application.Window)
_ = window
// Access the application
app := application.Get()
_ = app
// Your logic
return getCurrentUser(), nil
}Le contexte fournit :
- Fenêtre à l’origine de l’appel
- Instance de l’application
- Métadonnées de la requête
Transmission de données en continu
Pour les volumes de données importants, utilisez des événements plutôt que des valeurs de retour :
func ProcessLargeFile(path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
// Emit progress events
app.Event.Emit("file-progress", map[string]interface{}{
"line": lineNum,
"text": scanner.Text(),
})
}
return scanner.Err()
}import { Events } from '@wailsio/runtime'
import { ProcessLargeFile } from './bindings/FileService'
// Listen for progress
Events.On('file-progress', (data) => {
console.log(`Line ${data.line}: ${data.text}`)
})
// Start processing
await ProcessLargeFile('/path/to/large/file.txt')Annulation
Utilisez le contexte pour les opérations annulables :
func LongRunningTask(ctx context.Context) error {
for i := 0; i < 1000; i++ {
// Check if cancelled
select {
case <-ctx.Done():
return ctx.Err()
default:
// Continue work
time.Sleep(100 * time.Millisecond)
}
}
return nil
}Remarque : le contexte est automatiquement annulé lorsque le frontend se déconnecte.
Opérations par lots
Réduisez la surcharge liée au pont en regroupant les opérations :
// ❌ Inefficient: N bridge calls
for _, item := range items {
await ProcessItem(item)
}
// ✅ Efficient: 1 bridge call
await ProcessItems(items)func ProcessItems(items []Item) ([]Result, error) {
results := make([]Result, len(items))
for i, item := range items {
results[i] = processItem(item)
}
return results, nil
}Débogage du pont
Activer la journalisation de débogage
app := application.New(application.Options{
Name: "My App",
LogLevel: slog.LevelDebug, // requires `import "log/slog"`
// `Logger` is an optional *slog.Logger; the default-logger helper is
// application.DefaultLogger(slog.Leveler) if you want to construct one explicitly.
})La sortie affiche :
- Appels de méthodes
- Paramètres
- Valeurs de retour
- Erreurs
- Informations sur les temps d’exécution
Inspecter les liaisons générées
Consultez frontend/bindings/ pour voir le code TypeScript généré :
// frontend/bindings/<full-go-import-path>/myservice.js (real generated shape)
import { Call as $Call } from "/wails/runtime.js";
export function MyMethod($0) {
return $Call.ByID(1234567890, $0); // numeric method ID assigned by the generator
}Tester directement les services
Testez les services Go sans le frontend :
func TestGreetService(t *testing.T) {
service := &GreetService{prefix: "Hello, "}
result := service.Greet("Test")
if result != "Hello, Test!" {
t.Errorf("Expected 'Hello, Test!', got '%s'", result)
}
}Conseils de performance
✅ À faire
- Regrouper les opérations — Réduit le nombre d’appels au pont
- Utiliser des événements pour la transmission en continu — Ne renvoyez pas de grands tableaux
- Veiller à la rapidité des méthodes — Idéalement <100 ms
- Utiliser des goroutines — Pour les opérations longues
- Mettre en cache côté Go — Évite de répéter les calculs
❌ À ne pas faire
- Ne pas multiplier les appels — Regroupez-les lorsque c’est possible
- Ne pas renvoyer d’énormes volumes de données — Utilisez la pagination ou la transmission en continu
- Ne pas bloquer — Utilisez des goroutines pour les opérations longues
- Ne pas transmettre de types complexes — Faites simple
- Ne pas ignorer les erreurs — Gérez-les toujours
Sécurité
Le pont est sécurisé par défaut :
- Liste d’autorisation uniquement — Seuls les services enregistrés peuvent être appelés
- Validation des types — Les arguments sont vérifiés par rapport aux types Go
- Pas d’eval() — Le frontend ne peut pas exécuter de code Go arbitraire
- Pas d’utilisation abusive de la réflexion — Seules les méthodes exportées sont accessibles
Bonnes pratiques :
- Validez les entrées dans Go (ne faites pas confiance au frontend)
- Utilisez le contexte pour l’authentification et l’autorisation
- Limitez la fréquence des opérations coûteuses
- Nettoyez les chemins de fichiers et les entrées utilisateur
Étapes suivantes
Système de build — Découvrez comment Wails compile et regroupe votre application En savoir plus →
Services — Explorez en détail le système de services En savoir plus →
Événements - Utilisez les événements pour la communication par publication/abonnement En savoir plus →
Des questions sur le pont ? Posez-les sur Discord ou consultez les exemples de liaisons.