Skip to content

浏览器中的原生 UI ​

原生 UI 应用编译为 WebAssembly 后也能在浏览器中运行:它的窗口显示在 页面的 canvas 上,由与 Linux 相同的渲染器用 WebGL 2 和相同的着色器 绘制。应用本身无需任何改动:

go
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 在浏览器中进行开发:它把应用编译为 WebAssembly,在 http://localhost:5174 提供服务并打开它,你编辑时自动重新构建并重新 加载页面。mygo build -platform web 把它构建到 <out>/web,一个可以 从任何地方提供的静态站点:index.html、Go 的 wasm_exec.js 和应用的 WebAssembly(参见 CLI)。

sh
mygo dev -web
mygo build -platform web

不使用 CLI 时,为 js/wasm 构建,并把 Go 的 WebAssembly 加载器放在 旁边:

sh
GOOS=js GOARCH=wasm go build -o app.wasm .
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .

然后在页面中加载它:

html
<!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>

通过 HTTP 提供该目录,WebAssembly 的类型为 application/wasm,大多数 服务器默认如此。窗口填满 ID 为 mygo 的元素,没有该元素时填满页面; 之后打开的窗口覆盖先前的窗口。窗口大小由页面决定:大小、位置和标题栏 选项都无效,最后打开的窗口的标题就是页面的标题。

可用的功能 ​

  • 绘制。 帧以设备像素比用 WebGL 2 绘制,浏览器每一帧有变化时绘制 一次;特效,如 Liquid Glass,与桌面上一样 绘制。浏览器没有 WebGL 2 时,帧绘制在内存中,通过 2D canvas 显示。 WebGL 上下文丢失后,浏览器恢复它时即重新绘制。
  • 文本来自浏览器:浏览器负责查找字体、为字体缺少的字形回退并进行 文字排版,与渲染页面相同:system-ui 就是系统字体,和页面里一样。 断行与文字段(bidi)的顺序在所有地方都与 text 包一致。文本不使用 亚像素抗锯齿绘制。
  • 输入:指针(点击次数会被统计);滚轮和触控板;按键;输入法, 它们在一个位于光标处、不可见的文本区域中组合文本;以及剪贴板文本, 通过浏览器的复制、剪切和粘贴。在 Mac 上,视图的快捷键中 ⌘ 对应 Control,因为视图把浏览器当作非 macOS 的系统。
  • 路由器。 窗口显示的第一个路由器跟随标签页的 历史记录,记录在 URL 的片段中,如 #/notes/42:浏览器的前进和后退 按钮在其页面之间导航,指向某个页面的链接或重新加载都会在那里打开 应用。
  • 拖放到窗口上的文件:DroppedFiles、FileDragOver 和 OnFileDrop 与桌面上一样工作。浏览器不提供路径,因此后端在文件 落下时读取它们,并按自己的路径保存,如 /mygo-drop/3/photo.png, 可以用 os.Open、os.ReadFile、os.Stat 和 os.ReadDir 读取; 最近 16 次拖放的文件会保留。
  • 上下文菜单以页面上的 HTML 菜单形式显示。
  • 深色模式、减弱动态效果和增强对比度跟随浏览器的设置,并随之变化。 页面可以在其根元素上用 data-mygo-theme="dark" 或 "light" 自行 选择外观,正如包裹应用的框架为它指定自己的主题时那样(框架的 prefers-color-scheme 跟随浏览器,而不是它周围的页面)。
  • Dialog.Message 用浏览器的 alert 和 confirm 询问, Shell.OpenExternal 打开一个新标签页。

不可用的功能 ​

网页窗口、应用和 Dock 菜单、托盘、通知、全局快捷键、文件对话框,以及 文件之外数据的拖放,在浏览器中没有对应的东西,调用时会报告这一点。 辅助技术目前还看不到内容。样式的 OpenType 特性中,canvas 只应用 kern。用 ui.RegisterFont 添加的字体需要其 family,浏览器加载后 即可绘制。剪贴板文本在粘贴时由系统带入:视图自己的粘贴命令粘贴的是 应用上次复制的内容。

体积 ​

计数器示例的 WebAssembly 为 8.2 MB,gzip 压缩后 2.2 MB;本文档的预览 包含所有组件,为 9.8 MB(压缩后 2.6 MB)。浏览器构建会剔除 mygo 包中 服务于网页(它们并不显示网页)的部分:IPC、自定义 scheme 及其 HTTP、 TypeScript 生成器、更新器,以及它们所依赖的 JSON 和加密代码。它们的 API 仍然存在但什么都不做:Bind 不绑定任何东西,事件不会到达任何页面, Updater.Check 报告 ErrUpdatesDisabled。只有 Protocol.Handle、 HandleFunc 和 FileServer 缺失(它们接受 net/http 的类型),因此 使用它们的代码只适用于桌面(//go:build !js)。mygo build 为生产 构建设置的 mygo_noinspector 标签也会剔除原生 UI 检查器,约 350 KB。 为浏览器构建不会给桌面应用增加任何东西:浏览器的代码只存在于 js/wasm 构建中。

工作原理 ​

Go 的主 goroutine 运行事件循环;浏览器的事件通过 JavaScript 调用的 函数传到它那里,这些函数会等待它处理,以便仍能阻止浏览器的默认行为。 架构指南的浏览器一节有 细节。