Guides
Native UI in browsers
The same app in a page, compiled to WebAssembly and drawn with WebGL 2.
An app of native UI also runs in a browser, compiled to WebAssembly: its windows show on canvases of a page, drawn with WebGL 2 by the same renderer as on Linux, with the same shaders. Nothing changes in the app:
func main() {
s := &counter{}
mygo.App.WhenReady(func() {
mygo.NewWindow(mygo.WindowOptions{Title: "Counter", Content: ui.View(s.view)})
})
if err := mygo.App.Run(); err != nil {
log.Fatal(err)
}
}mygo dev -web develops it in the browser: it compiles the app to
WebAssembly, serves it at http://localhost:5174, opens it, and rebuilds it
as you edit, which reloads the page. mygo build -platform web builds it
into <out>/web, a static site to serve from anywhere: index.html, Go's
wasm_exec.js and the app's WebAssembly (see the CLI).
mygo dev -web
mygo build -platform webWithout the CLI, build it for js/wasm, with Go's loader of WebAssembly
beside it:
GOOS=js GOARCH=wasm go build -o app.wasm .
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .and load it from a page:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>html, body { margin: 0; height: 100%; }</style>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("app.wasm"), go.importObject).then((r) => go.run(r.instance));
</script>
</head>
<body></body>
</html>Serve the directory over HTTP, with WebAssembly as application/wasm, as
most servers do. A window fills the element with the ID mygo, or else
the page; a window opened later covers the earlier ones. The page sets the
window's size: the size, position and title bar options do nothing, and
the title of the window opened last is the page's.
What works
- Drawing. Frames draw with WebGL 2 at the device pixel ratio, once for each of the browser's frames that something changed in; effects, as Liquid Glass, draw as on the desktop. Where the browser has no WebGL 2, frames draw in memory and show through a 2D canvas. A lost WebGL context draws again once the browser restores it.
- Text comes from the browser, which finds the fonts, falls back for
what one lacks and shapes, as for its pages:
system-uiis the system's font, as a page's. Lines break and runs order (bidi) as in package text everywhere. Text is not drawn with subpixel antialiasing. - Input: the pointer, with the clicks counted; the wheel and touchpads; keys; input methods, which compose in a text area out of sight at the caret; and the clipboard's text, through the browser's copy, cut and paste. On a Mac, ⌘ is Control for the view's shortcuts, which take a browser for a system other than macOS.
- Routers. The first router a window shows follows
the tab's history, in the URL's fragment, as
#/notes/42: the browser's back and forward buttons go through its pages, and a link to a page, or a reload, opens the app there. - Files dropped on a window:
DroppedFiles,FileDragOverandOnFileDropwork as on desktops. Browsers give no paths, so the backend reads the files as they drop and keeps them at paths of its own, as/mygo-drop/3/photo.png, whichos.Open,os.ReadFile,os.Statandos.ReadDirread; the files of the last 16 drops stay. - Context menus show as menus of HTML on the page.
- Dark mode, reduced motion and more contrast follow the browser's
settings, and change with them. A page can choose the appearance itself
with
data-mygo-theme="dark"or"light"on its root element, as one framing the app does to give it its own theme (a frame'sprefers-color-schemefollows the browser, not the page around it). Dialog.Messageasks with the browser'salertandconfirm, andShell.OpenExternalopens a tab.
What does not
Windows of web pages, application and Dock menus, trays, notifications,
global shortcuts, file dialogs and the drag and drop of data other than
files have nothing
in a browser to stand for them, and report so. Assistive technology does
not see the content yet. Of a style's OpenType features, canvases apply
kern only. A font added with ui.RegisterFont needs its family, and
draws once the browser loaded it. The clipboard's text comes from the
system when a paste brings it: the view's own Paste command pastes what
the app copied last.
Size
The counter example is 8.2 MB of WebAssembly, 2.2 MB compressed with
gzip, and the previews of these docs, with every component, 9.8 MB
(2.6 MB). Builds for browsers leave out what package mygo has for web
pages, which they do not show: its IPC, custom schemes and their HTTP, the
TypeScript generator, the updater, with the JSON and the cryptography
they take. Their API stays, doing nothing: Bind binds nothing, events
reach no page, Updater.Check reports ErrUpdatesDisabled. Only
Protocol.Handle, HandleFunc and FileServer, which take net/http's
types, are missing, so that code using them is for desktops
(//go:build !js). The tag mygo_noinspector, which mygo build sets
for production, leaves out the inspector of native UI too, some 350 KB.
Building for browsers adds nothing to apps for desktops: the browser's
code is only in js/wasm builds.
Under the hood
Go's main goroutine runs the event loop; the browser's events reach it through the functions JavaScript calls, which wait for it to handle them, so that they can still prevent the browser's defaults. The architecture guide's Browsers section has the details.