一个轻量级、跨平台的 Go 语言 WebView 库(无 CGO),基于 purego 驱动。
gview 是 webview/webview 的 Go 语言绑定,使用 purego 实现,完全无需 CGO。它内置了 Windows、macOS 和 Linux 的原生库,开箱即用。
| 特性 | 描述 |
|---|---|
| 无 CGO | 纯 Go 实现,无需安装 GCC/MinGW 等编译环境 |
| 跨平台 | 支持 Windows (WebView2), macOS (WebKit), Linux (WebKitGTK) |
| 线程安全 | UI 操作全链路线程安全,内置高频 Dispatch 批处理优化 |
| 现代通信 | 内置双向 RPC、事件总线 (EventBus) 及 SSE/WebSocket 高速数据通道 |
| 内置运行库 | 各平台原生库已内嵌,支持自动提取与零依赖分发 |
| 灵活配置 | 支持自定义提取目录、RPC 超时、日志接口及深度的性能调优 |
| 多窗口管理 | 内置 WindowManager 助手,轻松管理多窗口应用 |
得益于 purego 和内嵌资源技术,gview 实现了真正的 单文件分发。开发者只需编译一个可执行文件,即可在目标机器上自动提取并运行原生 WebView 环境。
针对不同场景设计的三层通信方案:
| 通道 | 适用场景 | 特点 |
|---|---|---|
| RPC | 强逻辑交互 | 支持返回值和错误处理,适合 API 调用 |
| EventBus | 事件广播 | 多窗口高效事件分发,避免冗余序列化 |
| SSE/WebSocket | 实时数据流 | 非阻塞推送,支持 100Hz+ 高频更新 |
- 内置 UI 线程调度批处理优化
- 自动处理资源回收
- 针对 Windows 平台处理复杂的库加载与线程锁定问题
- 有效防止窗口"未响应"或进程残留
- 严格过滤 JS 注入风险
- 本地 SSE 数据服务引入 32 字节随机 Token 验证
- 仅允许 localhost 访问数据服务
gview/
├── assets/ # 嵌入资源
│ ├── gview_core.js # 核心 JS 库
│ ├── webview_helpers.js # 辅助函数库
│ ├── htmx.min.js # HTMX 库
│ ├── early_error_suppress.js
│ ├── VERSION.txt
│ ├── windows_amd64/ # Windows 原生库
│ ├── darwin_amd64/ # macOS Intel 原生库
│ ├── darwin_arm64/ # macOS ARM 原生库
│ ├── linux_amd64/ # Linux AMD64 原生库
│ └── linux_arm64/ # Linux ARM64 原生库
├── cmd/ # 示例应用
│ ├── demo/ # 统一演示应用
│ └── echarts/ # ECharts 集成示例
├── docs/ # 文档
├── webview.go # 核心 WebView 实现
├── interfaces.go # 接口定义
├── rpc.go # RPC 双向通信
├── eventbus.go # 事件总线
├── sse.go # SSE/WebSocket 数据流
├── native.go # 原生库加载
├── native_lib.go # 原生库符号定义
├── config.go # 配置
├── manager.go # 窗口管理器
├── embedded.go # 资源嵌入
├── embedded_config.go # 嵌入资源配置
├── errors.go # 错误定义
├── stats.go # 统计功能
├── dispatch.go # 调度器
├── schema.go # Schema 验证
├── load_windows.go # Windows 库加载
├── load_unix.go # Unix 库加载
└── webview_test.go # 测试文件
type WebView interface {
Lifecycle // 生命周期管理 (Run, Terminate, Destroy, Done, State)
Window // 窗口操作 (SetTitle, SetSize, Navigate, SetHtml, Window)
Binder // 函数绑定 (Bind, Unbind)
Caller // 函数调用 (Call, Eval, EvalRaw, Dispatch)
Emitter // 事件发射 (Emit, EmitRaw, On, Off)
Streamer // 数据流 (StartDataServer, StopDataServer, SendData, StreamJSON)
Init(js string, pattern ...string)
RegisterSchema(event string, schema SimpleSchema)
Context() context.Context
GetRuntimeStats() RuntimeStats
GetSSEStats() SSEStats
}┌─────────────────────────────────────────────────────────────────┐
│ Go Backend │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌──────────┐ ┌─────────────────────────────────┐ │
│ │ RPC │ │ EventBus │ │ SSE / WebSocket │ │
│ │ 双向调用 │ │ 事件广播 │ │ 高速数据流 │ │
│ └────┬────┘ └────┬─────┘ └───────────────┬─────────────────┘ │
│ │ │ │ │
│ │ Bind() │ Subscribe() │ SendData() │
│ │ Call() │ Publish() │ StreamJSON() │
│ ▼ ▼ ▼ │
├─────────────────────────────────────────────────────────────────┤
│ purego / Native Library │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WebView (Native) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ JavaScript Runtime ││
│ │ ┌─────────────────────────────────────────────────────────┐││
│ │ │ window.gview API │││
│ │ │ • gview.call(name, ...args) → Promise │││
│ │ │ • gview.on(event, callback) │││
│ │ │ • gview.emit(event, data) │││
│ │ │ • gview.connectSSEAuto(options) │││
│ │ │ • gview.stream.start(options) │││
│ │ └─────────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
go get gviewpackage main
import (
"log"
"gview"
)
func main() {
w, err := gview.New(gview.DefaultConfig(), gview.WithDebug(true))
if err != nil {
log.Fatal(err)
}
w.SetTitle("GView 示例")
w.SetSize(800, 600, gview.HintNone)
w.SetHtml("<h1>你好,GView</h1>")
w.Run()
}go run ./cmd/demo # 统一演示应用
go run ./cmd/echarts # ECharts 图表演示w, err := gview.New(cfg gview.Config, opts ...gview.Option) (gview.WebView, error)可用选项:
| 选项 | 说明 |
|---|---|
WithDebug(bool) |
启用调试模式 |
WithWindow(unsafe.Pointer) |
使用自定义原生窗口句柄 |
WithTitle(string) |
设置窗口标题 |
WithSize(width, height int, hint Hint) |
设置窗口大小 |
WithHTML(string) |
设置初始 HTML |
WithURL(string) |
设置初始 URL |
WithJSHelpers(bool) |
注入 JS 辅助函数 |
WithAutoStream(bool) |
自动启动数据流服务 |
WithStatusHelpers(bool) |
注入状态辅助函数 |
WithHotkeysHelpers(bool) |
注入热键辅助函数 |
WithLayoutHelpers(bool) |
注入布局辅助函数 |
w.SetTitle(title string)
w.SetSize(width, height int, hint Hint)
w.Navigate(url string)
w.SetHtml(html string)
w.Window() unsafe.PointerHint 常量:
HintNone- 默认HintMin- 最小尺寸HintMax- 最大尺寸HintFixed- 固定尺寸
w.Run() // 启动主循环(阻塞)
w.Terminate() // 终止运行
w.Destroy() // 销毁窗口
w.Done() <-chan struct{} // 获取完成通道
w.State() State // 获取当前状态
w.Context() context.ContextState 常量:
StateNone- 未创建StateCreated- 已创建StateRunning- 运行中StateTerminated- 已终止StateDestroyed- 已销毁
Go 绑定函数:
err := w.Bind("myFunc", func(a, b int) (int, error) {
return a + b, nil
})
err := w.Unbind("myFunc")Go 调用 JS:
result, err := w.Call("jsFunc", arg1, arg2)JS 调用 Go:
const result = await gview.call("myFunc", 1, 2);
const detailed = await gview.callDetailed("myFunc", 1, 2);gview.callDetailed() 返回结构化结果:成功时是 { ok: true, result, meta },失败时是 { ok: false, error, meta },其中 error.code、error.method、error.transport 可直接用于分支处理。
Go 端:
gview.Subscribe(w, "event-name")
gview.Publish("event-name", data)
gview.Unsubscribe(w, "event-name")
w.Emit("event-name", data)
w.On("event-name", func(data any) {})
w.Off("event-name")JS 端:
gview.on("event-name", (data) => console.log(data));
gview.emit("event-name", { key: "value" });
gview.off("event-name", callback);port, err := w.StartDataServer(port int, handler ...func([]byte))
err := w.StopDataServer()
port := w.DataServerPort()
w.SendData([]byte(`{"value": 42}`))
w.SendDataUnsafe([]byte(data))
w.SendDataBatch([][]byte{data1, data2})
w.StreamJSON("event-name", data)
w.RegisterSchema("event-name", gview.SimpleSchema{
Required: []string{"field1"},
Types: map[string]string{"field1": "string"},
})JS 端:
window.gview.connectSSEAuto({
transport: 'auto',
onopen: () => console.log('Connected'),
onmessage: (data) => console.log(data),
onclose: () => console.log('Disconnected')
});w.Init(js string, pattern ...string) // 注入初始化脚本
w.Eval(js string) // 执行 JS 代码
w.EvalRaw(js []byte) // 执行原始 JS 字节
w.Dispatch(f func()) // 在 UI 线程执行runtimeStats := w.GetRuntimeStats()
sseStats := w.GetSSEStats()
type RuntimeStats struct {
NumGoroutine int
MemAlloc uint64
MemTotalAlloc uint64
MemSys uint64
NumGC uint32
LastPauseNs uint64
}
type SSEStats struct {
Sent uint64
Dropped uint64
Clients int
ClientsSSE int
ClientsWS int
Published uint64
}cfg := gview.DefaultConfig()
cfg.RPCTimeout = 30 * time.Second
cfg.RPCWorkerPoolSize = runtime.NumCPU() * 2
cfg.MaxDispatchQueueLen = 10000
cfg.MaxGoStringLen = 10 * 1024 * 1024
cfg.SSEAddr = "127.0.0.1"
cfg.SSEKeepAliveInterval = 15 * time.Second
cfg.MaxSSEClients = 32
cfg.SSEClientBuffer = 64
cfg.SSEKeepAliveAsEvent = true
cfg.StatsEnabled = true
cfg.StatsInterval = 1 * time.Second
cfg.StatsEventName = "stats"
cfg.StatsIncludeRuntime = true
cfg.StatsSampleN = 1
cfg.StatsFields = []string{}
cfg.CustomExtractDir = ""
cfg.ForceExtract = false
cfg.Debug = false
cfg.Logger = nilwm := gview.NewWindowManager()
wm.Add(w)
wm.Remove(w)
wm.Has(w)
wm.Count()
wm.All()
wm.Broadcast("event", data)
wm.CloseAll()
wm.TerminateAll()htmxJS := gview.HTMXJS()
helpersJS := gview.HelpersJS()
coreJS := gview.CoreJS()
version := gview.Version()cfg := gview.GetEmbeddedConfig()
cfg.HTMX.UseExternal = true
cfg.HTMX.ExternalPath = "custom/htmx.min.js"
cfg.NativeLib.UseExternal = true
cfg.NativeLib.ExternalPath = "custom/webview.dll"
cfg.CustomExtractDir = "./libs"
cfg.ForceExtract = truego build -o myapp .go build -ldflags="-H windowsgui" -o myapp.exe .go build -tags no_embedded -o myapp .使用此标签时,需确保原生库文件存在于可执行文件目录或系统路径。
GOOS=windows GOARCH=amd64 go build -o myapp.exe .
GOOS=darwin GOARCH=arm64 go build -o myapp .
GOOS=linux GOARCH=amd64 go build -o myapp .系统自动检测客户端处理能力:
- 丢包率过高时自动启用数据合并模式
- 稳定后自动恢复正常传输
- 无需手动实现节流
w.SendDataBatch([][]byte{data1, data2, data3})参考 docs/ECHARTS_INTEGRATION.md:
- 使用增量更新 API
- 限制数据点数量
- 开启
large模式 - 利用 GView 自适应背压控制
| 平台 | 渲染引擎 | 依赖 |
|---|---|---|
| Windows 10/11 | WebView2 (Edge Chromium) | WebView2 Runtime(通常已内置) |
| macOS 10.15+ | WebKit (Cocoa) | 系统内置 |
| Linux | WebKitGTK | 需安装 libwebkit2gtk-4.0 |
sudo apt install libwebkit2gtk-4.0-dev确保已安装平台依赖:
- Windows: WebView2 Runtime
- Linux: WebKitGTK
检查 RPCTimeout 配置,确保 JS 函数存在且可调用。
检查 Token 是否正确,确保使用 window.__GO_DATA_TOKEN__。
使用 -tags no_embedded 并手动分发原生库。
设置环境变量 GVIEW_DEBUG=1 或使用 WithDebug(true)。
MIT License
核心功能:
- 无 CGO 的 WebView 绑定
- 双向 RPC 通信
- EventBus 事件总线
- SSE/WebSocket 双协议数据流
- 多窗口管理
- 内置 HTMX 支持
- 自适应背压控制
- Schema 验证
- 运行时统计
示例应用:
- 统一演示应用 (cmd/demo)
- ECharts 集成示例 (cmd/echarts)