Golang Desktop App: Webview vs. Lorca vs. Electron
参考文档: https://maori.geek.nz/golang-desktop-app-webview-vs-lorca-vs-electron-a5e6b2869391
I want to build a local desktop Golang app, there are a few ways to do this:
- Electron: bundled Node.js and the Chromium browser to create a packaged local web-app. Usable with Golang frameworks like go-app or go-astilectron.
- Lorca: using the locally installed Chrome driving it using its dev-tools communication protocol.
- Webview: create a native window with webview and render the app inside it using CGo bindings.
I have already written about building a simple electron app, so this post will go into how to build an app using Lorca and Webview, and then compare the three different options.
Lorca
A simple Lorca app in Go looks like:
func main() {
// Create UI with data URI
ui, _ := lorca.New("data:text/html,"+url.PathEscape(`
<html>
<head><title>Hello</title></head>
<body><h1>Hello, world!</h1></body>
</html>
`), "", 600, 200)
defer ui.Close()
// Create a GoLang function callable from JS
ui.Bind("hello", func() string { return "World!" })
// Call above `hello` function then log to the JS console
ui.Eval("hello().then( (x) => { console.log(x) })")
// Wait until UI window is closed
<-ui.Done()
}
This is remarkably simple for the complexity it is hiding! The above opens a Chome window, connects over a websocket to its dev-tools endpoint, sends the HTML to load, and provides the communication between Go and JS:
What is even more cool is that you can call a JS function inside chrome and get the output in Go(!):
n := ui.Eval(`Math.random()`).Float()
fmt.Println(n)
Using this library was so easy, so intuitive, so functional, that I was confused when it just worked. I thought there must be a catch, something complicated that I was missing. But no, it just worked.
An additional bonus is that you get the chrome dev tools to help debug any issues or adjust the layout. Also, I love the use of JS promises to implement the async calls between Go and JS, given I have been writing about promises since 2014.
The big downside to Lorca is that because it uses Chrome, some application details (like the system menu, icon, title) cannot be customized. The tradeoff is then between application polish and a simple application. Depending on what you are trying to build this might be a deal-breaker, e.g. it would be fine if you are building an internal tool, but for an enterprise application this might not look great.
Webview
参考文档: https://github.com/webview/webview_go
Webview is a library that helps building a web app directly on top of a native components. The code to do this looks like:
func main() {
w := webview.New(true)
defer w.Destroy()
w.SetSize(600, 200, webview.HintNone)
// Create a GoLang function callable from JS
w.Bind("hello", func() string { return "World!" })
// Create UI with data URI
w.Navigate(`data:text/html,
<!doctype html>
<html>
<head><title>Hello</title></head>
<body><h1>Hello, world!</h1></body>
<script> hello().then((x) => { console.log(x) }) </script>
</html>`)
w.Run()
}
This is very similar API to Lorca, which I assumed was based on webview. Though unlike Lorca, the output is a bit different:
You can see the in the above screen shot the webview application window has no drop shadow, has no border, and it is initialized in the bottom left corner of the screen. This can be customized through the Window
method that returns an unsafe.Pointer
to the OS dependent window object (NSWindow
in macOS). This is where the difficulty begins.
To work with the Window
object we must write binding from Go to the native component. For example, if we wanted our window to start centered, we would call center
on the NSWindow
. So we need to write a binding in three files (adapted from gogoa):
ns_window.go
package main
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Cocoa
//#include "ns_window.h"
import "C"
import "unsafe"
type NSWindow struct {
ptr unsafe.Pointer
}
func (self *NSWindow) Center() {
C.Center(self.ptr)
}
ns_window.h
#include <Cocoa/Cocoa.h>
void Center(void *);
ns_window.m
#include "ns_window.h"
void Center(void *self) {
NSWindow *window = self;
[window center];
}
Then in the main()
function we can center the window with:
window := NSWindow{w.Window()}
window.Center()
Unlike Lorca, webview can be fully customized for our application. The problem is that it requires a bit of work.
There are a few other parts of webview that make working with it a bit difficult:
- If using Bazel and gazelle,
webview
’s generatedBuild.bazel
file is incorrect andclinkopts = [“-framework WebKit”]
must be patched. - Calling
w.Init
only works whenw.Navigate
is called, but then thew.Eval
calls stop working. - To set the title you could write a binding as described above, or you have to use the
Dispatch
method e.g.w.Dispatch(func() { w.SetTitle("Title") })
. This is incorrect in the provided examples.
I am not sure how much of this is webview
and how much is NSWindow
. More investigation and learning on my part should make it clearer why these things are happening.
Electron
My previous post was about building a simple Electron app that looks like:
Electron is used in many large products like VSCode. This is probably because bundling everything into a single application makes portability much simpler and applications can be extensively customized. The downside of bundling the app with a browser and Node.js is that it makes the distribution very large.
Getting Golang to play with Electron is also a bit difficult. There are frameworks that make this easier, like go-astilectron, but these are complicated and mostly feature incomplete. Another way might be to use Golang compiled to WASM, which I wrote about before, but this is also not a simple solution.
The benefits of Electron are that it is portable, customizable, and battle tested for application distribution. It is just a bit complicated with Golang.
Comparison
I think the main comparison to be made is customizability vs. simplicity. Lorca is by far the simplest with very limited customizability, webview can be fully customized with some difficulty, and Electron is fully customizable but difficult to use with Golang.
Also the size of the bundle is very different between the frameworks; Lorca has a 8.7 MB binary, webview 3.7Mb, and Electron a 157Mb bundle size. The debugging tools also vary: Lorca and Electron use the Chrome dev tools, where webview uses the Safari dev tools.
Conclusion:
Both Lorca and webview work well with Golang, have small distribution sizes, and similar APIs. The main difference is between the underlying renderer being native and debug tooling.
Electron I think is probably too complicated to use with Golang without a lot of difficulty.
A potential workflow is to use Lorca during development and webview for distribution. Lorca provides familiar tooling for debugging and development, where webview provides the customizability for distribution. Lorca would also be a nice backup as a means of cross-compilation to other operating systems that webview does not support.
Note: there are still more options like wails
or gotk
* that can provide other means to build/distribute apps.*