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 onlyIncluding Additional Files
The //wails:include directive allows you to include additional files with the generated bindings:
//wails:include js/*.js
package mypackageThis 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:
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:
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:
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:
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.