关于go:从零开始Wails2编写Web桌面应用

44次阅读

共计 6791 个字符,预计需要花费 17 分钟才能阅读完成。

从零开始 Wails2 编写 Web 桌面利用

前端要写桌面利用的话首先想到的必定是 Electron,Electron 的利用成熟度曾经半信半疑,但包体积始终是个令人头疼的问题。如果很在意体积问题,而且喜爱尝试新技术的话,在古代其余编程语言一直侵入前端生态的状况下,咱们抉择的眼光也不肯定要局限在 JavaScript 上。

Wails 就是基于 Go+Web 技术的桌面应用程序生成的我的项目,其前端渲染层应用的是脱胎于 Edge 的 WebView2,相比于 Electron,打包编译体积小了十分多。微软的 Teams 我的项目在此前从 Electron 切换到 Webview2 内存耗费间接减半。两者更多的差别 Electron Blog 上有一篇文章能够查看:WebView2 and Electron。(Rust 生态中热门的 Tauri 我的项目也是应用 Webview2 作为渲染)

如果你是第一次接触 Go 或是查看 Wails 基础教程,能够配合 wails-data-filter 我的项目代码,从零开始,感性认识一下如何实现一个简略的桌面利用:

装置环境

指标平台是 Windows10,咱们就在这里制作编译桌面利用。

须要装置两个依赖:

  • NPM (Node 15+)

    这个前端应该很相熟了,抉择版本适宜的 Node 即可。

  • Go 1.17+

    从 https://go.dev/dl/ 下载 Windows 安装包。

    装置胜利后在命令行下输出

    go version

    如果返回版本信息即装置胜利。

    如果报错提醒找不到,则须要检查一下零碎环境变量 Path 里是否有 Go 装置的目录,Windows 默认装置在C:\Program Files\Go。如果存在,能够尝试从新关上命令行窗口或是重启一下。

    几个相干的环境变量:

    • GOROOT,装置目录门路
    • GOPATH,开发工作区门路
    • GOBIN,编译寄存的门路
  • Wails

    当能够运行 Go 之后就能够下载 Go 程序了,执行命令

    go install github.com/wailsapp/wails/v2/cmd/[email protected]

    装置胜利后能够输出

    wails doctor

    查看 wails 命令是否能正确运行,同时也会输入检查报告。按着提醒做相应操作即可。

创立我的项目

wails 提供了创立命令init,比方须要生成 Vue 我的项目

wails init -n wails-vue -t vue

须要应用 TypeScript 的话

wails init -n wails-vue -t vue-ts

React、原生 JavaScript 也一样,具体能够查看官网文档。

目录构造

├── build/              # 我的项目构建目录
│   ├── appicon.png     
│   ├── darwin/
│   └── windows/
├── frontend/           # 前端我的项目目录 能够放任意前端我的项目
│   └── wailsjs/          # wails 与前端通信的工具办法 会主动同步 Go 代码中的公共办法
├── go.mod              # Go 的依赖管理文件
├── go.sum              # Go 的依赖树校验文件
├── main.go             # 我的项目入口文件
├── app.go              # 我的项目 App 构造体定义
└── wails.json          # wails 配置文件

首次应用

Go 是类 C 语言,但比 C 语言弱小了不少,在编译、效率、开发难易度中尽力做出了均衡。所以有了 C 语言根底,对于 Go 的上手就能轻松不少。

没学过也不必放心,只有多看看根底文档,相熟一下语法格调也能很快上手。而且开发中除非波及到与零碎交互 JavaScript 没方法解决,否则能够不编写 Go 代码。

main.go中寄存着我的项目入口 main() 函数,其中只有相干配置须要咱们依据理论状况做调整。

思考到如果第一次接触 Go 语言,间接从编写逻辑开始可能会有一点抗拒,所以还是先看看 main.go 代码,理解一下 Go 语法的样子:

package main

import (
  "embed"

  "github.com/wailsapp/wails/v2"
  "github.com/wailsapp/wails/v2/pkg/options"
)

//go:embed frontend/dist
var assets embed.FS

func main() {
  // Create an instance of the app structure
  app := NewApp()

  // Create application with options
  err := wails.Run(&options.App{
    Title:            "数据过滤工具",
    Width:            640,
    Height:           480,
    Assets:           assets,
    BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
    OnStartup:        app.startup,
    Bind: []interface{}{app,},
  })

  if err != nil {println("Error:", err.Error())
  }
}
  • package

    package 用来指定包名,能够视为命名空间。在不同文件中如果在同一个包名下,之间的定义是能够相互调用的。

    package 必须在第一行且小写。对于可执行程序必须要有 main 包,并且有且只有一个 main() 函数。如果没有 main() 函数,在应用程序启动时将会报错。

  • import

    import 用来导入其余包。像是以上代码就导入了三个包,一个内置的 embed 用来将动态资源嵌入程序中,另外两个就是 wails。

  • var

    var是申明变量,var assets embed.FS即是申明 assets 类型为 embed.FS 的变量。这里应用了 embed 包提供的指令 //go:embed,将指定门路资源转换为embed.FS 类型(匹配多个文件时应用)并绑定到变量上,这里即绑定了前端目录下的 dist 目录中所有的资源。

  • main 函数逻辑

    • :=

      在 Go 语言中,除了应用 var 申明变量外,还能够应用简写办法 :=,一次性申明并赋值。NewApp() 即是来自同个包名的另一个文件app.go,临时先不关注。

    • wails

      wails.Run 即是调用 wails 的启动办法,其中参数是 wails 提供的配置,能够自定义调整。具体的配置能够看文档参数选项。

    • if err != nil

      Go 中的 if 语句 写法,无需括号。nil代表空值。

启动文件只有配置信息须要依据理论状况批改,其余的放弃默认即可。

来试着运行一下,输出编译命令:

wails build

期待编译胜利后,在 build/bin 目录下会生成 exe 文件,双击即可运行。此时可能看到前端我的项目的网页,这样一个独立的前端 Web 利用就生成了。

开发

既然分为前端与 Go 我的项目,那么必定得先晓得两者是通过什么形式进行通信的。

前端与 Go 相互通信

应用事件通信

当 Go 须要被动推送音讯给前端时,或者前端推送音讯给 Go 时,能够应用事件函数。

发送事件:EventsEmit

监听事件:EventsOnEventsOnceEventsOnMultiple

Go 中是在 runtime 中调用,前端是在 window.runtime 中调用(wailsjs/runtime下曾经列举了 runtime 所有可用函数)。

间接调用 Go 函数

前端能够间接调用 Go 办法。wails 会在 window 对象上绑定 go 对象,其中会裸露相干公共函数。

默认代码有提供 App 构造体的一个公共函数Greet,按以下门路拜访

window.go.main.App.Greet

返回的是一个 promis 对象。

在前端目录下有一个 wailsjs 文件夹,外面有两个文件夹runtimego

wailsjs/runtime中提供了 wails 内置的公共函数。

wailsjs/go中提供了构造体对应的公共函数,当 go 代码变动时,这里文件会自动更新。

为了导出不便,咱们能够在根目录创立一个 index.js 文件,对立导出子目录中的办法。

export * from "./go/main/App";
export * from "./runtime/runtime";

在应用时就不必从 window 里写一长串代码调用了,只需 import 即可

import {Greet} from "../wailsjs";

其余内置函数也以同样形式调用。

向 Go 中 增加公共函数

如何增加公共函数供前端调用?这就须要看一看 app.go 文件了

package main

import (
  "context"
  "fmt"
)

// App struct
type App struct {ctx context.Context}

// NewApp creates a new App application struct
func NewApp() *App {return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {a.ctx = ctx}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {return fmt.Sprintf("Hello %s, It's show time!", name)
}
  • struct

    Go 中没有类的概念,对于复合型数据,特地是还须要带有函数的数据,须要应用构造体来实现。

    应用语法与前端对象看起来差不多。

    type 名称 struct {成员名称 成员类型}

    但增加函数就差异比拟大了,没方法在构造体里间接定义函数,须要应用 func 关键字与构造体绑定。

    func (a *App) startup(ctx context.Context) {a.ctx = ctx}

    如果是一般函数申明

    func startup(ctx context.Context) {//}

    二者差别就在 func 之后的括号,追随的是须要绑定的构造体定义,a是构造体在函数内的调用名。

  • 变量名命名标准

    Go 为了简化语法,变量名是否可内部拜访依赖首字母是否大写来判断。

    func (a *App) Greet(name string) string {return fmt.Sprintf("Hello %s, It's show time!", name)
    }

    Greet 绝对于下面的函数就是以首字母大写结尾,在编译时也会主动生成到前端文件中供调用。

    所以须要增加函数,只须要将此代码复制一下,批改成所需名称,只有放弃首字母大写即可被前端拜访到。

通信数据转换

Go 接管前端返回的值会波及到类型问题。wails 会做主动类型转换。大抵如下:

  • 数组、切片:JavaScript 数组
  • Map:JavaScript 对象
  • 构造体:JavaScript Map 对象

开发模式

开发的时候应用 dev 命令会主动编译构建

wails dev

程序会主动监控,Go 与前端资源变动后都会主动刷新。

接下来就很简略了,就不太具体的形容了。

前端页面

这里应用 Vue3+Tailwindcss+Daisyui 构建前端页面。这里默认大家都有必要的前端根底了,代码也简单,过程就省略了,实现的页面款式:

次要性能就是点击按钮,让用户抉择文件,按配置的规定会过滤其中的数据,最初在文件当前目录下生成解决后的文件。局部关键点进行阐明,残缺代码能够查看我的项目:

获取文件门路

因为平安限度用浏览器的 type 为 file 的 input 无奈获取到文件实在门路。好在 wails 提供了调用零碎抉择文件的办法,调用 runtime.OpenFileDialog 即可关上零碎抉择文件弹窗。

app.go 文件中增加代码

// SelectFile 抉择须要解决的文件
func (a *App) SelectFile(filetype string) string {
  if filetype == "" {filetype = "*.txt;*.json"}
  selection, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
    Title: "抉择文件",
    Filters: []runtime.FileFilter{
      {
        DisplayName: "文本数据",
        Pattern:     filetype,
      },
    },
  })
  if err != nil {return fmt.Sprintf("err %s!", err)
  }
  return selection
}

之后从 wailsjs 文件夹中导入即可应用

import {SelectFile} from "../../wailsjs";

const handleSelectFile = async () => {const path = await SelectFile();
  console.log("抉择文件的门路", path)
};
超大文件读取

对于文本处理,如果用户上传的一个超大文件,一次性读取会占用过多内存导致问题。能够换成逐行读取,节约解决内存

// FilterFile 解决文件数据
func (a *App) FilterFile(filePath string) string {fileHandle, err := os.Open(filePath)
  if err != nil {fmt.Println(err.Error())
    return ""
  }
  defer fileHandle.Close()
  
  accumulationLine := 0
  lineScanner := bufio.NewScanner(fileHandle)
  for lineScanner.Scan() {newStr := lineScanner.Text()
    accumulationLine++
    runtime.EventsEmit(a.ctx, "filter-change", accumulationLine)
    // ...
  }
}

这里同时调用 runtime.EventsEmit 向前端推送事件,提供正在读取的行数信息。

编码问题

文本文件会遇到各种编码,Go 中默认是以 UTF- 8 保留读取。如果遇到 GBK 编码就会乱码了。好在有一些计划能够解决。

判断是否是 UTF- 8 编码:

import "unicode/utf8"

utf8.ValidString(str)

GBK 转 UTF- 8 编码:

import (
  "bytes"
  "golang.org/x/text/encoding/simplifiedchinese"
  "golang.org/x/text/transform"
  "io/ioutil"
)

func GbkToUtf8(s []byte) ([]byte, error) {reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())
  t, e := ioutil.ReadAll(reader)
  if e != nil {return nil, e}
  return t, nil
}

理论的性能编写就如写平常前端一样,须要存储数据、一些前端不不便做的操作,写在 Go 里,前端像是调用接口一样传参调用即可。

无边框界面

wails 提供无边框模式,只须要在 wails.Run 配置中增加 Frameless 配置即可

  err := wails.Run(&options.App{
    Title:            "数据过滤工具",
    Width:            640,
    Height:           480,
    Assets:           assets,
    BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
    OnStartup:        app.startup,
    Frameless:        true, // 无边框模式
    DisableResize:    true,
    Bind: []interface{}{app,},
  })

启动后应用程序就没用边框,此时因为没有边框,所以程序没有可拖动窗体的区域。

只须要在要拖动的元素上减少 data-wails-no-drag 属性,即可从新取得拖动能力。如果其中子元素不须要响应拖动,则增加data-wails-no-drag

生成应用程序

wails build

Webview2 是调用零碎自带的,如果零碎未携带,或者版本太旧就很麻烦了。编译有提供 -webview2 命令,能够配置这种状况下的解决计划。一共有四种:Error(报错),Browser(关上浏览器疏导程序下载页)、Download(主动下载疏导程序并运行)、Embed(内嵌文件)。

但我理论测试了一下,编译文件变大了(大概减少 150k 体积),换了台 Win10 运行时仍然须要网络下载,不确定是否要同时配置什么参数。

更换图标

更换 build/windows 下的 icon.ico 文件,打包时就会应用新的图标了。

报毒问题

如果在应用 360 安全卫士的电脑上会间接报毒,这个也是制作小工具最令人头疼的中央,要不然让对方将软件退出白名单,要不然只能给 360 提交误报了。

链接

Wails 官网

wails-data-filter 我的项目

正文完
 0