727 lines
17 KiB
Text
727 lines
17 KiB
Text
---
|
|
title: Migrating from v2 to v3
|
|
description: Complete guide to migrating your Wails v2 application to v3
|
|
sidebar:
|
|
order: 1
|
|
---
|
|
|
|
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
|
|
|
|
Wails v3 is a **complete rewrite** with significant improvements in architecture, performance, and developer experience. This guide helps you migrate your v2 application to v3.
|
|
|
|
**Key changes:**
|
|
- New application structure
|
|
- Improved bindings system
|
|
- Enhanced window management
|
|
- Better event system
|
|
- Simplified configuration
|
|
|
|
**Migration time:** 1-4 hours for typical applications
|
|
|
|
## Breaking Changes
|
|
|
|
### Application Initialisation
|
|
|
|
In v2, application setup, window configuration, and execution were all combined into a single `wails.Run()` call. This monolithic approach made it difficult to create multiple windows, handle errors at different stages, or test individual components of your application.
|
|
|
|
v3 separates these concerns into distinct phases: application creation, window creation, and execution. This separation gives you explicit control over each stage of your application's lifecycle and makes the code more modular and testable.
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
err := wails.Run(&options.App{
|
|
Title: "My App",
|
|
Width: 1024,
|
|
Height: 768,
|
|
Bind: []interface{}{
|
|
&GreetService{},
|
|
},
|
|
})
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
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()
|
|
```
|
|
|
|
**Why this is better:**
|
|
|
|
- **Multi-window support**: You can create windows dynamically at any point, not just at startup
|
|
- **Better error handling**: Each phase can be validated separately with proper error handling
|
|
- **Clearer code**: The separation makes it obvious what's happening at each stage
|
|
- **More testable**: You can test application setup without running the event loop
|
|
- **More flexible**: Windows can be created, destroyed, and recreated throughout the application lifecycle
|
|
|
|
### Bindings
|
|
|
|
In v2, every bound struct required a context field and a `startup(ctx)` method to receive the runtime context. This created tight coupling between your business logic and the Wails runtime, making code harder to test and understand.
|
|
|
|
v3 introduces the service pattern, where your structs are completely standalone and don't need to store runtime context. If a service needs access to the application instance, it explicitly receives it through dependency injection rather than implicit context threading.
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
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:**
|
|
|
|
```go
|
|
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{}),
|
|
},
|
|
})
|
|
```
|
|
|
|
**Why this is better:**
|
|
|
|
- **No implicit dependencies**: Services are plain Go structs without hidden runtime dependencies
|
|
- **Easier testing**: You can test service methods without mocking a Wails context
|
|
- **Clearer code**: Dependencies are explicit (passed as constructor arguments) rather than hidden in a context field
|
|
- **Better organization**: Services can be grouped by domain rather than all living in a single `App` struct
|
|
- **Proper initialization**: Use `ServiceStartup()` method when you need initialization, making it explicit
|
|
|
|
### Runtime
|
|
|
|
In v2, all runtime operations required passing a context to global functions from the `runtime` package. This created tight coupling to the context object throughout your codebase and made the API feel procedural rather than object-oriented.
|
|
|
|
v3 replaces the context-based runtime with direct method calls on application and window objects. Operations are called directly on the objects they affect, making the code more intuitive and object-oriented.
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
import "github.com/wailsapp/wails/v2/pkg/runtime"
|
|
|
|
runtime.WindowSetTitle(a.ctx, "New Title")
|
|
runtime.EventsEmit(a.ctx, "event-name", data)
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
// Store app reference
|
|
type MyService struct {
|
|
app *application.Application
|
|
}
|
|
|
|
func (s *MyService) UpdateTitle() {
|
|
window := s.app.Window.Current()
|
|
window.SetTitle("New Title")
|
|
}
|
|
|
|
func (s *MyService) EmitEvent() {
|
|
s.app.Event.Emit("event-name", data)
|
|
}
|
|
```
|
|
|
|
**Why this is better:**
|
|
|
|
- **Object-oriented design**: Methods are called on the objects they affect (window, app, menu, etc.)
|
|
- **Clearer intent**: `window.SetTitle()` is more obvious than `runtime.WindowSetTitle(ctx, ...)`
|
|
- **Better IDE support**: Autocomplete works properly when methods are on objects
|
|
- **Multi-window clarity**: With multiple windows, you explicitly choose which window to operate on
|
|
- **No context threading**: You don't need to pass context through every function
|
|
|
|
### Frontend Bindings
|
|
|
|
In v2, bindings were organized by Go package and struct name, typically resulting in paths like `wailsjs/go/main/App`. This structure didn't reflect logical grouping and made it hard to find related functionality.
|
|
|
|
v3 organizes bindings by service name and application module, creating a clearer logical structure. The bindings are generated into a `bindings` directory organized by your application name and service names, making it easier to understand what functionality is available.
|
|
|
|
**v2:**
|
|
|
|
```javascript
|
|
import { Greet } from '../wailsjs/go/main/App'
|
|
|
|
const result = await Greet("World")
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```javascript
|
|
import { Greet } from './bindings/myapp/greetservice'
|
|
|
|
const result = await Greet("World")
|
|
```
|
|
|
|
**Why this is better:**
|
|
|
|
- **Logical organization**: Bindings are grouped by service name rather than Go package structure
|
|
- **Clearer imports**: The path reflects the domain logic (greetservice) not the file structure (main/App)
|
|
- **Better discoverability**: You can navigate bindings by feature rather than by technical structure
|
|
- **Consistent naming**: Service-based organization matches your backend architecture
|
|
- **Simpler paths**: No more `../wailsjs/go` prefix - just `./bindings`
|
|
|
|
### Events
|
|
|
|
In v2, events used variadic `interface{}` parameters and required passing context to every event function. Event handlers received untyped data that needed manual type assertions, making the event system error-prone and hard to debug.
|
|
|
|
v3 introduces typed event objects and removes the context requirement. Event handlers receive a proper event object with typed data, making the event system more reliable and easier to use.
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
runtime.EventsOn(ctx, "event-name", func(data ...interface{}) {
|
|
// Handle event
|
|
})
|
|
|
|
runtime.EventsEmit(ctx, "event-name", data)
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
app.Event.On("event-name", func(e *application.CustomEvent) {
|
|
data := e.Data
|
|
// Handle event
|
|
})
|
|
|
|
app.Event.Emit("event-name", data)
|
|
```
|
|
|
|
**Why this is better:**
|
|
|
|
- **Type safety**: Events use proper event objects instead of `...interface{}`
|
|
- **Better debugging**: Event objects contain metadata like event name, making debugging easier
|
|
- **Clearer API**: `app.Event.On()` and `app.Event.Emit()` are more intuitive than runtime functions
|
|
- **No context needed**: Events work directly on the app object without threading context
|
|
- **Simpler handlers**: Event handlers have a clear signature instead of variadic parameters
|
|
|
|
### Windows
|
|
|
|
v2 supported only a single window per application. The window was created at startup and all window operations were performed through runtime functions that implicitly targeted that single window.
|
|
|
|
v3 introduces native multi-window support as a core feature. Each window is a first-class object with its own methods and lifecycle. You can create, manage, and destroy multiple windows dynamically throughout your application's lifetime.
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
// Single window only
|
|
runtime.WindowSetSize(ctx, 800, 600)
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
// Multiple windows supported
|
|
window1 := app.Window.New()
|
|
window1.SetSize(800, 600)
|
|
|
|
window2 := app.Window.New()
|
|
window2.SetSize(1024, 768)
|
|
```
|
|
|
|
**Why this is better:**
|
|
|
|
- **Multi-window applications**: Build apps with multiple independent windows (dashboards, preferences, tools, etc.)
|
|
- **Explicit window references**: Each window is an object you can store and manipulate directly
|
|
- **Dynamic window creation**: Create and destroy windows at any time during runtime
|
|
- **Independent window state**: Each window has its own events, properties, and lifecycle
|
|
- **Better architecture**: Window management is object-oriented rather than context-based
|
|
|
|
## Migration Steps
|
|
|
|
### Step 1: Update Dependencies
|
|
|
|
**go.mod:**
|
|
|
|
```go
|
|
module myapp
|
|
|
|
go 1.21
|
|
|
|
require (
|
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.1
|
|
)
|
|
```
|
|
|
|
**Update:**
|
|
|
|
```bash
|
|
go get github.com/wailsapp/wails/v3@latest
|
|
go mod tidy
|
|
```
|
|
|
|
### Step 2: Update main.go
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
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:**
|
|
|
|
```go
|
|
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)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 3: Convert App Struct to Service
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
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:**
|
|
|
|
```go
|
|
type MyService struct {
|
|
app *application.Application
|
|
}
|
|
|
|
func NewMyService(app *application.Application) *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)))
|
|
```
|
|
|
|
### Step 4: Update Runtime Calls
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
func (a *App) DoSomething() {
|
|
runtime.WindowSetTitle(a.ctx, "New Title")
|
|
runtime.EventsEmit(a.ctx, "update", data)
|
|
runtime.LogInfo(a.ctx, "Message")
|
|
}
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
func (s *MyService) DoSomething() {
|
|
window := s.app.Window.Current()
|
|
window.SetTitle("New Title")
|
|
|
|
s.app.Event.Emit("update", data)
|
|
|
|
s.app.Logger.Info("Message")
|
|
}
|
|
```
|
|
|
|
### Step 5: Update Frontend
|
|
|
|
**Generate new bindings:**
|
|
|
|
```bash
|
|
wails3 generate bindings
|
|
```
|
|
|
|
**Update imports:**
|
|
|
|
```javascript
|
|
// v2
|
|
import { Greet } from '../wailsjs/go/main/App'
|
|
|
|
// v3
|
|
import { Greet } from './bindings/myapp/myservice'
|
|
```
|
|
|
|
**Update event handling:**
|
|
|
|
```javascript
|
|
// 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)
|
|
```
|
|
|
|
### Step 6: Update Configuration
|
|
|
|
**v2 (wails.json):**
|
|
|
|
```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):**
|
|
|
|
```json
|
|
{
|
|
"name": "myapp",
|
|
"frontend": {
|
|
"dir": "./frontend",
|
|
"install": "npm install",
|
|
"build": "npm run build",
|
|
"dev": "npm run dev",
|
|
"devServerUrl": "http://localhost:5173"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Feature Mapping
|
|
|
|
### dialogs
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
selection, err := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{
|
|
Title: "Select File",
|
|
})
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
selection, err := app.Dialog.OpenFile(application.OpenFileDialogOptions{
|
|
Title: "Select File",
|
|
})
|
|
```
|
|
|
|
### Menus
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
menu := menu.NewMenu()
|
|
menu.Append(menu.Text("File", nil, []*menu.MenuItem{
|
|
menu.Text("Quit", nil, func(_ *menu.CallbackData) {
|
|
runtime.Quit(ctx)
|
|
}),
|
|
}))
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
menu := app.NewMenu()
|
|
fileMenu := menu.AddSubmenu("File")
|
|
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
|
app.Quit()
|
|
})
|
|
```
|
|
|
|
### System Tray
|
|
|
|
**v2:**
|
|
|
|
```go
|
|
// Not available in v2
|
|
```
|
|
|
|
**v3:**
|
|
|
|
```go
|
|
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)
|
|
```
|
|
|
|
## Common Issues
|
|
|
|
### Issue: Bindings not found
|
|
|
|
**Problem:** Import errors after migration
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# Regenerate bindings
|
|
wails3 generate bindings
|
|
|
|
# Check output directory
|
|
ls frontend/bindings
|
|
```
|
|
|
|
### Issue: Context errors
|
|
|
|
**Problem:** `ctx` not available
|
|
|
|
**Solution:**
|
|
|
|
Store app reference instead:
|
|
|
|
```go
|
|
type MyService struct {
|
|
app *application.Application
|
|
}
|
|
|
|
func NewMyService(app *application.Application) *MyService {
|
|
return &MyService{app: app}
|
|
}
|
|
```
|
|
|
|
### Issue: Window methods not working
|
|
|
|
**Problem:** `runtime.WindowSetTitle()` doesn't exist
|
|
|
|
**Solution:**
|
|
|
|
Use window methods directly:
|
|
|
|
```go
|
|
window := s.app.Window.Current()
|
|
window.SetTitle("New Title")
|
|
```
|
|
|
|
### Issue: Events not firing
|
|
|
|
**Problem:** Events registered but not received
|
|
|
|
**Solution:**
|
|
|
|
Check event names match exactly:
|
|
|
|
```go
|
|
// Go
|
|
app.Event.Emit("my-event", data)
|
|
|
|
// JavaScript
|
|
OnEvent("my-event", handler) // Must match exactly
|
|
```
|
|
|
|
## Testing Migration
|
|
|
|
### Checklist
|
|
|
|
- [ ] Application starts without errors
|
|
- [ ] All bindings work
|
|
- [ ] Events are sent and received
|
|
- [ ] Windows open and close correctly
|
|
- [ ] Menus work (if applicable)
|
|
- [ ] dialogs work (if applicable)
|
|
- [ ] System tray works (if applicable)
|
|
- [ ] Build process works
|
|
- [ ] Production build works
|
|
|
|
### Test Commands
|
|
|
|
```bash
|
|
# Development
|
|
wails3 dev
|
|
|
|
# Build
|
|
wails3 build
|
|
|
|
# Generate bindings
|
|
wails3 generate bindings
|
|
```
|
|
|
|
## Benefits of v3
|
|
|
|
### Performance
|
|
|
|
- **Faster startup** - Optimised initialisation
|
|
- **Lower memory** - Efficient resource usage
|
|
- **Better bridge** - <1ms call overhead
|
|
|
|
### Features
|
|
|
|
- **Multi-window** - Native support
|
|
- **System tray** - Built-in
|
|
- **Better events** - Typed, simpler API
|
|
- **Services** - Better code organisation
|
|
|
|
### Developer Experience
|
|
|
|
- **Type safety** - Full TypeScript support
|
|
- **Better errors** - Clear error messages
|
|
- **Hot reload** - Faster development
|
|
- **Better docs** - Comprehensive guides
|
|
|
|
## Getting Help
|
|
|
|
### Resources
|
|
|
|
- [Documentation](/quick-start/why-wails)
|
|
- [Discord Community](https://discord.gg/JDdSxwjhGf)
|
|
- [GitHub Issues](https://github.com/wailsapp/wails/issues)
|
|
- [Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples)
|
|
|
|
### Common Questions
|
|
|
|
**Q: Can I run v2 and v3 side by side?**
|
|
A: Yes, they use different import paths.
|
|
|
|
**Q: Is v3 production-ready?**
|
|
A: v3 is in alpha/beta. Test thoroughly before production.
|
|
|
|
**Q: Will v2 be maintained?**
|
|
A: Yes, v2 will receive critical updates.
|
|
|
|
**Q: How long does migration take?**
|
|
A: 1-4 hours for typical applications.
|
|
|
|
## Next Steps
|
|
|
|
<CardGrid>
|
|
<Card title="Quick Start" icon="rocket">
|
|
Get started with Wails v3.
|
|
|
|
[Learn More →](/quick-start/installation)
|
|
</Card>
|
|
|
|
<Card title="Core Concepts" icon="star">
|
|
Understand v3 architecture.
|
|
|
|
[Learn More →](/concepts/architecture)
|
|
</Card>
|
|
|
|
<Card title="Bindings" icon="puzzle">
|
|
Learn the new bindings system.
|
|
|
|
[Learn More →](/features/bindings/methods)
|
|
</Card>
|
|
|
|
<Card title="Examples" icon="open-book">
|
|
See complete v3 examples.
|
|
|
|
[View Examples →](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples)
|
|
</Card>
|
|
</CardGrid>
|
|
|
|
---
|
|
|
|
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open an issue](https://github.com/wailsapp/wails/issues).
|