mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
docs: document automatic enum generation in binding generator (#4973)
* docs: document automatic enum generation in binding generator (#4972) Add dedicated Enums page covering string/integer/type-alias enums, $zero values, struct field typing, imported package enums, supported types, and limitations. Fix inaccurate enum section in Data Models page and add both Data Models and Enums to sidebar navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add enum generation changelog entry (#4972) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: address review feedback on enum documentation - Add bool → false to $zero value table - Fix title.String() → string(title) in imported package example - Clarify $zero defaults apply to class output, not interfaces - Improve iota limitation wording for clarity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e01d0feb22
commit
bbc4e532c4
4 changed files with 545 additions and 18 deletions
|
|
@ -138,6 +138,8 @@ export default defineConfig({
|
|||
items: [
|
||||
{ label: "Method Binding", link: "/features/bindings/methods" },
|
||||
{ label: "Services", link: "/features/bindings/services" },
|
||||
{ label: "Data Models", link: "/features/bindings/models" },
|
||||
{ label: "Enums", link: "/features/bindings/enums" },
|
||||
{ label: "Advanced Binding", link: "/features/bindings/advanced" },
|
||||
{ label: "Best Practices", link: "/features/bindings/best-practices" },
|
||||
],
|
||||
|
|
|
|||
529
docs/src/content/docs/features/bindings/enums.mdx
Normal file
529
docs/src/content/docs/features/bindings/enums.mdx
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
---
|
||||
title: Enums
|
||||
description: Automatic enum generation from Go constants
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Enum Bindings
|
||||
|
||||
The Wails v3 binding generator **automatically detects Go constant types and generates TypeScript enums or JavaScript const objects**. No registration, no configuration — just define your types and constants in Go, and the generator handles the rest.
|
||||
|
||||
:::note
|
||||
Unlike Wails v2, there is **no need to call `EnumBind`** or register enums manually. The generator discovers them automatically from your source code.
|
||||
:::
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Define a named type with constants in Go:**
|
||||
|
||||
```go
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusActive Status = "active"
|
||||
StatusPending Status = "pending"
|
||||
StatusClosed Status = "closed"
|
||||
)
|
||||
```
|
||||
|
||||
**Use the type in a struct or service method:**
|
||||
|
||||
```go
|
||||
type Ticket struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generate bindings:**
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
The generator output will report enum counts alongside models:
|
||||
|
||||
```
|
||||
3 Enums, 5 Models
|
||||
```
|
||||
|
||||
**Use in your frontend:**
|
||||
|
||||
```javascript
|
||||
import { Ticket, Status } from './bindings/myapp/models'
|
||||
|
||||
const ticket = new Ticket({
|
||||
id: 1,
|
||||
title: "Bug report",
|
||||
status: Status.StatusActive
|
||||
})
|
||||
```
|
||||
|
||||
**That's it!** The enum type is enforced in both Go and JavaScript/TypeScript.
|
||||
|
||||
## Defining Enums
|
||||
|
||||
An enum in Wails is a **named type** with an underlying basic type, combined with **const declarations** of that type.
|
||||
|
||||
### String Enums
|
||||
|
||||
```go
|
||||
// Title is a title
|
||||
type Title string
|
||||
|
||||
const (
|
||||
// Mister is a title
|
||||
Mister Title = "Mr"
|
||||
Miss Title = "Miss"
|
||||
Ms Title = "Ms"
|
||||
Mrs Title = "Mrs"
|
||||
Dr Title = "Dr"
|
||||
)
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Title is a title
|
||||
*/
|
||||
export enum Title {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Mister is a title
|
||||
*/
|
||||
Mister = "Mr",
|
||||
Miss = "Miss",
|
||||
Ms = "Ms",
|
||||
Mrs = "Mrs",
|
||||
Dr = "Dr",
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Title is a title
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const Title = {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero: "",
|
||||
|
||||
/**
|
||||
* Mister is a title
|
||||
*/
|
||||
Mister: "Mr",
|
||||
Miss: "Miss",
|
||||
Ms: "Ms",
|
||||
Mrs: "Mrs",
|
||||
Dr: "Dr",
|
||||
};
|
||||
```
|
||||
|
||||
### Integer Enums
|
||||
|
||||
```go
|
||||
type Priority int
|
||||
|
||||
const (
|
||||
PriorityLow Priority = 0
|
||||
PriorityMedium Priority = 1
|
||||
PriorityHigh Priority = 2
|
||||
)
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
export enum Priority {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = 0,
|
||||
|
||||
PriorityLow = 0,
|
||||
PriorityMedium = 1,
|
||||
PriorityHigh = 2,
|
||||
}
|
||||
```
|
||||
|
||||
### Type Alias Enums
|
||||
|
||||
Go type aliases (`=`) also work, but generate a slightly different output — a type definition plus a const object, rather than a native TypeScript `enum`:
|
||||
|
||||
```go
|
||||
// Age is an integer with some predefined values
|
||||
type Age = int
|
||||
|
||||
const (
|
||||
NewBorn Age = 0
|
||||
Teenager Age = 12
|
||||
YoungAdult Age = 18
|
||||
|
||||
// Oh no, some grey hair!
|
||||
MiddleAged Age = 50
|
||||
Mathusalem Age = 1000 // Unbelievable!
|
||||
)
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Age is an integer with some predefined values
|
||||
*/
|
||||
export type Age = number;
|
||||
|
||||
/**
|
||||
* Predefined constants for type Age.
|
||||
* @namespace
|
||||
*/
|
||||
export const Age = {
|
||||
NewBorn: 0,
|
||||
Teenager: 12,
|
||||
YoungAdult: 18,
|
||||
|
||||
/**
|
||||
* Oh no, some grey hair!
|
||||
*/
|
||||
MiddleAged: 50,
|
||||
|
||||
/**
|
||||
* Unbelievable!
|
||||
*/
|
||||
Mathusalem: 1000,
|
||||
};
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Age is an integer with some predefined values
|
||||
* @typedef {number} Age
|
||||
*/
|
||||
|
||||
/**
|
||||
* Predefined constants for type Age.
|
||||
* @namespace
|
||||
*/
|
||||
export const Age = {
|
||||
NewBorn: 0,
|
||||
Teenager: 12,
|
||||
YoungAdult: 18,
|
||||
|
||||
/**
|
||||
* Oh no, some grey hair!
|
||||
*/
|
||||
MiddleAged: 50,
|
||||
|
||||
/**
|
||||
* Unbelievable!
|
||||
*/
|
||||
Mathusalem: 1000,
|
||||
};
|
||||
```
|
||||
|
||||
:::tip
|
||||
**Named types** (`type Title string`) generate native TypeScript `enum` declarations with a `$zero` member.
|
||||
**Type aliases** (`type Age = int`) generate a `type` + `const` namespace pair without `$zero`.
|
||||
:::
|
||||
|
||||
## The `$zero` Value
|
||||
|
||||
Every named-type enum includes a special `$zero` member representing the **Go zero value** for the underlying type:
|
||||
|
||||
| Underlying Type | `$zero` Value |
|
||||
|----------------|---------------|
|
||||
| `string` | `""` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | `0` |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `0` |
|
||||
| `float32`, `float64` | `0` |
|
||||
| `bool` | `false` |
|
||||
|
||||
When a struct field uses an enum type and no value is provided, the constructor defaults to `$zero`:
|
||||
|
||||
```typescript
|
||||
export class Person {
|
||||
"Title": Title;
|
||||
|
||||
constructor($$source: Partial<Person> = {}) {
|
||||
if (!("Title" in $$source)) {
|
||||
this["Title"] = Title.$zero; // defaults to ""
|
||||
}
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This ensures type-safe initialisation when generating classes — enum fields are never `undefined`. When generating TypeScript interfaces (using `-i`), there is no constructor and fields may be absent as usual.
|
||||
|
||||
## Using Enums in Structs
|
||||
|
||||
When a struct field has an enum type, the generated code **preserves that type** rather than falling back to the primitive:
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Title Title
|
||||
Name string
|
||||
Age Age
|
||||
}
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
export class Person {
|
||||
"Title": Title;
|
||||
"Name": string;
|
||||
"Age": Age;
|
||||
|
||||
constructor($$source: Partial<Person> = {}) {
|
||||
if (!("Title" in $$source)) {
|
||||
this["Title"] = Title.$zero;
|
||||
}
|
||||
if (!("Name" in $$source)) {
|
||||
this["Name"] = "";
|
||||
}
|
||||
if (!("Age" in $$source)) {
|
||||
this["Age"] = 0;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Title` field is typed as `Title`, not `string`. This gives your IDE full autocompletion and type checking on enum values.
|
||||
|
||||
## Enums from Imported Packages
|
||||
|
||||
Enums defined in separate packages are fully supported. They are generated into the corresponding package directory:
|
||||
|
||||
```go
|
||||
// services/types.go
|
||||
package services
|
||||
|
||||
type Title string
|
||||
|
||||
const (
|
||||
Mister Title = "Mr"
|
||||
Miss Title = "Miss"
|
||||
Ms Title = "Ms"
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
// main.go
|
||||
package main
|
||||
|
||||
import "myapp/services"
|
||||
|
||||
func (*GreetService) Greet(name string, title services.Title) string {
|
||||
return "Hello " + string(title) + " " + name
|
||||
}
|
||||
```
|
||||
|
||||
The `Title` enum is generated in the `services` models file, and import paths are resolved automatically:
|
||||
|
||||
```typescript
|
||||
// bindings/myapp/services/models.ts
|
||||
export enum Title {
|
||||
$zero = "",
|
||||
Mister = "Mr",
|
||||
Miss = "Miss",
|
||||
Ms = "Ms",
|
||||
}
|
||||
```
|
||||
|
||||
## Enum Methods
|
||||
|
||||
You can add methods to your enum types in Go. These don't affect binding generation but provide useful server-side functionality:
|
||||
|
||||
```go
|
||||
type Title string
|
||||
|
||||
func (t Title) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
const (
|
||||
Mister Title = "Mr"
|
||||
Miss Title = "Miss"
|
||||
)
|
||||
```
|
||||
|
||||
The generated enum is identical whether or not Go methods exist on the type.
|
||||
|
||||
## Comments and Documentation
|
||||
|
||||
The generator preserves Go comments as JSDoc in the generated output:
|
||||
|
||||
- **Type comments** become the enum's doc comment
|
||||
- **Const group comments** become section separators
|
||||
- **Individual const comments** become member doc comments
|
||||
- **Inline comments** are preserved where possible
|
||||
|
||||
This means your IDE will show documentation for enum values on hover.
|
||||
|
||||
## Supported Underlying Types
|
||||
|
||||
The binding generator supports enums with the following Go underlying types:
|
||||
|
||||
| Go Type | Works as Enum |
|
||||
|---------|:---:|
|
||||
| `string` | Yes |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | Yes |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | Yes |
|
||||
| `float32`, `float64` | Yes |
|
||||
| `byte` (`uint8`) | Yes |
|
||||
| `rune` (`int32`) | Yes |
|
||||
| `bool` | Yes |
|
||||
| `complex64`, `complex128` | No |
|
||||
|
||||
## Limitations
|
||||
|
||||
The following are **not** supported for enum generation:
|
||||
|
||||
- **Generic types** — Type parameters prevent constant detection
|
||||
- **Types with custom `json.Marshaler` or `encoding.TextMarshaler`** — Custom serialisation means the generated values may not match runtime behaviour, so the generator skips these
|
||||
- **Constants whose values cannot be statically evaluated or represented** — Constants must have known, representable values in their underlying type. Standard `iota` patterns work fine since the compiler resolves them to concrete values
|
||||
- **Complex number types** — `complex64` and `complex128` cannot be enum underlying types
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// BackgroundType defines the type of background
|
||||
type BackgroundType string
|
||||
|
||||
const (
|
||||
BackgroundSolid BackgroundType = "solid"
|
||||
BackgroundGradient BackgroundType = "gradient"
|
||||
BackgroundImage BackgroundType = "image"
|
||||
)
|
||||
|
||||
type BackgroundConfig struct {
|
||||
Type BackgroundType `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ThemeService struct{}
|
||||
|
||||
func (*ThemeService) GetBackground() BackgroundConfig {
|
||||
return BackgroundConfig{
|
||||
Type: BackgroundSolid,
|
||||
Value: "#ffffff",
|
||||
}
|
||||
}
|
||||
|
||||
func (*ThemeService) SetBackground(config BackgroundConfig) error {
|
||||
// Apply background
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&ThemeService{}),
|
||||
},
|
||||
})
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**Frontend (TypeScript):**
|
||||
|
||||
```typescript
|
||||
import { GetBackground, SetBackground } from './bindings/myapp/themeservice'
|
||||
import { BackgroundConfig, BackgroundType } from './bindings/myapp/models'
|
||||
|
||||
// Get current background
|
||||
const bg = await GetBackground()
|
||||
|
||||
// Check the type using enum values
|
||||
if (bg.type === BackgroundType.BackgroundSolid) {
|
||||
console.log("Solid background:", bg.value)
|
||||
}
|
||||
|
||||
// Set a new background
|
||||
await SetBackground(new BackgroundConfig({
|
||||
type: BackgroundType.BackgroundGradient,
|
||||
value: "linear-gradient(to right, #000, #fff)"
|
||||
}))
|
||||
```
|
||||
|
||||
**Frontend (JavaScript):**
|
||||
|
||||
```javascript
|
||||
import { GetBackground, SetBackground } from './bindings/myapp/themeservice'
|
||||
import { BackgroundConfig, BackgroundType } from './bindings/myapp/models'
|
||||
|
||||
// Use enum values for type-safe comparisons
|
||||
const bg = await GetBackground()
|
||||
|
||||
switch (bg.type) {
|
||||
case BackgroundType.BackgroundSolid:
|
||||
applySolid(bg.value)
|
||||
break
|
||||
case BackgroundType.BackgroundGradient:
|
||||
applyGradient(bg.value)
|
||||
break
|
||||
case BackgroundType.BackgroundImage:
|
||||
applyImage(bg.value)
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Data Models" icon="document">
|
||||
Structs, type mapping, and model generation.
|
||||
|
||||
[Learn More →](/features/bindings/models)
|
||||
</Card>
|
||||
|
||||
<Card title="Method Binding" icon="rocket">
|
||||
Bind Go methods to the frontend.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
|
||||
<Card title="Advanced Binding" icon="setting">
|
||||
Directives, code injection, and custom IDs.
|
||||
|
||||
[Learn More →](/features/bindings/advanced)
|
||||
</Card>
|
||||
|
||||
<Card title="Best Practices" icon="approve-check">
|
||||
Binding design patterns.
|
||||
|
||||
[Learn More →](/features/bindings/best-practices)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
|
||||
|
|
@ -480,6 +480,8 @@ if (user.nickname) {
|
|||
|
||||
### Enums
|
||||
|
||||
The binding generator automatically detects Go named types with constants and generates TypeScript enums or JavaScript const objects — including a `$zero` member for Go's zero value and full JSDoc preservation.
|
||||
|
||||
```go
|
||||
type UserRole string
|
||||
|
||||
|
|
@ -488,26 +490,16 @@ const (
|
|||
RoleUser UserRole = "user"
|
||||
RoleGuest UserRole = "guest"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role UserRole `json:"role"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated:**
|
||||
**Generated TypeScript:**
|
||||
|
||||
```javascript
|
||||
export const UserRole = {
|
||||
Admin: "admin",
|
||||
User: "user",
|
||||
Guest: "guest"
|
||||
}
|
||||
|
||||
export class User {
|
||||
/** @type {string} */
|
||||
role = UserRole.User
|
||||
```typescript
|
||||
export enum UserRole {
|
||||
$zero = "",
|
||||
RoleAdmin = "admin",
|
||||
RoleUser = "user",
|
||||
RoleGuest = "guest",
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -518,10 +510,12 @@ import { User, UserRole } from './bindings/myapp/models'
|
|||
|
||||
const admin = new User({
|
||||
name: "Admin",
|
||||
role: UserRole.Admin
|
||||
role: UserRole.RoleAdmin
|
||||
})
|
||||
```
|
||||
|
||||
For comprehensive coverage of string enums, integer enums, type aliases, imported package enums, and limitations, see the dedicated **[Enums](/features/bindings/enums)** page.
|
||||
|
||||
### Validation
|
||||
|
||||
```javascript
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ After processing, the content will be moved to the main changelog and this file
|
|||
|
||||
## Added
|
||||
<!-- New features, capabilities, or enhancements -->
|
||||
- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957)
|
||||
- Add documentation for automatic enum generation in binding generator, including dedicated Enums page and sidebar navigation (#4972)
|
||||
|
||||
## Changed
|
||||
<!-- Changes in existing functionality -->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue