Bermigrasi dari v2 ke v3
Panduan lengkap untuk memigrasikan aplikasi Wails v2 Anda ke v3
Wails v3 merupakan penulisan ulang secara menyeluruh dengan peningkatan signifikan pada arsitektur, performa, dan pengalaman developer. Panduan ini membantu Anda memigrasikan aplikasi v2 ke v3.
Perubahan utama:
- Struktur aplikasi baru
- Sistem binding yang ditingkatkan
- Pengelolaan jendela yang disempurnakan
- Sistem peristiwa yang lebih baik
- Konfigurasi yang disederhanakan
Waktu migrasi: 1-4 jam untuk aplikasi pada umumnya
Perubahan yang Tidak Kompatibel
Inisialisasi Aplikasi
Di v2, penyiapan aplikasi, konfigurasi jendela, dan eksekusi semuanya digabungkan dalam satu pemanggilan wails.Run(). Pendekatan monolitik ini menyulitkan pembuatan beberapa jendela, penanganan kesalahan pada tahap yang berbeda, atau pengujian setiap komponen aplikasi secara terpisah.
v3 memisahkan hal-hal tersebut menjadi beberapa fase tersendiri: pembuatan aplikasi, pembuatan jendela, dan eksekusi. Pemisahan ini memberi Anda kendali eksplisit atas setiap tahap siklus hidup aplikasi serta menjadikan kode lebih modular dan mudah diuji.
v2:
err := wails.Run(&options.App{
Title: "My App",
Width: 1024,
Height: 768,
Bind: []interface{}{
&GreetService{},
},
})v3:
app := application.New(application.Options{
Name: "My App",
Services: []application.Service{
application.NewService(&GreetService{}),
},
})
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "My App",
Width: 1024,
Height: 768,
})
app.Run()Mengapa ini lebih baik:
- Dukungan multi-jendela: Anda dapat membuat jendela secara dinamis kapan saja, tidak hanya saat aplikasi dimulai
- Penanganan kesalahan yang lebih baik: Setiap fase dapat divalidasi secara terpisah dengan penanganan kesalahan yang tepat
- Kode yang lebih jelas: Pemisahan ini memperjelas apa yang terjadi pada setiap tahap
- Lebih mudah diuji: Anda dapat menguji penyiapan aplikasi tanpa menjalankan event loop
- Lebih fleksibel: Jendela dapat dibuat, dihapus, dan dibuat ulang sepanjang siklus hidup aplikasi
Binding
Di v2, setiap struct yang di-binding memerlukan field konteks dan metode startup(ctx) untuk menerima konteks runtime. Hal ini menciptakan keterikatan yang erat antara logika bisnis dan runtime Wails sehingga kode lebih sulit diuji dan dipahami.
v3 memperkenalkan pola layanan, yang memungkinkan struct Anda sepenuhnya mandiri dan tidak perlu menyimpan konteks runtime. Jika suatu layanan memerlukan akses ke instans aplikasi, layanan tersebut menerimanya secara eksplisit melalui injeksi dependensi, bukan melalui penerusan konteks secara implisit.
v2:
type App struct {
ctx context.Context
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) Greet(name string) string {
return "Hello " + name
}v3:
type GreetService struct{}
func (g *GreetService) Greet(name string) string {
return "Hello " + name
}
// Register as service
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&GreetService{}),
},
})Mengapa ini lebih baik:
- Tanpa dependensi implisit: Layanan merupakan struct Go biasa tanpa dependensi runtime tersembunyi
- Pengujian lebih mudah: Anda dapat menguji metode layanan tanpa membuat mock konteks Wails
- Kode yang lebih jelas: Dependensi dinyatakan secara eksplisit (diteruskan sebagai argumen konstruktor), bukan disembunyikan dalam field konteks
- Pengorganisasian yang lebih baik: Layanan dapat dikelompokkan berdasarkan domain, bukan semuanya ditempatkan dalam satu struct
App - Inisialisasi yang tepat: Gunakan metode
ServiceStartup()saat Anda memerlukan inisialisasi agar proses tersebut dinyatakan secara eksplisit
Runtime
Di v2, semua operasi runtime mengharuskan konteks diteruskan ke fungsi global dari paket runtime. Hal ini menciptakan keterikatan yang erat dengan objek konteks di seluruh basis kode Anda dan membuat API terasa prosedural, bukan berorientasi objek.
v3 mengganti runtime berbasis konteks dengan pemanggilan metode langsung pada objek aplikasi dan jendela. Operasi dipanggil langsung pada objek yang dipengaruhinya sehingga kode menjadi lebih intuitif dan berorientasi objek.
v2:
import "github.com/wailsapp/wails/v2/pkg/runtime"
runtime.WindowSetTitle(a.ctx, "New Title")
runtime.EventsEmit(a.ctx, "event-name", data)v3:
// Store app reference
type MyService struct {
app *application.App
}
func (s *MyService) UpdateTitle() {
window := s.app.Window.Current()
window.SetTitle("New Title")
}
func (s *MyService) EmitEvent() {
s.app.Event.Emit("event-name", data)
}Mengapa ini lebih baik:
- Desain berorientasi objek: Metode dipanggil pada objek yang dipengaruhinya (jendela, aplikasi, menu, dan sebagainya)
- Maksud yang lebih jelas:
window.SetTitle()lebih mudah dipahami daripadaruntime.WindowSetTitle(ctx, ...) - Dukungan IDE yang lebih baik: Pelengkapan otomatis berfungsi dengan baik ketika metode berada pada objek
- Pengelolaan multi-jendela yang lebih jelas: Saat terdapat beberapa jendela, Anda secara eksplisit memilih jendela yang akan dikenai operasi
- Tanpa penerusan konteks: Anda tidak perlu meneruskan konteks melalui setiap fungsi
Binding Frontend
Di v2, binding disusun berdasarkan paket Go dan nama struct, yang biasanya menghasilkan path seperti wailsjs/go/main/App. Struktur ini tidak mencerminkan pengelompokan logis dan menyulitkan pencarian fungsionalitas yang saling terkait.
v3 menyusun binding berdasarkan nama layanan dan modul aplikasi sehingga menghasilkan struktur logis yang lebih jelas. Binding dibuat dalam direktori bindings yang disusun berdasarkan nama aplikasi dan nama layanan Anda, sehingga fungsionalitas yang tersedia lebih mudah dipahami.
v2:
import { Greet } from '../wailsjs/go/main/App'
const result = await Greet("World")v3:
import { Greet } from './bindings/changeme/greetservice'
const result = await Greet("World")Mengapa ini lebih baik:
- Pengorganisasian logis: Binding dikelompokkan berdasarkan nama layanan, bukan struktur paket Go
- Impor yang lebih jelas: Path mencerminkan logika domain (greetservice), bukan struktur file (main/App)
- Lebih mudah ditemukan: Anda dapat menelusuri binding berdasarkan fitur, bukan berdasarkan struktur teknis
- Penamaan yang konsisten: Pengorganisasian berbasis layanan selaras dengan arsitektur backend Anda
- Path yang lebih sederhana: Tidak ada lagi prefiks
../wailsjs/go—cukup./bindings
Event
Di v2, event menggunakan parameter variadik interface{} dan mengharuskan konteks diteruskan ke setiap fungsi event. Handler event menerima data tanpa tipe yang memerlukan assertion tipe secara manual sehingga sistem event rentan terhadap kesalahan dan sulit di-debug.
v3 memperkenalkan objek event bertipe dan menghapus keharusan penggunaan konteks. Handler event menerima objek event yang tepat dengan data bertipe sehingga sistem event lebih andal dan lebih mudah digunakan.
v2:
runtime.EventsOn(ctx, "event-name", func(data ...interface{}) {
// Handle event
})
runtime.EventsEmit(ctx, "event-name", data)v3:
app.Event.On("event-name", func(e *application.CustomEvent) {
data := e.Data
// Handle event
})
app.Event.Emit("event-name", data)Mengapa ini lebih baik:
- Keamanan tipe: Event menggunakan objek event yang tepat, bukan
...interface{} - Debugging yang lebih mudah: Objek event berisi metadata seperti nama event sehingga debugging menjadi lebih mudah
- API yang lebih jelas:
app.Event.On()danapp.Event.Emit()lebih intuitif daripada fungsi runtime - Tidak memerlukan konteks: Event bekerja langsung pada objek aplikasi tanpa meneruskan konteks
- Handler yang lebih sederhana: Handler event memiliki signature yang jelas, bukan parameter variadik
Jendela
v2 hanya mendukung satu jendela per aplikasi. Jendela dibuat saat aplikasi dimulai dan semua operasi jendela dilakukan melalui fungsi runtime yang secara implisit menargetkan satu-satunya jendela tersebut.
v3 memperkenalkan dukungan multi-jendela native sebagai fitur inti. Setiap jendela merupakan objek mandiri dengan metode dan siklus hidupnya sendiri. Anda dapat membuat, mengelola, dan memusnahkan beberapa jendela secara dinamis sepanjang masa aktif aplikasi.
v2:
// Single window only
runtime.WindowSetSize(ctx, 800, 600)v3:
// Multiple windows supported
window1 := app.Window.New()
window1.SetSize(800, 600)
window2 := app.Window.New()
window2.SetSize(1024, 768)Mengapa ini lebih baik:
- Aplikasi multi-jendela: Buat aplikasi dengan beberapa jendela independen (dasbor, preferensi, alat, dan sebagainya)
- Referensi jendela yang eksplisit: Setiap jendela adalah objek yang dapat Anda simpan dan manipulasi secara langsung
- Pembuatan jendela dinamis: Buat dan musnahkan jendela kapan saja selama runtime
- Status jendela independen: Setiap jendela memiliki event, properti, dan siklus hidupnya sendiri
- Arsitektur yang lebih baik: Pengelolaan jendela berorientasi objek, bukan berbasis konteks
Langkah Migrasi
Langkah 1: Perbarui Dependensi
go.mod:
module myapp
go 1.25.0
require (
github.com/wailsapp/wails/v3 v3.0.0-beta.0
)Perbarui:
go get github.com/wailsapp/wails/v3@latest
go mod tidy
Langkah 2: Perbarui main.go
v2:
package main
import (
"embed"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "My App",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: assets,
},
Bind: []interface{}{
app,
},
Windows: &windows.Options{
WebviewIsTransparent: false,
},
})
if err != nil {
println("Error:", err.Error())
}
}v3:
package main
import (
"embed"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed frontend/dist
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "My App",
Services: []application.Service{
application.NewService(&MyService{}),
},
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
})
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "My App",
Width: 1024,
Height: 768,
})
err := app.Run()
if err != nil {
panic(err)
}
}Langkah 3: Ubah Struct App Menjadi Layanan
v2:
type App struct {
ctx context.Context
}
func NewApp() *App {
return &App{}
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
// Initialisation
}
func (a *App) Greet(name string) string {
return "Hello " + name
}v3:
type MyService struct {
app *application.App
}
func NewMyService(app *application.App) *MyService {
return &MyService{app: app}
}
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Initialisation
return nil
}
func (s *MyService) Greet(name string) string {
return "Hello " + name
}
// Register after app creation
app := application.New(application.Options{})
app.RegisterService(application.NewService(NewMyService(app)))Langkah 4: Perbarui Pemanggilan Runtime
v2:
func (a *App) DoSomething() {
runtime.WindowSetTitle(a.ctx, "New Title")
runtime.EventsEmit(a.ctx, "update", data)
runtime.LogInfo(a.ctx, "Message")
}v3:
func (s *MyService) DoSomething() {
window := s.app.Window.Current()
window.SetTitle("New Title")
s.app.Event.Emit("update", data)
s.app.Logger.Info("Message")
}Langkah 5: Perbarui Frontend
Buat binding baru:
wails3 generate bindings
Perbarui impor:
// v2
import { Greet } from '../wailsjs/go/main/App'
// v3
import { Greet } from './bindings/changeme/myservice'Perbarui penanganan event:
// v2
import { EventsOn, EventsEmit } from '../wailsjs/runtime/runtime'
EventsOn("update", (data) => {
console.log(data)
})
EventsEmit("action", data)
// v3
import { Events } from '@wailsio/runtime'
Events.On("update", (data) => {
console.log(data)
})
Events.Emit("action", data)Langkah 6: Perbarui Konfigurasi
v2 (wails.json):
{
"name": "myapp",
"outputfilename": "myapp",
"frontend:install": "npm install",
"frontend:build": "npm run build",
"frontend:dev:watcher": "npm run dev",
"frontend:dev:serverUrl": "auto"
}v3 (wails.json):
{
"name": "myapp",
"frontend": {
"dir": "./frontend",
"install": "npm install",
"build": "npm run build",
"dev": "npm run dev",
"devServerUrl": "http://localhost:5173"
}
}Pemetaan Fitur
Dialog
v2:
selection, err := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{
Title: "Select File",
})v3:
selection, err := app.Dialog.OpenFileWithOptions(&application.OpenFileDialogOptions{
Title: "Select File",
}).PromptForSingleSelection()Menu
v2:
menu := menu.NewMenu()
menu.Append(menu.Text("File", nil, []*menu.MenuItem{
menu.Text("Quit", nil, func(_ *menu.CallbackData) {
runtime.Quit(ctx)
}),
}))v3:
menu := app.NewMenu()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})Baki Sistem
v2:
// Not available in v2v3:
systray := app.SystemTray.New()
systray.SetIcon(iconBytes)
systray.SetLabel("My App")
menu := app.NewMenu()
menu.Add("Show").OnClick(showWindow)
menu.Add("Quit").OnClick(app.Quit)
systray.SetMenu(menu)Masalah Umum
Masalah: Binding tidak ditemukan
Masalah: Kesalahan impor setelah migrasi
Solusi:
# Regenerate bindings
wails3 generate bindings
# Check output directory
ls frontend/bindings
Masalah: Kesalahan konteks
Masalah: ctx tidak tersedia
Solusi:
Simpan referensi aplikasi sebagai gantinya:
type MyService struct {
app *application.App
}
func NewMyService(app *application.App) *MyService {
return &MyService{app: app}
}Masalah: Metode jendela tidak berfungsi
Masalah: runtime.WindowSetTitle() tidak tersedia
Solusi:
Gunakan metode jendela secara langsung:
window := s.app.Window.Current()
window.SetTitle("New Title")Masalah: Peristiwa tidak dipicu
Masalah: Peristiwa telah didaftarkan, tetapi tidak diterima
Solusi:
Pastikan nama peristiwa sama persis:
// Go
app.Event.Emit("my-event", data)
// JavaScript
OnEvent("my-event", handler) // Must match exactlyMenguji Migrasi
Daftar Periksa
- Aplikasi dimulai tanpa kesalahan
- Semua binding berfungsi
- Peristiwa dikirim dan diterima
- Jendela terbuka dan tertutup dengan benar
- Menu berfungsi (jika berlaku)
- Dialog berfungsi (jika berlaku)
- Baki sistem berfungsi (jika berlaku)
- Proses build berfungsi
- Build produksi berfungsi
Perintah Pengujian
# Development
wails3 dev
# Build
wails3 build
# Generate bindings
wails3 generate bindings
Manfaat v3
Performa
- Startup lebih cepat - Inisialisasi yang dioptimalkan
- Penggunaan memori lebih rendah - Penggunaan sumber daya yang efisien
- Bridge yang lebih baik - Overhead panggilan sebesar <1ms
Fitur
- Multi-jendela - Dukungan native
- Baki sistem - Bawaan
- Peristiwa yang lebih baik - API bertipe yang lebih sederhana
- Layanan - Pengorganisasian kode yang lebih baik
Pengalaman Developer
- Keamanan tipe - Dukungan TypeScript penuh
- Kesalahan yang lebih baik - Pesan kesalahan yang jelas
- Hot reload - Pengembangan lebih cepat
- Dokumentasi yang lebih baik - Panduan lengkap
Mendapatkan Bantuan
Sumber Daya
Pertanyaan Umum
T: Dapatkah saya menjalankan v2 dan v3 secara berdampingan? J: Ya, keduanya menggunakan jalur impor yang berbeda.
T: Apakah v3 siap digunakan dalam produksi? J: v3 merupakan perangkat lunak beta dengan API desktop yang stabil. Sudah ada aplikasi yang menggunakannya di lingkungan produksi, tetapi lakukan pengujian menyeluruh sebelum deployment. v2 tetap merupakan riliis stabil saat ini.
T: Apakah v2 akan tetap dipelihara? J: Ya, v2 akan menerima pembaruan kritis.
T: Berapa lama waktu yang diperlukan untuk migrasi? J: 1-4 jam untuk aplikasi pada umumnya.
Langkah Berikutnya
Mulai gunakan Wails v3.
Pahami arsitektur v3.
Pelajari sistem binding yang baru.
Lihat contoh lengkap v3.
Ada pertanyaan? Tanyakan di Discord atau buat issue.