Alasan Aplikasi Wails Crash di Produksi dan Cara Mengontrolnya di Level Rendah
26 de julho de 2026
0
Computing/SoftwareComments (0)
Log in to leave a comment
No posts yet
Log in to leave a comment
No posts yet
Saat membuat aplikasi desktop dengan Go, Wails adalah pilihan yang sangat menarik. Berbeda dengan Electron yang mengemas seluruh Chromium, Wails terasa jauh lebih ringan dan cepat. Namun, begitu Anda keluar dari tutorial dan mencoba membangun layanan yang sesungguhnya, Anda akan langsung menabrak dinding. Memori CGo mulai bocor, dan WebView berperilaku berbeda antara Windows dan macOS.
Bagi pengembang backend yang belum pernah berpengalaman dengan integrasi C/C++ atau Objective-C, titik ini adalah tembok penghalang yang besar. Jika Anda tidak bisa menangani kebocoran memori yang tersembunyi di balik contoh-contoh keren di dokumentasi resmi serta fragmentasi WebView antar-OS, rilis ke lingkungan produksi akan menjadi hal yang mustahil.
Kesalahpahaman paling umum saat menggunakan CGo adalah berharap garbage collector milik Go akan mengurus area C juga. Tentu saja, itu tidak akan terjadi. Memori yang dialokasikan menggunakan C.CString atau C.malloc akan tetap berada di area C dan terus memakan memori sampai aplikasi berhenti.
Saat mengoper slice Go ke fungsi C, Anda juga harus berhati-hati. Jika Anda mengoper alamat dari slice header itu sendiri, korupsi memori akan terjadi. Sangat penting untuk mengoper alamat sebenarnya dari elemen pertama, yaitu unsafe.Pointer(&slice[0]), agar tetap aman. Lalu saat memanggil kode Objective-C di macOS, jangan pernah terlalu percaya pada ARC. Objek yang dibuat di dalam thread loop CGo akan terus menumpuk di NSAutoreleasePool. Anda harus membungkusnya secara eksplisit dengan blok @autoreleasepool { ... } agar memori langsung dibersihkan secara berkala.
Jika Anda berada di lingkungan Windows, Anda tidak perlu repot-repot membawa kompilator CGo (MinGW) sebagai ketergantungan. Tanpa overhead CGo, Anda bisa langsung memanggil DLL menggunakan paket syscall. Kode untuk memanggil dwmapi.dll dan mengaktifkan dark mode ternyata jauh lebih sederhana dari yang dikira.
`go
// system_windows.go
//go:build windows
package native
import (
"syscall"
"unsafe"
)
var (
modDwmApi = syscall.NewLazyDLL("dwmapi.dll")
procDwmSetWindowAttribute = modDwmApi.NewProc("DwmSetWindowAttribute")
)
const DWMWA_USE_IMMERSIVE_DARK_MODE = 20
func SetWindowsDarkMode(hwnd uintptr, enable bool) error {
var val int32
if enable {
val = 1
}
ret, _, err := procDwmSetWindowAttribute.Call(
hwnd,
uintptr(DWMWA_USE_IMMERSIVE_DARK_MODE),
uintptr(unsafe.Pointer(&val)),
uintptr(unsafe.Sizeof(val)),
)
if ret != 0 {
return err
}
return nil
}
`
Saat membuat modul kontrol native, pertama-tama definisikan antarmuka bersama (system_interface.go). Kemudian untuk implementasi macOS (system_darwin.go), masukkan logika Objective-C bersama dengan direktif //go:build darwin. Sementara untuk implementasi Windows, tulis Pure-Go Syscall bersama dengan //go:build windows untuk memisahkannya. Biasakan untuk langsung menempelkan defer C.free tepat setelah alokasi CGo, dan aplikasi Anda tidak akan pernah lagi crash akibat kebocoran memori.
Wails menggunakan WebView yang sudah terinstal di OS secara bawaan. macOS menggunakan WebKit (Safari), sedangkan Windows menggunakan WebView2 (Chromium). Sebagai imbalan atas ukuran biner yang menyusut hingga sekitar 15MB, Anda harus menangani sendiri fragmentasi antar-mesin peramban ini.
Sebagai contoh, saat mengatur area drag pada jendela frameless tanpa title bar, WebView2 akan langsung bekerja hanya dengan memberikan --wails-draggable: drag. Namun, WebKit tidak akan menggerakkan jendela kecuali Anda juga menyebutkan -webkit-app-region: drag secara bersamaan.
Masalah juga sering terjadi pada penanganan event. Jika goroutine Go melemparkan runtime.EventsEmit ribuan kali per detik, single UI thread pada WebView akan kewalahan hingga layar menjadi freeze. Anda harus memasang buffer di backend untuk melakukan throttling event pada interval 60fps (sekitar 16ms). Kejadian buruk seperti pengguna menekan F5 di frontend hingga merusak state atau munculnya menu klik-kanan juga harus dicegah menggunakan global patch.
`typescript
// eventPatch.ts
export function applyGlobalUIFixes() {
window.addEventListener('contextmenu', (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (target.tagName !== 'INPUT' && target.tagName !== 'TEXTAREA') {
e.preventDefault();
}
});
window.addEventListener('keydown', (e: KeyboardEvent) => {
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const modifier = isMac ? e.metaKey : e.ctrlKey;
if (e.key === 'F5' || (modifier && e.key.toLowerCase() === 'r')) {
e.preventDefault();
e.stopPropagation();
}
});
}
`
Tuliskan properti drag untuk kedua mesin secara bersamaan di CSS, lalu jalankan applyGlobalUIFixes() di entry point aplikasi (main.ts atau App.tsx). Di sisi backend yang mempublikasikan event, pasang timer throttling. Perilaku aneh yang disebabkan oleh karakteristik WebView di tiap OS sebagian besar akan teratasi hanya dengan mekanisme seperti ini.
Aplikasi Wails biasanya menggunakan RAM sekitar 35MB hingga 50MB. Ini sangat hemat dibandingkan Electron yang bisa melahap lebih dari 200MB. Masalah baru muncul saat Anda mengirimkan file berukuran besar atau data biner ke frontend.
Jika Anda mengirim dan menerima data sebesar 50MB menggunakan binding JSON RPC bawaan, penggunaan RAM akan melonjak secara instan hingga lebih dari 180MB selama proses serialisasi JSON. Untuk menghindari fenomena ini, Anda harus mengimplementasikan custom HTTP streaming menggunakan opsi AssetServer.AssetsHandler. Dengan memindahkan data menggunakan metode Zero-copy tanpa penyalinan memori, penggunaan memori baik saat idle maupun beroperasi dapat ditahan di kisaran 22MB hingga 30MB.
Penanganan untuk lingkungan pengguna Windows juga diperlukan. Bagi klien yang belum memiliki WebView2 Runtime terinstal, tambahkan flag -webview2 download saat build agar bootstrapper ikut dikemas secara otomatis.
`yaml
name: Multiplatform Release Build
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-latest
platform: darwin/universal
output_name: OptimizedApp-macOS-Universal
- os: windows-latest
platform: windows/amd64
output_name: OptimizedApp-Windows-Installer
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- name: Build macOS Universal Binary
if: runner.os == 'macOS'
run: wails build -platform darwin/universal -clean
- name: Build Windows Installer
if: runner.os == 'Windows'
run: |
choco install nsis -y
wails build -platform windows/amd64 -nsis -webview2 download -clean
`
Di main.go, hubungkan http.Handler kustom ke AssetServer.AssetsHandler untuk mengatasi lonjakan memori besar. Kemudian, masukkan pipeline di atas ke dalam .github/workflows/release.yml. Setiap kali Anda mengunggah tag, biner universal macOS dan file penginstal Windows NSIS akan dibuat dan diunggah secara otomatis ke GitHub Releases.
Dengan mengelola memori di area low-level secara langsung dan menyerap perbedaan antar-mesin WebView melalui kode, Anda dapat membangun aplikasi desktop yang sangat solid menggunakan Wails.