Jembatan Go-Frontend
Komunikasi Go-JavaScript Langsung
Section titled “Komunikasi Go-JavaScript Langsung”Wails menyediakan jembatan in-memory langsung antara Go dan JavaScript, memungkinkan komunikasi seamless tanpa overhead HTTP, batas proses, atau bottleneck serialisasi.
Gambaran Besar
Section titled “Gambaran Besar”Wawasan kunci: Tanpa HTTP, tanpa IPC, tanpa batas proses. Hanya panggilan fungsi langsung dengan type safety.
Cara Kerjanya: Langkah demi Langkah
Section titled “Cara Kerjanya: Langkah demi Langkah”1. Registrasi Service (Startup)
Section titled “1. Registrasi Service (Startup)”Ketika aplikasi Anda start, Wails memindai service Anda:
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 serviceapp := application.New(application.Options{ Services: []application.Service{ application.NewService(&GreetService{prefix: "Hello, "}), },})Yang dilakukan Wails:
- Memindai struct untuk metode yang diekspor
- Mengekstrak informasi tipe (parameter, tipe return)
- Membangun registry yang memetakan nama metode ke fungsi
- Menghasilkan binding TypeScript dengan definisi tipe lengkap
2. Generasi Binding (Waktu Build)
Section titled “2. Generasi Binding (Waktu Build)”Wails menghasilkan binding TypeScript secara otomatis:
export function Greet(name: string): Promise<string>export function Add(a: number, b: number): Promise<number>Pemetaan tipe:
| Tipe Go | Tipe 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 (thrown) |
3. Panggilan Frontend (Runtime)
Section titled “3. Panggilan Frontend (Runtime)”Developer memanggil metode Go dari JavaScript:
import { Greet, Add } from './bindings/GreetService'
// Call Go from JavaScriptconst greeting = await Greet("World")console.log(greeting) // "Hello, World!"
const sum = await Add(5, 3)console.log(sum) // 8Yang terjadi:
- Fungsi binding dipanggil -
Greet("World") - Pesan dibuat -
{ service: "GreetService", method: "Greet", args: ["World"] } - Dikirim ke bridge - Via JavaScript bridge WebView
- Promise dikembalikan - Menunggu respons
4. Pemrosesan Bridge (Runtime)
Section titled “4. Pemrosesan Bridge (Runtime)”Bridge menerima pesan dan memprosesnya:
Keamanan: Hanya service terdaftar dan metode yang diekspor yang dapat dipanggil.
5. Eksekusi Go (Runtime)
Section titled “5. Eksekusi Go (Runtime)”Metode Go dieksekusi:
func (g *GreetService) Greet(name string) string { // This runs in Go return g.prefix + name + "!"}Context eksekusi:
- Berjalan di goroutine (non-blocking)
- Memiliki akses ke semua fitur Go (file system, jaringan, database)
- Dapat memanggil kode Go lain dengan bebas
- Mengembalikan hasil atau error
6. Respons (Runtime)
Section titled “6. Respons (Runtime)”Hasil dikirim kembali ke JavaScript:
// Promise resolves with resultconst greeting = await Greet("World")// greeting = "Hello, World!"Penanganan error:
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"}Karakteristik Performa
Section titled “Karakteristik Performa”Kecepatan
Section titled “Kecepatan”Overhead panggilan tipikal: <1ms
Frontend Call → Bridge → Go Execution → Bridge → Frontend Response ↓ ↓ ↓ ↓ ↓ <0.1ms <0.1ms [varies] <0.1ms <0.1msDibanding alternatif:
- HTTP/REST: 5-50ms (network stack, serialisasi)
- IPC: 1-10ms (batas proses, marshalling)
- Wails Bridge: <1ms (in-memory, panggilan langsung)
Memori
Section titled “Memori”Overhead per-panggilan: ~1KB (buffer pesan)
Optimasi zero-copy: Data besar (>1MB) menggunakan shared memory jika memungkinkan.
Concurrency
Section titled “Concurrency”Panggilan bersifat concurrent:
- Setiap panggilan berjalan di goroutine sendiri
- Beberapa panggilan dapat dieksekusi secara simultan
- Tidak ada blocking antar panggilan
// These run concurrentlyconst [result1, result2, result3] = await Promise.all([ SlowOperation1(), SlowOperation2(), SlowOperation3(),])Sistem Tipe
Section titled “Sistem Tipe”Tipe yang Didukung
Section titled “Tipe yang Didukung”Primitif
Section titled “Primitif”// Gofunc 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]>Slice dan Array
Section titled “Slice dan Array”// Gofunc Sum(numbers []int) int { total := 0 for _, n := range numbers { total += n } return total}// TypeScriptfunction Sum(numbers: number[]): Promise<number>
// Usageconst total = await Sum([1, 2, 3, 4, 5]) // 15// Gofunc GetConfig() map[string]interface{} { return map[string]interface{}{ "theme": "dark", "fontSize": 14, "enabled": true, }}// TypeScriptfunction GetConfig(): Promise<Record<string, any>>
// Usageconst config = await GetConfig()console.log(config.theme) // "dark"Struct
Section titled “Struct”// Gotype 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", }, nil}// TypeScript (auto-generated)interface User { id: number name: string email: string}
function GetUser(id: number): Promise<User>
// Usageconst user = await GetUser(1)console.log(user.name) // "Alice"Tag JSON: Gunakan tag json: untuk mengontrol nama field di TypeScript.
// Gofunc GetTimestamp() time.Time { return time.Now()}// TypeScriptfunction GetTimestamp(): Promise<Date>
// Usageconst timestamp = await GetTimestamp()console.log(timestamp.toISOString())// Gofunc Validate(input string) error { if input == "" { return errors.New("input cannot be empty") } return nil}// TypeScriptfunction Validate(input: string): Promise<void>
// Usagetry { await Validate("")} catch (error) { console.error(error) // "input cannot be empty"}Tipe yang Tidak Didukung
Section titled “Tipe yang Tidak Didukung”Tipe ini tidak dapat diteruskan melintasi bridge:
- Channel (
chan T) - Fungsi (
func()) - Interface (kecuali
interface{}/any) - Pointer (kecuali ke struct)
- Field tidak diekspor (lowercase)
Solusi: Gunakan ID atau handle:
// ❌ Can't pass file handlefunc OpenFile(path string) (*os.File, error) { return os.Open(path)}
// ✅ Return file ID insteadvar 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()}Pola Lanjutan
Section titled “Pola Lanjutan”Context Passing
Section titled “Context Passing”Service dapat mengakses context panggilan:
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}Context menyediakan:
- Window yang melakukan panggilan
- Instance aplikasi
- Metadata request
Streaming Data
Section titled “Streaming Data”Untuk data besar, gunakan event alih-alih nilai return:
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 progressEvents.On('file-progress', (data) => { console.log(`Line ${data.line}: ${data.text}`)})
// Start processingawait ProcessLargeFile('/path/to/large/file.txt')Pembatalan
Section titled “Pembatalan”Gunakan context untuk operasi yang dapat dibatalkan:
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}Catatan: Pembatalan context saat frontend disconnect otomatis.
Operasi Batch
Section titled “Operasi Batch”Kurangi overhead bridge dengan batching:
// ❌ Inefficient: N bridge callsfor _, item := range items { await ProcessItem(item)}
// ✅ Efficient: 1 bridge callawait 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}Debugging Bridge
Section titled “Debugging Bridge”Aktifkan Debug Logging
Section titled “Aktifkan Debug Logging”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.})Output menampilkan:
- Panggilan metode
- Parameter
- Nilai return
- Error
- Informasi timing
Periksa Binding yang Dihasilkan
Section titled “Periksa Binding yang Dihasilkan”Periksa frontend/bindings/ untuk melihat TypeScript yang dihasilkan:
// 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}Uji Service Langsung
Section titled “Uji Service Langsung”Uji service Go tanpa 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) }}Tips Performa
Section titled “Tips Performa”✅ Lakukan
Section titled “✅ Lakukan”- Operasi batch - Kurangi panggilan bridge
- Gunakan event untuk streaming - Jangan return array besar
- Jaga metode tetap cepat - <100ms ideal
- Gunakan goroutine - Untuk operasi panjang
- Cache di sisi Go - Hindari perhitungan berulang
❌ Jangan
Section titled “❌ Jangan”- Jangan buat panggilan berlebihan - Batch jika memungkinkan
- Jangan return data besar - Gunakan pagination atau streaming
- Jangan block - Gunakan goroutine untuk operasi panjang
- Jangan kirim tipe kompleks - Jaga tetap sederhana
- Jangan abaikan error - Selalu tangani error
Keamanan
Section titled “Keamanan”Bridge aman secara default:
- Whitelist saja - Hanya service terdaftar yang dapat dipanggil
- Validasi tipe - Argumen dicek terhadap tipe Go
- Tanpa eval() - Frontend tidak dapat mengeksekusi kode Go sembarangan
- Tanpa penyalahgunaan reflection - Hanya metode yang diekspor yang dapat diakses
Praktik terbaik:
- Validasi input di Go (jangan percaya frontend)
- Gunakan context untuk autentikasi/otorisasi
- Rate limit operasi yang mahal
- Sanitasi path file dan input pengguna
Langkah Selanjutnya
Section titled “Langkah Selanjutnya”Sistem Build - Pelajari bagaimana Wails mem-build dan membundel aplikasi Anda
Pelajari Selengkapnya →
Service - Pelajari mendalam sistem service
Pelajari Selengkapnya →
Event - Gunakan event untuk komunikasi pub/sub
Pelajari Selengkapnya →
Pertanyaan tentang bridge? Tanyakan di Discord atau lihat contoh binding.