mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
advanced bindings documentation (#4404)
* advanced bindings documentation * update changelog --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
parent
9f96439ba1
commit
e8765607ff
6 changed files with 3255 additions and 1403 deletions
1240
docs/package-lock.json
generated
1240
docs/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Added
|
||||
- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345)
|
||||
- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404)
|
||||
|
||||
## v3.0.0-alpha.10 - 2025-07-06
|
||||
|
||||
|
|
|
|||
342
docs/src/content/docs/learn/advanced-binding.mdx
Normal file
342
docs/src/content/docs/learn/advanced-binding.mdx
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
---
|
||||
title: Advanced Binding Techniques
|
||||
sidebar:
|
||||
order: 22
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
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:
|
||||
|
||||
```go
|
||||
//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:
|
||||
|
||||
```go
|
||||
//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:
|
||||
|
||||
```go
|
||||
//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:
|
||||
|
||||
```go
|
||||
//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:
|
||||
|
||||
```go
|
||||
//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:
|
||||
|
||||
```go
|
||||
//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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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`:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```typescript
|
||||
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 uses method names instead of IDs:
|
||||
|
||||
```javascript
|
||||
export function Greet(name) {
|
||||
let $resultPromise = $Call.ByName("Greet", name);
|
||||
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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
//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.
|
||||
115
docs/src/content/docs/learn/binding-best-practices.mdx
Normal file
115
docs/src/content/docs/learn/binding-best-practices.mdx
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: Binding Best Practices
|
||||
sidebar:
|
||||
order: 23
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide provides best practices and patterns for using the Wails binding system effectively in your applications.
|
||||
|
||||
## Service Design Patterns
|
||||
|
||||
### Service Organization
|
||||
|
||||
Organize your services based on functionality rather than technical concerns. For example, instead of having a single large service, split it into smaller, focused services:
|
||||
|
||||
```go
|
||||
// Instead of this:
|
||||
type AppService struct {}
|
||||
|
||||
func (s *AppService) GetUser() User { /* ... */ }
|
||||
func (s *AppService) UpdateUser(user User) error { /* ... */ }
|
||||
func (s *AppService) GetProducts() []Product { /* ... */ }
|
||||
func (s *AppService) AddProduct(product Product) error { /* ... */ }
|
||||
|
||||
// Do this:
|
||||
type UserService struct {}
|
||||
func (s *UserService) GetUser() User { /* ... */ }
|
||||
func (s *UserService) UpdateUser(user User) error { /* ... */ }
|
||||
|
||||
type ProductService struct {}
|
||||
func (s *ProductService) GetProducts() []Product { /* ... */ }
|
||||
func (s *ProductService) AddProduct(product Product) error { /* ... */ }
|
||||
```
|
||||
|
||||
This makes your code more maintainable and easier to understand.
|
||||
|
||||
### Use JSON Tags
|
||||
|
||||
Use JSON tags to control how your models are serialized:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"-"` // Exclude from JSON
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
### Separate Frontend and Backend Models
|
||||
|
||||
Consider using different models for the frontend and backend:
|
||||
|
||||
```go
|
||||
// Backend model
|
||||
type User struct {
|
||||
ID int
|
||||
Name string
|
||||
Email string
|
||||
Password string // Sensitive data
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Frontend model
|
||||
type UserDTO struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (s *UserService) GetUser() UserDTO {
|
||||
user := getUserFromDatabase()
|
||||
return UserDTO{
|
||||
ID: user.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
CreatedAt: user.CreatedAt,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This gives you more control over what data is exposed to the frontend.
|
||||
|
||||
### Use Context for Cancellation
|
||||
|
||||
Use context for cancellation to avoid wasting resources on abandoned requests:
|
||||
|
||||
```go
|
||||
func (s *ProductService) GetProducts(ctx context.Context, req ProductsRequest) (ProductsResponse, error) {
|
||||
// Check if the request has been cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ProductsResponse{}, ctx.Err()
|
||||
default:
|
||||
// Continue processing
|
||||
}
|
||||
|
||||
products, total, err := getProductsFromDatabase(ctx, req.Page, req.PageSize, req.Filter)
|
||||
if err != nil {
|
||||
return ProductsResponse{}, err
|
||||
}
|
||||
|
||||
return ProductsResponse{
|
||||
Products: products,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
155
docs/src/content/docs/learn/binding-system.mdx
Normal file
155
docs/src/content/docs/learn/binding-system.mdx
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
---
|
||||
title: Binding System Internals
|
||||
sidebar:
|
||||
order: 21
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The Wails binding system consists of three main components:
|
||||
|
||||
1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations
|
||||
2. **Configuration**: Manages settings and options for the binding generation process
|
||||
3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information
|
||||
|
||||
<FileTree>
|
||||
- internal/generator/
|
||||
- collect/ # Package analysis and information extraction
|
||||
- config/ # Configuration structures and interfaces
|
||||
- render/ # Code generation for JS/TS
|
||||
</FileTree>
|
||||
|
||||
## Collection Process
|
||||
|
||||
The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package.
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Collector**: Manages package information and caches collected data
|
||||
- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives
|
||||
- **Service**: Collects information about service types and their methods
|
||||
- **Model**: Collects detailed information about model types, including fields, values, and type parameters
|
||||
- **Directive**: Parses and interprets `//wails:` directives in Go source code
|
||||
|
||||
### Collection Flow
|
||||
|
||||
1. The collector scans the Go packages specified in the project
|
||||
2. It identifies service types (structs with methods that will be exposed to the frontend)
|
||||
3. For each service, it collects information about its methods
|
||||
4. It identifies model types (structs used as parameters or return values in service methods)
|
||||
5. For each model, it collects information about its fields and type parameters
|
||||
6. It processes any `//wails:` directives found in the code
|
||||
|
||||
## Rendering Process
|
||||
|
||||
The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package.
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Renderer**: Orchestrates the rendering of service, model, and index files
|
||||
- **Module**: Represents a single generated JavaScript/TypeScript module
|
||||
- **Templates**: Text templates used for code generation
|
||||
|
||||
### Rendering Flow
|
||||
|
||||
1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods
|
||||
2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct
|
||||
3. The renderer generates index files that re-export all services and models
|
||||
4. The renderer applies any custom code injections specified by `//wails:inject` directives
|
||||
|
||||
## Type Mapping
|
||||
|
||||
One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping:
|
||||
|
||||
| Go Type | JavaScript Type | TypeScript Type |
|
||||
|---------|----------------|----------------|
|
||||
| `bool` | `boolean` | `boolean` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` |
|
||||
| `string` | `string` | `string` |
|
||||
| `[]byte` | `Uint8Array` | `Uint8Array` |
|
||||
| `[]T` | `Array<T>` | `T[]` |
|
||||
| `map[K]V` | `Object` | `Record<K, V>` |
|
||||
| `struct` | `Object` | Custom class |
|
||||
| `interface{}` | `any` | `any` |
|
||||
| `*T` | `T \| null` | `T \| null` |
|
||||
| `func` | Not supported | Not supported |
|
||||
| `chan` | Not supported | Not supported |
|
||||
|
||||
## Directives System
|
||||
|
||||
The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code.
|
||||
|
||||
### Available Directives
|
||||
|
||||
- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings
|
||||
- `//wails:include`: Includes additional files with the generated bindings
|
||||
- `//wails:internal`: Marks a type or method as internal, preventing it from being exported to the frontend
|
||||
- `//wails:ignore`: Completely ignores a method during binding generation
|
||||
- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID
|
||||
|
||||
### Directive Processing
|
||||
|
||||
1. During the collection phase, the collector identifies and parses directives in the Go code
|
||||
2. The directives are stored with the corresponding declarations (services, methods, models, etc.)
|
||||
3. During the rendering phase, the renderer applies the directives to customize the generated code
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Conditional Code Generation
|
||||
|
||||
The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives:
|
||||
|
||||
```
|
||||
<language><style>:<content>
|
||||
```
|
||||
|
||||
Where:
|
||||
- `<language>` can be:
|
||||
- `*` - Both JavaScript and TypeScript
|
||||
- `j` - JavaScript only
|
||||
- `t` - TypeScript only
|
||||
|
||||
- `<style>` can be:
|
||||
- `*` - Both classes and interfaces
|
||||
- `c` - Classes only
|
||||
- `i` - Interfaces only
|
||||
|
||||
For example:
|
||||
```go
|
||||
//wails:inject j*:console.log("JavaScript only");
|
||||
//wails:inject t*:console.log("TypeScript only");
|
||||
```
|
||||
|
||||
### Custom Method IDs
|
||||
|
||||
By default, methods are identified by a hash-based ID. However, you can specify a custom ID using the `//wails:id` directive:
|
||||
|
||||
```go
|
||||
//wails:id 42
|
||||
func (s *Service) CustomIDMethod() {}
|
||||
```
|
||||
|
||||
This can be useful for maintaining compatibility when refactoring code.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
The binding generator is designed to be efficient, but there are a few things to keep in mind:
|
||||
|
||||
1. The first run will be slower as it builds up a cache of packages to scan
|
||||
2. Subsequent runs will be faster as they use the cached information
|
||||
3. The generator processes all packages in the project, which can be time-consuming for large projects
|
||||
4. You can use the `-clean` flag to clean the output directory before generation
|
||||
|
||||
## Debugging
|
||||
|
||||
If you encounter issues with the binding generation, you can use the `-v` flag to enable debug output:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -v
|
||||
```
|
||||
|
||||
This will provide detailed information about the collection and rendering process, which can help identify the source of the issue.
|
||||
|
|
@ -183,7 +183,6 @@ func main() {
|
|||
},
|
||||
})
|
||||
// ....
|
||||
app.NewWebviewWindow()
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -672,4 +671,4 @@ look for warnings from the binding generator to catch these.
|
|||
If you see a `ReferenceError` complaining about unknown methods,
|
||||
it could mean that your JS bindings have gotten out of sync with Go code
|
||||
and must be regenerated.
|
||||
:::
|
||||
:::
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue