diff --git a/v2/internal/appng/app_bindings.go b/v2/internal/appng/app_bindings.go index 6f94e211f..1db5c266f 100644 --- a/v2/internal/appng/app_bindings.go +++ b/v2/internal/appng/app_bindings.go @@ -4,6 +4,9 @@ package appng import ( + "os" + "path/filepath" + "github.com/leaanthony/gosod" "github.com/wailsapp/wails/v2/internal/binding" wailsRuntime "github.com/wailsapp/wails/v2/internal/frontend/runtime" @@ -12,8 +15,6 @@ import ( "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/project" "github.com/wailsapp/wails/v2/pkg/options" - "os" - "path/filepath" ) // App defines a Wails application structure @@ -96,6 +97,10 @@ func generateBindings(bindings *binding.Bindings) error { return err } + err = bindings.GenerateGoBindings(targetDir) + if err != nil { + return err + } // Write backend method wrappers bindingsFilename := filepath.Join(targetDir, "bindings.js") err = bindings.GenerateBackendJS(bindingsFilename) diff --git a/v2/internal/appng/app_dev.go b/v2/internal/appng/app_dev.go index 3ee63c565..bd9400265 100644 --- a/v2/internal/appng/app_dev.go +++ b/v2/internal/appng/app_dev.go @@ -225,6 +225,11 @@ func generateBindings(bindings *binding.Bindings) error { return err } + err = bindings.GenerateGoBindings(targetDir) + if err != nil { + return err + } + // Write backend method wrappers bindingsFilename := filepath.Join(targetDir, "bindings.js") err = bindings.GenerateBackendJS(bindingsFilename) diff --git a/v2/internal/binding/boundMethod.go b/v2/internal/binding/boundMethod.go index f6ffdb600..206ffe252 100644 --- a/v2/internal/binding/boundMethod.go +++ b/v2/internal/binding/boundMethod.go @@ -9,11 +9,12 @@ import ( // BoundMethod defines all the data related to a Go method that is // bound to the Wails application type BoundMethod struct { - Name string `json:"name"` - Inputs []*Parameter `json:"inputs,omitempty"` - Outputs []*Parameter `json:"outputs,omitempty"` - Comments string `json:"comments,omitempty"` - Method reflect.Value `json:"-"` + Name string `json:"name"` + Inputs []*Parameter `json:"inputs,omitempty"` + Outputs []*Parameter `json:"outputs,omitempty"` + Comments string `json:"comments,omitempty"` + Method reflect.Value `json:"-"` + StructNames []string `json:"structNames"` } // InputCount returns the number of inputs this bound method has diff --git a/v2/internal/binding/generate.go b/v2/internal/binding/generate.go index e3cb37bb0..1bd3283bf 100644 --- a/v2/internal/binding/generate.go +++ b/v2/internal/binding/generate.go @@ -16,6 +16,83 @@ import ( //go:embed assets/package.json var packageJSON []byte +func (b *Bindings) GenerateGoBindings(baseDir string) error { + store := b.db.store + for packageName, structs := range store { + packageDir := filepath.Join(baseDir, packageName) + err := fs.Mkdir(packageDir) + if err != nil { + return err + } + for structName, methods := range structs { + var jsoutput bytes.Buffer + jsoutput.WriteString(`// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +`) + var tsoutput bytes.Buffer + tsoutput.WriteString(`// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +`) + for methodName, methodDetails := range methods { + + // Generate JS + var args slicer.StringSlicer + for count := range methodDetails.Inputs { + arg := fmt.Sprintf("arg%d", count+1) + args.Add(arg) + } + argsString := args.Join(", ") + jsoutput.WriteString(fmt.Sprintf("\nexport function %s(%s) {", methodName, argsString)) + jsoutput.WriteString("\n") + jsoutput.WriteString(fmt.Sprintf(" return window['go']['%s']['%s']['%s'](%s);", packageName, structName, methodName, argsString)) + jsoutput.WriteString("\n") + jsoutput.WriteString(fmt.Sprintf("}")) + jsoutput.WriteString("\n") + + // Generate TS + + if len(methodDetails.StructNames) > 0 { + classes := strings.Join(methodDetails.StructNames, ", ") + tsoutput.WriteString("import {" + classes + "} from '../models';") + } + tsoutput.WriteString(fmt.Sprintf("\nexport function %s(", methodName)) + + args.Clear() + for count, input := range methodDetails.Inputs { + arg := fmt.Sprintf("arg%d", count+1) + args.Add(arg + ":" + goTypeToTypescriptType(input.TypeName, false)) + } + tsoutput.WriteString(args.Join(",") + "):") + returnType := "Promise" + if methodDetails.OutputCount() > 0 { + firstType := goTypeToTypescriptType(methodDetails.Outputs[0].TypeName, false) + returnType += "<" + firstType + if methodDetails.OutputCount() == 2 { + secondType := goTypeToTypescriptType(methodDetails.Outputs[1].TypeName, false) + returnType += "|" + secondType + } + returnType += ">" + } else { + returnType = "Promise" + } + tsoutput.WriteString(returnType + ";\n") + } + jsfilename := filepath.Join(packageDir, structName+".js") + err = os.WriteFile(jsfilename, jsoutput.Bytes(), 0755) + if err != nil { + return err + } + tsfilename := filepath.Join(packageDir, structName+".d.ts") + err = os.WriteFile(tsfilename, tsoutput.Bytes(), 0755) + if err != nil { + return err + } + } + } + return nil +} + func (b *Bindings) GenerateBackendJS(targetfile string) error { store := b.db.store @@ -24,6 +101,13 @@ func (b *Bindings) GenerateBackendJS(targetfile string) error { output.WriteString(`// @ts-check // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT + +// ************************************************ +// This file is deprecated and will not be generated +// in the next version of Wails. Bindings are now +// generated in their own files. +// ************************************************ + `) output.WriteString(`const go = {`) @@ -63,15 +147,15 @@ func (b *Bindings) GenerateBackendJS(targetfile string) error { for count, input := range methodDetails.Inputs { arg := fmt.Sprintf("arg%d", count+1) args.Add(arg) - output.WriteString(fmt.Sprintf(" * @param {%s} %s - Go Type: %s\n", goTypeToJSDocType(input.TypeName), arg, input.TypeName)) + output.WriteString(fmt.Sprintf(" * @param {%s} %s - Go Type: %s\n", goTypeToJSDocType(input.TypeName, true), arg, input.TypeName)) } returnType := "Promise" returnTypeDetails := "" if methodDetails.OutputCount() > 0 { - firstType := goTypeToJSDocType(methodDetails.Outputs[0].TypeName) + firstType := goTypeToJSDocType(methodDetails.Outputs[0].TypeName, true) returnType += "<" + firstType if methodDetails.OutputCount() == 2 { - secondType := goTypeToJSDocType(methodDetails.Outputs[1].TypeName) + secondType := goTypeToJSDocType(methodDetails.Outputs[1].TypeName, true) returnType += "|" + secondType } returnType += ">" @@ -120,6 +204,16 @@ func (b *Bindings) GenerateBackendTS(targetfile string) error { store := b.db.store var output bytes.Buffer + output.WriteString(` + +// ************************************************ +// This file is deprecated and will not be generated +// in the next version of Wails. Bindings are now +// generated in their own files. +// ************************************************ + +`) + output.WriteString("import * as models from './models';\n\n") output.WriteString("export interface go {\n") @@ -140,7 +234,7 @@ func (b *Bindings) GenerateBackendTS(targetfile string) error { sortedStructNames.Each(func(structName string) { structs := packages[structName] - output.WriteString(fmt.Sprintf(" \"models.%s\": {", structName)) + output.WriteString(fmt.Sprintf(" \"%s\": {", structName)) output.WriteString("\n") var sortedMethodNames slicer.StringSlicer @@ -156,15 +250,15 @@ func (b *Bindings) GenerateBackendTS(targetfile string) error { var args slicer.StringSlicer for count, input := range methodDetails.Inputs { arg := fmt.Sprintf("arg%d", count+1) - args.Add(arg + ":" + goTypeToTypescriptType(input.TypeName)) + args.Add(arg + ":" + goTypeToTypescriptType(input.TypeName, true)) } output.WriteString(args.Join(",") + "):") returnType := "Promise" if methodDetails.OutputCount() > 0 { - firstType := goTypeToTypescriptType(methodDetails.Outputs[0].TypeName) + firstType := goTypeToTypescriptType(methodDetails.Outputs[0].TypeName, true) returnType += "<" + firstType if methodDetails.OutputCount() == 2 { - secondType := goTypeToTypescriptType(methodDetails.Outputs[1].TypeName) + secondType := goTypeToTypescriptType(methodDetails.Outputs[1].TypeName, true) returnType += "|" + secondType } returnType += ">" @@ -191,7 +285,7 @@ declare global { return os.WriteFile(targetfile, output.Bytes(), 0755) } -func goTypeToJSDocType(input string) string { +func goTypeToJSDocType(input string, useModelsNamespace bool) string { switch true { case input == "interface{}": return "any" @@ -209,20 +303,23 @@ func goTypeToJSDocType(input string) string { case input == "[]byte": return "string" case strings.HasPrefix(input, "[]"): - arrayType := goTypeToJSDocType(input[2:]) + arrayType := goTypeToJSDocType(input[2:], useModelsNamespace) return "Array<" + arrayType + ">" default: if strings.ContainsRune(input, '.') { - return "models." + strings.Split(input, ".")[1] + if useModelsNamespace { + return "models." + strings.Split(input, ".")[1] + } + return strings.Split(input, ".")[1] } return "any" } } -func goTypeToTypescriptType(input string) string { +func goTypeToTypescriptType(input string, useModelsNamespace bool) string { if strings.HasPrefix(input, "[]") { - arrayType := goTypeToJSDocType(input[2:]) + arrayType := goTypeToJSDocType(input[2:], useModelsNamespace) return "Array<" + arrayType + ">" } - return goTypeToJSDocType(input) + return goTypeToJSDocType(input, useModelsNamespace) } diff --git a/v2/internal/binding/reflect.go b/v2/internal/binding/reflect.go index 29dbe9440..e89793164 100755 --- a/v2/internal/binding/reflect.go +++ b/v2/internal/binding/reflect.go @@ -91,6 +91,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { a := reflect.New(typ) s := reflect.Indirect(a).Interface() b.converter.Add(s) + boundMethod.StructNames = append(boundMethod.StructNames, thisInput.Name()) } } @@ -99,6 +100,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { a := reflect.New(thisInput) s := reflect.Indirect(a).Interface() b.converter.Add(s) + boundMethod.StructNames = append(boundMethod.StructNames, thisInput.Name()) } inputs = append(inputs, thisParam) @@ -128,6 +130,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { a := reflect.New(typ) s := reflect.Indirect(a).Interface() b.converter.Add(s) + boundMethod.StructNames = append(boundMethod.StructNames, thisOutput.Name()) } } @@ -136,6 +139,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) { a := reflect.New(thisOutput) s := reflect.Indirect(a).Interface() b.converter.Add(s) + boundMethod.StructNames = append(boundMethod.StructNames, thisOutput.Name()) } outputs = append(outputs, thisParam)