支持WebView/Lorca、WASM和Bazel的Golang桌面应用程序

2020-08-11 15:35:49

在我构建GoLang桌面应用程序的过程中,我发现了一些有用的框架,Lorca和WebView(我在上一篇文章中写过)。这些框架创建了一个窗口,GoLang可以在其中注入HTML、CSS和JavaScript来构建UI。

但是我不想写JavaScript(!)。并处理所有随之而来的复杂性,如npm,webpack,Tyescript…。幸运的是,我只需将GoLang编译成WebAssembly(WASM)并使用它来代替JavaScript。WASM是一种可以在大多数现代浏览器中本机执行的二进制格式。我之前的帖子展示了如何用Bazel构建一个WASM web-app。

此外,在桌面应用程序(而不是Web应用程序)中使用WASM避免了它的两个主要缺点:

大型WASM二进制文件(特别是来自GoLang的):通过网络发送这些文件需要很长时间,增加了加载时间。在桌面应用程序中,二进制文件不通过网络传输,因此开销较小。

浏览器不兼容:某些WebAssembly方法在浏览器中不可用,并且根本不支持较旧的浏览器。在桌面应用程序中,“浏览器”是受控制的(用于Lorca的Chrome和用于Webview的Safari),因此不存在兼容性问题。

Func main(){//创建index.html url:=fmt.Sprintf(";data:text/html,%s";,url.PathEscape(assets.INDEX_HTML))的数据URI//创建Lorca UI,_:=lorca.New(url,";";";,600,200)defer ui.Close()//创建以。,func()string{return assets.WASM_BIN})//初始化wasm_exec.js脚本ui.Eval(assets.WASM_EXEC_JS)//调用初始JS函数加载WASM ui.Eval(assets.INIT_JS)<;-ui.Done()}。

定义作为资产承诺的函数getWASM。WASM_BIN,以Base64编码字符串形式的WASM二进制。

这是一个非常简单的示例Lorca应用程序,它基本上是一个演示示例。主要的复杂问题是资产包以及如何创建它。这就是巴泽尔的用武之地。

ASSES包包含HTML、JS和WASM文件作为字符串常量。有一些GoLang工具可以做到这一点,比如pkger或go-bindata,但是我们可以通过to_go_constant.bzl中的Bazel规则来保持简单:

Def to_go_constant(Name,Package,Constant,FILE,Base64=false):pkgStr=';<;(ECHO";Package%s";)';%Package constr=';<;(ECHO";const%s String=\`";)';%Constant Suffix=';<;(ECHO";\`";)'。%(pkgStr,conr,Suffix)If Base64:printContents=';cat$(SRCS)|Base64';Else:printContents=';cat$(SRCS)';native.genRule(name=name,SRCS=[file],outs=[name+";.go";],cmd=printContents+';|';+。

此规则使用cat获取文件并输出定义了包和常量的.go文件。如果base64=True,则文件为base64编码。

此规则的一个实际应用示例是添加从文件index.html构建的assets.INDEX_HTML:

GO_LIBRARY(名称=";GO_DEFAULT_LIBRARY";,SRCS=[";assets.GO";,";:index.go";,#Keep],import path=";github.com/.../sets";,)。

对于大多数其他文件来说,这很简单,但是对于WASM二进制数据来说,则稍微复杂一些:

To_Go_Constant(Name=";wasmbin";,Base64=True,Constant=";WASM_BIN";,FILE=";//project/wasm";,Package=";Assets";,)。

文件//project/wasm引用生成的GO_BINARY规则来编译WASM(如下所述)。Base64也为True,因此可以将二进制数据编码为常量。

注意:有一个小问题是wasm_exec.js文件,其中有几个必须替换的`。

WASM二进制文件是在浏览器中运行的客户端GoLang应用程序。此示例将Hello World<;p>;标记注入正文:

软件包Main import(";fmt";";syscall/js&34;)func main(){fmt.Println(";Hello World&34;)document:=js.Global().Get(";document";)p:=document.Call(";createElement";,";p";)p.Set(";innerHTML。)document.Get(";body";).Call(";appendChild";,p)}。

为此,go_inary规则只需要goos=";js";和goarch=";wasm&34;就可以将其输出为WASM二进制文件,例如:

GO_BINARY(name=";wasm";,embed=[";:GO_DEFAULT_LIBRARY";],goarch=";wasm";,goos=";js";,Visibility=[";//Visibility:public";],)。

此规则的输出用于上面,以创建上面的WASM_BIN常量。

LoadWebASM=()=>;{const go=new go();getWASM().Then((B64)=>;{//解码并转换为ArrayBuffer buf=Uint8Array.from(atob(B64),c=>;c.charCodeAt(0)).buffer返回WebAssembly.Instantiate(buf,go.import Object)}).Then((Result)。加载wasm失败:";+err);});}loadWebASM()。

此代码获取getWASM承诺的WASM_BIN,然后解码并将其转换为ArrayBuffer。此缓冲区被传递给WebAssembly.instantiate函数,该函数将应用程序编译并初始化为实例,然后再执行go.run。这是唯一需要的JavaScript。

以上代码就是现在编写纯(-ish)GoLang桌面应用程序所需的全部代码。这也使得将其他WASM应用程序转换为桌面应用程序变得很容易,例如,我在https://github.com/olivewind/go-webassembly-canvas将WASM应用程序转换为桌面应用程序非常简单:

有很多用GoLang编写的库和工具可以使用漂亮的用户界面,例如terraform和docker(就在我脑海中)。Web应用程序不能很好地满足他们的要求,因为他们正在编辑本地文件并与本地守护程序对话。另一种选择是重新实现或将他们的API和集成绑定到另一种语言,这是复杂的、容易出错的,并且需要大量的工作。对于这类工具来说,构建纯GoLang桌面应用程序的方法非常有意义。

这种方法可以非常快速地创建一个可分发的、单可执行的桌面应用程序,并且具有最小的外部依赖性。一款可以重用许多已经构建的GoLang库和工具的应用程序。我认为这种方法最糟糕的部分是它浪费了像Reaction和Vue这样的JavaScript工具多年的开发。但这只是意味着我必须编写更多的GO来重新实现它们,所以这并不是那么糟糕:)