Advanced Binding

This guide covers advanced techniques for customizing and optimizing the binding generation process in Wails v3.

Customizing Generated Code with Directives

Injecting Custom Code

The //wails:inject directive allows you to inject custom JavaScript/TypeScript code into the generated bindings:

//wails:inject console.log("Hello from Wails!");
type MyService struct {}

func (s *MyService) Greet(name string) string {
    return "Hello, " + name
}

This will inject the specified code into the generated JavaScript/TypeScript file for the MyService service.

You can also use conditional injection to target specific output formats:

//wails:inject j*:console.log("Hello JS!");  // JavaScript only
//wails:inject t*:console.log("Hello TS!");  // TypeScript only

Including Additional Files

The //wails:include directive allows you to include additional files with the generated bindings:

//wails:include js/*.js
package mypackage

This directive is typically used in package documentation comments to include additional JavaScript/TypeScript files with the generated bindings.

Marking Internal Types and Methods

The //wails:internal directive marks a type or method as internal, preventing it from being exported to the frontend:

//wails:internal
type InternalModel struct {
    Field string
}

//wails:internal
func (s *MyService) InternalMethod() {}

This is useful for types and methods that are only used internally by your Go code and should not be exposed to the frontend.

Ignoring Methods

The //wails:ignore directive completely ignores a method during binding generation:

//wails:ignore
func (s *MyService) IgnoredMethod() {}

This is similar to //wails:internal, but it completely ignores the method rather than marking it as internal.

Custom Method IDs

The //wails:id directive specifies a custom ID for a method, overriding the default hash-based ID:

//wails:id 42
func (s *MyService) CustomIDMethod() {}

This can be useful for maintaining compatibility when refactoring code.

Working with Complex Types

Nested Structs

The binding generator handles nested structs automatically:

type Address struct {
    Street string
    City   string
    State  string
    Zip    string
}

type Person struct {
    Name    string
    Address Address
}

func (s *MyService) GetPerson() Person {
    return Person{
        Name: "John Doe",
        Address: Address{
            Street: "123 Main St",
            City:   "Anytown",
            State:  "CA",
            Zip:    "12345",
        },
    }
}

The generated JavaScript/TypeScript code will include classes for both Person and Address.

Maps and Slices

Maps and slices are also handled automatically:

type Person struct {
    Name       string
    Attributes map[string]string
    Friends    []string
}

func (s *MyService) GetPerson() Person {
    return Person{
        Name: "John Doe",
        Attributes: map[string]string{
            "hair": "brown",
            "eyes": "blue",
        },
        Friends: []string{"Jane", "Bob", "Alice"},
    }
}

In JavaScript, maps are represented as objects and slices as arrays. In TypeScript, maps are represented as Record<K, V> and slices as T[].

Generic Types

The binding generator supports generic types:

type Result[T any] struct {
    Data  T
    Error string
}

func (s *MyService) GetResult() Result[string] {
    return Result[string]{
        Data:  "Hello, World!",
        Error: "",
    }
}

The generated TypeScript code will include a generic class for Result:

export class Result<T> {
    "Data": T;
    "Error": string;

    constructor(source: Partial<Result<T>> = {}) {
        if (!("Data" in source)) {
            this["Data"] = null as any;
        }
        if (!("Error" in source)) {
            this["Error"] = "";
        }

        Object.assign(this, source);
    }

    static createFrom<T>(source: string | object = {}): Result<T> {
        let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
        return new Result<T>(parsedSource as Partial<Result<T>>);
    }
}

Interfaces

The binding generator can generate TypeScript interfaces instead of classes using the -i flag:

bash
wails3 generate bindings -ts -i

This will generate TypeScript interfaces for all models:

export interface Person {
    Name: string;
    Attributes: Record<string, string>;
    Friends: string[];
}

Optimizing Binding Generation

Using Names Instead of IDs

By default, the binding generator uses hash-based IDs for method calls. You can use the -names flag to use names instead:

bash
wails3 generate bindings -names

This will generate code that calls into the bound method by its fully-qualified name (<package>.<Service>.<Method>) and uses positional $0, $1, … parameter names:

export function Greet($0) {
    let $resultPromise = $Call.ByName("main.GreetService.Greet", $0);
    return $resultPromise;
}

This can make the generated code more readable and easier to debug, but it may be slightly less efficient.

Bundling the Runtime

By default, the generated code imports the Wails runtime from the @wailsio/runtime npm package. You can use the -b flag to bundle the runtime with the generated code:

bash
wails3 generate bindings -b

This will include the runtime code directly in the generated files, eliminating the need for the npm package.

Disabling Index Files

If you don’t need the index files, you can use the -noindex flag to disable their generation:

bash
wails3 generate bindings -noindex

This can be useful if you prefer to import services and models directly from their respective files.

Real-World Examples

Authentication Service

Here’s an example of an authentication service with custom directives:

package auth

//wails:inject console.log("Auth service initialized");
type AuthService struct {
    // Private fields
    users map[string]User
}

type User struct {
    Username string
    Email    string
    Role     string
}

type LoginRequest struct {
    Username string
    Password string
}

type LoginResponse struct {
    Success bool
    User    User
    Token   string
    Error   string
}

// Login authenticates a user
func (s *AuthService) Login(req LoginRequest) LoginResponse {
    // Implementation...
}

// GetCurrentUser returns the current user
func (s *AuthService) GetCurrentUser() User {
    // Implementation...
}

// Internal helper method
//wails:internal
func (s *AuthService) validateCredentials(username, password string) bool {
    // Implementation...
}

Data Processing Service

Here’s an example of a data processing service with generic types:

package data

type ProcessingResult[T any] struct {
    Data  T
    Error string
}

type DataService struct {}

// Process processes data and returns a result
func (s *DataService) Process(data string) ProcessingResult[map[string]int] {
    // Implementation...
}

// ProcessBatch processes multiple data items
func (s *DataService) ProcessBatch(data []string) ProcessingResult[[]map[string]int] {
    // Implementation...
}

// Internal helper method
//wails:internal
func (s *DataService) parseData(data string) (map[string]int, error) {
    // Implementation...
}

Conditional Code Injection

Here’s an example of conditional code injection for different output formats:

//wails:inject j*:/**
//wails:inject j*: * @param {string} arg
//wails:inject j*: * @returns {Promise<void>}
//wails:inject j*: */
//wails:inject j*:export async function CustomMethod(arg) {
//wails:inject t*:export async function CustomMethod(arg: string): Promise<void> {
//wails:inject     await InternalMethod("Hello " + arg + "!");
//wails:inject }
type Service struct{}

This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language.

Edit page

Last updated: