5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 06:39:30 +08:00

Ability to define prefix / suffix for TS module (#2101)

* add tspostfix and tsprefix flags + organise under struct

* postifx -> suffix

* tsPrefix options on bindings struct

* pass prefix and suffix to the executable

* add support for CLI flags for generating module

* method to set TSpref/suff to bindings

* use passed ts prefix for typescriptify

* add brief Readme udpate to include new flags

* create reusable common flags

* use common flags instead of hardcoded text

* support tsprefix & suffix for dev

* add tsPrefix & tsSuffix for build cmd

* take pref & suff in account when gen d.ts

* export colorsful log functions into utils for reuse

* detect and warn the user about usage of reserved keyword

* fmt

* add TrimSpace on fn input

* refactor utils -> logutils

* add bindings -> ts_generation options to wailsjson parse

* use wailsjson for ts generation

* update warning message + extract to func

* remove suff/pref info from readme

* update json schema

* add tests for prefix and suffix case

* rename suffix method

* Update v2/internal/typescriptify/typescriptify.go

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* Update website/static/schemas/config.v2.json

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* Update website/static/schemas/config.v2.json

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>

* update changelog

* Minor tweaks

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
Oleg Gulevskyy 2022-11-24 11:33:58 +01:00 committed by GitHub
parent c1c1bff7a4
commit ca8a1fab36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 352 additions and 67 deletions

View File

@ -2,7 +2,6 @@ package build
import ( import (
"fmt" "fmt"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"io" "io"
"os" "os"
"os/exec" "os/exec"
@ -11,6 +10,8 @@ import (
"text/tabwriter" "text/tabwriter"
"time" "time"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"github.com/wailsapp/wails/v2/internal/colour" "github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/project" "github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/system" "github.com/wailsapp/wails/v2/internal/system"

View File

@ -33,36 +33,13 @@ import (
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process" "github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build" "github.com/wailsapp/wails/v2/pkg/commands/build"
) )
func LogGreen(message string, args ...interface{}) {
if len(message) == 0 {
return
}
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}
func LogRed(message string, args ...interface{}) {
if len(message) == 0 {
return
}
text := fmt.Sprintf(message, args...)
println(colour.Red(text))
}
func LogDarkYellow(message string, args ...interface{}) {
if len(message) == 0 {
return
}
text := fmt.Sprintf(message, args...)
println(colour.DarkYellow(text))
}
func sliceToMap(input []string) map[string]struct{} { func sliceToMap(input []string) map[string]struct{} {
result := map[string]struct{}{} result := map[string]struct{}{}
for _, value := range input { for _, value := range input {
@ -184,16 +161,18 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if !buildOptions.SkipBindings { if !buildOptions.SkipBindings {
if flags.verbosity == build.VERBOSE { if flags.verbosity == build.VERBOSE {
LogGreen("Generating Bindings...") logutils.LogGreen("Generating Bindings...")
} }
stdout, err := bindings.GenerateBindings(bindings.Options{ stdout, err := bindings.GenerateBindings(bindings.Options{
Tags: buildOptions.UserTags, Tags: buildOptions.UserTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
}) })
if err != nil { if err != nil {
return err return err
} }
if flags.verbosity == build.VERBOSE { if flags.verbosity == build.VERBOSE {
LogGreen(stdout) logutils.LogGreen(stdout)
} }
} }
@ -238,7 +217,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
} }
defer func() { defer func() {
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil { if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
LogDarkYellow("Unable to kill process and cleanup binary: %s", err) logutils.LogDarkYellow("Unable to kill process and cleanup binary: %s", err)
} }
}() }()
@ -263,17 +242,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
} }
}(watcher) }(watcher)
LogGreen("Watching (sub)/directory: %s", cwd) logutils.LogGreen("Watching (sub)/directory: %s", cwd)
LogGreen("Using DevServer URL: %s", devServerURL) logutils.LogGreen("Using DevServer URL: %s", devServerURL)
if flags.frontendDevServerURL != "" { if flags.frontendDevServerURL != "" {
LogGreen("Using Frontend DevServer URL: %s", flags.frontendDevServerURL) logutils.LogGreen("Using Frontend DevServer URL: %s", flags.frontendDevServerURL)
} }
LogGreen("Using reload debounce setting of %d milliseconds", flags.debounceMS) logutils.LogGreen("Using reload debounce setting of %d milliseconds", flags.debounceMS)
// Show dev server URL in terminal after 3 seconds // Show dev server URL in terminal after 3 seconds
go func() { go func() {
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
LogGreen("\n\nTo develop in the browser and call your bound Go methods from Javascript, navigate to: %s", devServerURL) logutils.LogGreen("\n\nTo develop in the browser and call your bound Go methods from Javascript, navigate to: %s", devServerURL)
}() }()
// Watch for changes and trigger restartApp() // Watch for changes and trigger restartApp()
@ -288,7 +267,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
debugBinaryProcess = nil debugBinaryProcess = nil
appBinary = "" appBinary = ""
LogGreen("Development mode exited") logutils.LogGreen("Development mode exited")
return nil return nil
}) })
@ -312,7 +291,7 @@ func killProcessAndCleanupBinary(process *process.Process, binary string) error
} }
func runCommand(dir string, exitOnError bool, command string, args ...string) error { func runCommand(dir string, exitOnError bool, command string, args ...string) error {
LogGreen("Executing: " + command + " " + strings.Join(args, " ")) logutils.LogGreen("Executing: " + command + " " + strings.Join(args, " "))
cmd := exec.Command(command, args...) cmd := exec.Command(command, args...)
cmd.Dir = dir cmd.Dir = dir
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
@ -460,7 +439,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
} }
} }
LogGreen("Running frontend DevWatcher command: '%s'", devCommand) logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
@ -474,7 +453,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
wasRunning := atomic.CompareAndSwapInt32(&state, stateRunning, stateStopped) wasRunning := atomic.CompareAndSwapInt32(&state, stateRunning, stateStopped)
if err.Error() != "exit status 1" && wasRunning { if err.Error() != "exit status 1" && wasRunning {
LogRed("Error from DevWatcher '%s': %s", devCommand, err.Error()) logutils.LogRed("Error from DevWatcher '%s': %s", devCommand, err.Error())
} }
} }
atomic.StoreInt32(&state, stateStopped) atomic.StoreInt32(&state, stateStopped)
@ -496,13 +475,13 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
appBinary, err := build.Build(buildOptions) appBinary, err := build.Build(buildOptions)
println() println()
if err != nil { if err != nil {
LogRed("Build error - " + err.Error()) logutils.LogRed("Build error - " + err.Error())
msg := "Continuing to run current version" msg := "Continuing to run current version"
if debugBinaryProcess == nil { if debugBinaryProcess == nil {
msg = "No version running, build will be retriggered as soon as changes have been detected" msg = "No version running, build will be retriggered as soon as changes have been detected"
} }
LogDarkYellow(msg) logutils.LogDarkYellow(msg)
return nil, "", nil return nil, "", nil
} }
@ -558,7 +537,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
} }
thePath, err := filepath.Abs(dir) thePath, err := filepath.Abs(dir)
if err != nil { if err != nil {
LogRed("Unable to expand reloadDir '%s': %s", dir, err) logutils.LogRed("Unable to expand reloadDir '%s': %s", dir, err)
continue continue
} }
dirsThatTriggerAReload = append(dirsThatTriggerAReload, thePath) dirsThatTriggerAReload = append(dirsThatTriggerAReload, thePath)
@ -585,7 +564,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
quit = true quit = true
} }
case err := <-watcher.Errors: case err := <-watcher.Errors:
LogDarkYellow(err.Error()) logutils.LogDarkYellow(err.Error())
case item := <-watcher.Events: case item := <-watcher.Events:
isEligibleFile := func(fileName string) bool { isEligibleFile := func(fileName string) bool {
// Iterate all file patterns // Iterate all file patterns
@ -637,7 +616,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
if err != nil { if err != nil {
buildOptions.Logger.Fatal("%s", err.Error()) buildOptions.Logger.Fatal("%s", err.Error())
} }
LogGreen("Added new directory to watcher: %s", item.Name) logutils.LogGreen("Added new directory to watcher: %s", item.Name)
} }
} else if isEligibleFile(item.Name) { } else if isEligibleFile(item.Name) {
// Handle creation of new file. // Handle creation of new file.
@ -652,11 +631,11 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
case <-timer.C: case <-timer.C:
if rebuild { if rebuild {
rebuild = false rebuild = false
LogGreen("[Rebuild triggered] files updated") logutils.LogGreen("[Rebuild triggered] files updated")
// Try and build the app // Try and build the app
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, flags, exitCodeChannel) newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, flags, exitCodeChannel)
if err != nil { if err != nil {
LogRed("Error during build: %s", err.Error()) logutils.LogRed("Error during build: %s", err.Error())
continue continue
} }
// If we have a new process, saveConfig it // If we have a new process, saveConfig it
@ -669,11 +648,11 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
if assetDir == "" { if assetDir == "" {
resp, err := http.Get(assetDirURL) resp, err := http.Get(assetDirURL)
if err != nil { if err != nil {
LogRed("Error during retrieving assetdir: %s", err.Error()) logutils.LogRed("Error during retrieving assetdir: %s", err.Error())
} else { } else {
content, err := io.ReadAll(resp.Body) content, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
LogRed("Error reading assetdir from devserver: %s", err.Error()) logutils.LogRed("Error reading assetdir from devserver: %s", err.Error())
} else { } else {
assetDir = string(content) assetDir = string(content)
} }
@ -689,19 +668,19 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
} }
} }
} else if len(dirsThatTriggerAReload) == 0 { } else if len(dirsThatTriggerAReload) == 0 {
LogRed("Reloading couldn't be triggered: Please specify -assetdir or -reloaddirs") logutils.LogRed("Reloading couldn't be triggered: Please specify -assetdir or -reloaddirs")
} }
} }
if reload { if reload {
reload = false reload = false
_, err := http.Get(reloadURL) _, err := http.Get(reloadURL)
if err != nil { if err != nil {
LogRed("Error during refresh: %s", err.Error()) logutils.LogRed("Error during refresh: %s", err.Error())
} }
} }
changedPaths = map[string]struct{}{} changedPaths = map[string]struct{}{}
case <-quitChannel: case <-quitChannel:
LogGreen("\nCaught quit") logutils.LogGreen("\nCaught quit")
quit = true quit = true
} }
} }

View File

@ -7,6 +7,7 @@ import (
"os/exec" "os/exec"
"syscall" "syscall"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -30,7 +31,7 @@ func killProc(cmd *exec.Cmd, devCommand string) {
if err == nil { if err == nil {
err := syscall.Kill(-pgid, unix.SIGTERM) // note the minus sign err := syscall.Kill(-pgid, unix.SIGTERM) // note the minus sign
if err != nil { if err != nil {
LogRed("Error from '%s' when attempting to kill the process: %s", devCommand, err.Error()) logutils.LogRed("Error from '%s' when attempting to kill the process: %s", devCommand, err.Error())
} }
} }
} }

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/acarl005/stripansi" "github.com/acarl005/stripansi"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
) )
// stdoutScanner acts as a stdout target that will scan the incoming // stdoutScanner acts as a stdout target that will scan the incoming
@ -35,10 +36,10 @@ func (s *stdoutScanner) Write(data []byte) (n int, err error) {
continue continue
} }
viteServerURL := strings.TrimSpace(line[index+6:]) viteServerURL := strings.TrimSpace(line[index+6:])
LogGreen("Vite Server URL: %s", viteServerURL) logutils.LogGreen("Vite Server URL: %s", viteServerURL)
_, err := url.Parse(viteServerURL) _, err := url.Parse(viteServerURL)
if err != nil { if err != nil {
LogRed(err.Error()) logutils.LogRed(err.Error())
} else { } else {
s.ViteServerURLChan <- viteServerURL s.ViteServerURLChan <- viteServerURL
} }

View File

@ -16,3 +16,10 @@ Generate a starter template for you to customise.
| :------------- | :----------- | | :------------- | :----------- |
| -frontend | Copies all the files from the current directory into the template's `frontend` directory. Useful for converting frontend projects created by boilerplate generators. | | -frontend | Copies all the files from the current directory into the template's `frontend` directory. Useful for converting frontend projects created by boilerplate generators. |
| -q | Suppress output | | -q | Suppress output |
## Module
`wails generate module [-h]`
Generate TS module for your frontend

View File

@ -1,28 +1,45 @@
package generate package generate
import ( import (
"io"
"os"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/commands/bindings" "github.com/wailsapp/wails/v2/pkg/commands/bindings"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags" "github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"io"
) )
type generateFlags struct {
tags string
}
// AddModuleCommand adds the `module` subcommand for the `generate` command // AddModuleCommand adds the `module` subcommand for the `generate` command
func AddModuleCommand(app *clir.Cli, parent *clir.Command, w io.Writer) error { func AddModuleCommand(app *clir.Cli, parent *clir.Command, w io.Writer) error {
command := parent.NewSubCommand("module", "Generate wailsjs modules") command := parent.NewSubCommand("module", "Generate wailsjs modules")
var tags string genFlags := generateFlags{}
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags) command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &genFlags.tags)
command.Action(func() error { command.Action(func() error {
cwd, err := os.Getwd()
if err != nil {
return err
}
projectConfig, err := project.Load(cwd)
if err != nil {
return err
}
buildTags, err := buildtags.Parse(tags) buildTags, err := buildtags.Parse(genFlags.tags)
if err != nil { if err != nil {
return err return err
} }
_, err = bindings.GenerateBindings(bindings.Options{ _, err = bindings.GenerateBindings(bindings.Options{
Tags: buildTags, Tags: buildTags,
TsPrefix: projectConfig.Bindings.TsGeneration.Prefix,
TsSuffix: projectConfig.Bindings.TsGeneration.Suffix,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,31 @@
package logutils
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/colour"
)
func LogGreen(message string, args ...interface{}) {
if len(message) == 0 {
return
}
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}
func LogRed(message string, args ...interface{}) {
if len(message) == 0 {
return
}
text := fmt.Sprintf(message, args...)
println(colour.Red(text))
}
func LogDarkYellow(message string, args ...interface{}) {
if len(message) == 0 {
return
}
text := fmt.Sprintf(message, args...)
println(colour.DarkYellow(text))
}

View File

@ -5,6 +5,7 @@ package app
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"flag"
"github.com/leaanthony/gosod" "github.com/leaanthony/gosod"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
@ -25,8 +26,35 @@ func (a *App) Run() error {
a.options.OnBeforeClose, a.options.OnBeforeClose,
} }
// Check for CLI Flags
bindingFlags := flag.NewFlagSet("bindings", flag.ContinueOnError)
var tsPrefixFlag *string
var tsPostfixFlag *string
tsPrefix := os.Getenv("tsprefix")
if tsPrefix == "" {
tsPrefixFlag = bindingFlags.String("tsprefix", "", "Prefix for generated typescript entities")
}
tsSuffix := os.Getenv("tssuffix")
if tsSuffix == "" {
tsPostfixFlag = bindingFlags.String("tssuffix", "", "Suffix for generated typescript entities")
}
_ = bindingFlags.Parse(os.Args[1:])
if tsPrefixFlag != nil {
tsPrefix = *tsPrefixFlag
}
if tsPostfixFlag != nil {
tsSuffix = *tsPostfixFlag
}
appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated()) appBindings := binding.NewBindings(a.logger, a.options.Bind, bindingExemptions, IsObfuscated())
appBindings.SetTsPrefix(tsPrefix)
appBindings.SetTsPostfix(tsSuffix)
err := generateBindings(appBindings) err := generateBindings(appBindings)
if err != nil { if err != nil {
return err return err

View File

@ -23,6 +23,8 @@ type Bindings struct {
exemptions slicer.StringSlicer exemptions slicer.StringSlicer
structsToGenerateTS map[string]map[string]interface{} structsToGenerateTS map[string]map[string]interface{}
tsPrefix string
tsSuffix string
obfuscate bool obfuscate bool
} }
@ -92,6 +94,8 @@ func (b *Bindings) GenerateModels() ([]byte, error) {
for packageName, structsToGenerate := range b.structsToGenerateTS { for packageName, structsToGenerate := range b.structsToGenerateTS {
thisPackageCode := "" thisPackageCode := ""
w := typescriptify.New() w := typescriptify.New()
w.WithPrefix(b.tsPrefix)
w.WithSuffix(b.tsSuffix)
w.Namespace = packageName w.Namespace = packageName
w.WithBackupDir("") w.WithBackupDir("")
w.KnownStructs = allStructNames w.KnownStructs = allStructNames
@ -219,6 +223,16 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,
} }
} }
func (b *Bindings) SetTsPrefix(prefix string) *Bindings {
b.tsPrefix = prefix
return b
}
func (b *Bindings) SetTsSuffix(postfix string) *Bindings {
b.tsSuffix = postfix
return b
}
func (b *Bindings) getAllStructNames() *slicer.StringSlicer { func (b *Bindings) getAllStructNames() *slicer.StringSlicer {
var result slicer.StringSlicer var result slicer.StringSlicer
for packageName, structsToGenerate := range b.structsToGenerateTS { for packageName, structsToGenerate := range b.structsToGenerateTS {

View File

@ -16,6 +16,12 @@ type BindingTest struct {
exemptions []interface{} exemptions []interface{}
want string want string
shouldError bool shouldError bool
TsGenerationOptionsTest
}
type TsGenerationOptionsTest struct {
TsPrefix string
TsSuffix string
} }
func TestBindings_GenerateModels(t *testing.T) { func TestBindings_GenerateModels(t *testing.T) {
@ -30,6 +36,7 @@ func TestBindings_GenerateModels(t *testing.T) {
SingleFieldTest, SingleFieldTest,
MultistructTest, MultistructTest,
EmptyStructTest, EmptyStructTest,
GeneratedJsEntityTest,
} }
testLogger := &logger.Logger{} testLogger := &logger.Logger{}
@ -40,6 +47,10 @@ func TestBindings_GenerateModels(t *testing.T) {
err := b.Add(s) err := b.Add(s)
require.NoError(t, err) require.NoError(t, err)
} }
b.SetTsPrefix(tt.TsPrefix)
// TODO - rename this to SetTsSuffix
b.SetTsSuffix(tt.TsSuffix)
got, err := b.GenerateModels() got, err := b.GenerateModels()
if (err != nil) != tt.shouldError { if (err != nil) != tt.shouldError {
t.Errorf("GenerateModels() error = %v, shouldError %v", err, tt.shouldError) t.Errorf("GenerateModels() error = %v, shouldError %v", err, tt.shouldError)

View File

@ -0,0 +1,41 @@
package binding_test
type GeneratedJsEntity struct {
Name string `json:"name"`
}
func (s GeneratedJsEntity) Get() GeneratedJsEntity {
return s
}
var GeneratedJsEntityTest = BindingTest{
name: "GeneratedJsEntityTest ",
structs: []interface{}{
&GeneratedJsEntity{},
},
exemptions: nil,
shouldError: false,
TsGenerationOptionsTest: TsGenerationOptionsTest{
TsPrefix: "MY_PREFIX_",
TsSuffix: "_MY_SUFFIX",
},
want: `
export namespace binding_test {
export class MY_PREFIX_GeneratedJsEntity_MY_SUFFIX {
name: string;
static createFrom(source: any = {}) {
return new MY_PREFIX_GeneratedJsEntity_MY_SUFFIX(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
}
}
}
`,
}

View File

@ -79,11 +79,13 @@ func (b *Bindings) GenerateGoBindings(baseDir string) error {
tsBody.WriteString(args.Join(",") + "):") tsBody.WriteString(args.Join(",") + "):")
returnType := "Promise" returnType := "Promise"
if methodDetails.OutputCount() > 0 { if methodDetails.OutputCount() > 0 {
firstType := goTypeToTypescriptType(methodDetails.Outputs[0].TypeName, &importNamespaces) outputTypeName := entityFullReturnType(methodDetails.Outputs[0].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
firstType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
returnType += "<" + firstType returnType += "<" + firstType
if methodDetails.OutputCount() == 2 { if methodDetails.OutputCount() == 2 {
if methodDetails.Outputs[1].TypeName != "error" { if methodDetails.Outputs[1].TypeName != "error" {
secondType := goTypeToTypescriptType(methodDetails.Outputs[1].TypeName, &importNamespaces) outputTypeName = entityFullReturnType(methodDetails.Outputs[1].TypeName, b.tsPrefix, b.tsSuffix, &importNamespaces)
secondType := goTypeToTypescriptType(outputTypeName, &importNamespaces)
returnType += "|" + secondType returnType += "|" + secondType
} }
} }
@ -171,3 +173,12 @@ func goTypeToTypescriptType(input string, importNamespaces *slicer.StringSlicer)
} }
return goTypeToJSDocType(input, importNamespaces) return goTypeToJSDocType(input, importNamespaces)
} }
func entityFullReturnType(input, prefix, suffix string, importNamespaces *slicer.StringSlicer) string {
if strings.ContainsRune(input, '.') {
nameSpace, returnType := getSplitReturn(input)
return nameSpace + "." + prefix + returnType + suffix
}
return input
}

View File

@ -166,6 +166,12 @@ func getPackageName(in string) string {
return result return result
} }
func getSplitReturn(in string) (string, string) {
result := strings.Split(in, ".")
return result[0], result[1]
}
func hasElements(typ reflect.Type) bool { func hasElements(typ reflect.Type) bool {
kind := typ.Kind() kind := typ.Kind()
return kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map return kind == reflect.Ptr || kind == reflect.Array || kind == reflect.Slice || kind == reflect.Map

View File

@ -2,11 +2,12 @@ package project
import ( import (
"encoding/json" "encoding/json"
"github.com/samber/lo"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"github.com/samber/lo"
) )
// Project holds the data related to a Wails project // Project holds the data related to a Wails project
@ -92,6 +93,8 @@ type Project struct {
// Frontend directory // Frontend directory
FrontendDir string `json:"frontend:dir"` FrontendDir string `json:"frontend:dir"`
Bindings Bindings `json:"bindings"`
} }
func (p *Project) GetFrontendDir() string { func (p *Project) GetFrontendDir() string {
@ -222,6 +225,15 @@ type Info struct {
Comments *string `json:"comments"` Comments *string `json:"comments"`
} }
type Bindings struct {
TsGeneration TsGeneration `json:"ts_generation"`
}
type TsGeneration struct {
Prefix string `json:"prefix"`
Suffix string `json:"suffix"`
}
// Parse the given JSON data into a Project struct // Parse the given JSON data into a Project struct
func Parse(projectData []byte) (*Project, error) { func Parse(projectData []byte) (*Project, error) {
project := &Project{} project := &Project{}

View File

@ -0,0 +1,69 @@
package typescriptify
var jsReservedKeywords []string = []string{
"abstract",
"arguments",
"await",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"double",
"else",
"enum",
"eval",
"export",
"extends",
"false",
"final",
"finally",
"float",
"for",
"function",
"goto",
"if",
"implements",
"import",
"in",
"instanceof",
"int",
"interface",
"let",
"long",
"native",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"true",
"try",
"typeof",
"var",
"void",
"volotile",
"while",
"with",
"yield",
"object",
}

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path" "path"
"reflect" "reflect"
@ -595,6 +596,11 @@ func (t *TypeScriptify) convertType(depth int, typeOf reflect.Type, customCode m
t.alreadyConverted[typeOf.String()] = true t.alreadyConverted[typeOf.String()] = true
entityName := t.Prefix + typeOf.Name() + t.Suffix entityName := t.Prefix + typeOf.Name() + t.Suffix
if typeClashWithReservedKeyword(entityName) {
warnAboutTypesClash(entityName)
}
result := "" result := ""
if t.CreateInterface { if t.CreateInterface {
result += fmt.Sprintf("interface %s {\n", entityName) result += fmt.Sprintf("interface %s {\n", entityName)
@ -901,3 +907,21 @@ func differentNamespaces(namespace string, typeOf reflect.Type) bool {
} }
return false return false
} }
func typeClashWithReservedKeyword(input string) bool {
in := strings.ToLower(strings.TrimSpace(input))
for _, v := range jsReservedKeywords {
if in == v {
return true
}
}
return false
}
func warnAboutTypesClash(entity string) {
// TODO: Refactor logging
l := log.New(os.Stderr, "", 0)
l.Println(fmt.Sprintf("Usage of reserved keyword found and not supported: %s", entity))
log.Println("Please rename returned type or consider adding bindings config to your wails.json")
}

View File

@ -2,12 +2,15 @@ package bindings
import ( import (
"fmt" "fmt"
"github.com/samber/lo" "log"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
) )
// Options for generating bindings // Options for generating bindings
@ -16,6 +19,8 @@ type Options struct {
Tags []string Tags []string
ProjectDirectory string ProjectDirectory string
GoModTidy bool GoModTidy bool
TsPrefix string
TsSuffix string
} }
// GenerateBindings generates bindings for the Wails project in the given ProjectDirectory. // GenerateBindings generates bindings for the Wails project in the given ProjectDirectory.
@ -57,10 +62,14 @@ func GenerateBindings(options Options) (string, error) {
_ = os.Remove(filename) _ = os.Remove(filename)
}() }()
stdout, stderr, err = shell.RunCommand(workingDirectory, filename) stdout, stderr, err = shell.RunCommand(workingDirectory, filename, "-tsprefix", options.TsPrefix, "-tssuffix", options.TsSuffix)
if err != nil { if err != nil {
return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err) return stdout, fmt.Errorf("%s\n%s\n%s", stdout, stderr, err)
} }
if stderr != "" {
log.Println(colour.DarkYellow(stderr))
}
return stdout, nil return stdout, nil
} }

View File

@ -199,6 +199,8 @@ func GenerateBindings(buildOptions *Options) error {
output, err := bindings.GenerateBindings(bindings.Options{ output, err := bindings.GenerateBindings(bindings.Options{
Tags: buildOptions.UserTags, Tags: buildOptions.UserTags,
GoModTidy: !buildOptions.SkipModTidy, GoModTidy: !buildOptions.SkipModTidy,
TsPrefix: buildOptions.ProjectData.Bindings.TsGeneration.Prefix,
TsSuffix: buildOptions.ProjectData.Bindings.TsGeneration.Suffix,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `OpenInspectorOnStartup` to debug options to allow opening the WebInspector during startup of the application in debug mode. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2080) - Added `OpenInspectorOnStartup` to debug options to allow opening the WebInspector during startup of the application in debug mode. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2080)
- On macOS `wails doctor` now also shows the version of Xcode installed. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2089) - On macOS `wails doctor` now also shows the version of Xcode installed. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2089)
- The [AssetServer](/docs/reference/options#assetserver) now supports handling range-requests if the [Assets](/docs/reference/options/#assets-1) `fs.FS` provides an `io.ReadSeeker`. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2091) - The [AssetServer](/docs/reference/options#assetserver) now supports handling range-requests if the [Assets](/docs/reference/options/#assets-1) `fs.FS` provides an `io.ReadSeeker`. Added by @stffabi in [PR](https://github.com/wailsapp/wails/pull/2091)
- Add new property for the `wails.json` config file - `bindings`. More information on the new property can be found in the updated [schema](static/schemas/config.v2.json). Properties `prefix` and `suffix` allow you to control the generated TypeScript entity name in the `model.ts` file. Added by @OlegGulevskyy in [PR](https://github.com/wailsapp/wails/pull/2101)
- The `WindowSetAlwaysOnTop` method is now exposed in the JS runtime. Fixed by @gotid in [PR](https://github.com/wailsapp/wails/pull/2128) - The `WindowSetAlwaysOnTop` method is now exposed in the JS runtime. Fixed by @gotid in [PR](https://github.com/wailsapp/wails/pull/2128)
### Fixed ### Fixed

View File

@ -226,5 +226,25 @@
} }
} }
} }
} },
"bindings": {
"type": "object",
"description": "Bindings configurations",
"properties": {
"ts_generation": {
"type": "object",
"description": "model.ts file generation config",
"properties": {
"prefix": {
"type": "string",
"description": "All generated JavaScript entities will be prefixed with this value"
},
"suffix": {
"type": "string",
"description": "All generated JavaScript entities will be suffixed with this value"
}
}
}
}
}
} }