go 1.21+ 虽稳定支持 wasm,但需用 goos=js goarch=wasm 编译并配合 syscall/js.wait() 和官方 main_js.js 胶水代码才能在浏览器运行;直接 go build -buildmode=exe 生成的 wasm 不可被 instantiatestreaming() 加载。

Go 1.21+ 直接支持 WASM,但默认不生成可运行的 main.wasm
Go 官方从 1.11 开始实验性支持 WebAssembly,到 1.21 已稳定,但很多人卡在“编译成功却打不开”——根本原因是:go build -o main.wasm -buildmode=exe 生成的是一个带 Go 运行时的完整 wasm 模块,但它**不能直接被浏览器 WebAssembly.instantiateStreaming() 加载**,因为缺少 JS 胶水代码和内存初始化逻辑。
真正能跑起来的流程是:Go 编译出 wasm + 用 syscall/js 写入口 + 浏览器加载配套 JS。没这层胶水,main.wasm 就是个裸二进制,浏览器报 RuntimeError: unreachable 或直接白屏。
- 必须用
GOOS=js GOARCH=wasm go build -o main.wasm main.go,不是-buildmode=exe -
main.go必须调用syscall/js.SetFinalize或阻塞住(比如select{}),否则程序瞬间退出 - 浏览器里不能直接
fetch('main.wasm'),得用 Go 提供的golang.org/x/exp/shiny/driver/wasm/main_js.js或自己写轻量胶水
为什么 main.go 里必须写 syscall/js.Wait()?
Go 的 WASM 目标不是生成独立可执行 wasm,而是和 JS 协同运行。Go runtime 启动后会立即尝试退出,除非你显式告诉它“等 JS 调用我”。syscall/js.Wait() 就是这个作用:它让 Go 协程挂起,把控制权交还给 JS 事件循环,之后所有 Go 函数都只能通过 JS 显式调用(比如 global.GoFunc = function() { ... })。
不加这句,你会看到 wasm 加载一闪而过,控制台没报错但什么也不发生——因为 Go 主 goroutine 已经结束了。
- 替代写法:
select{}也行,但语义不如syscall/js.Wait()清晰 - 如果用了
http.ListenAndServe等阻塞服务,不用额外Wait(),但浏览器环境不支持net包的 TCP 监听 -
syscall/js.Wait()是单次调用,不可重复;想响应多次 JS 调用,得提前注册回调函数
浏览器加载时常见的 LinkError 和内存配置问题
典型错误是:LinkError: WebAssembly.instantiate(): Import #0 module="go" error: module is not an object or function。这说明 JS 胶水代码没正确提供 Go 所需的导入对象(env, go 等),或者 wasm 文件路径不对。
另一个高频问题是内存越界:RangeError: WebAssembly.Memory(): Memory size must be within bounds。Go 默认申请 2GB 内存(65536 pages),但浏览器限制通常只有 4KB–64KB 初始页,且不能超限增长。
- 必须用 Go 自带的
main_js.js(位于$GOROOT/misc/wasm/main_js.js),别手写导入对象 - 加载时要确保
fetch('main.wasm')返回的是Response对象,不是文本;用WebAssembly.instantiateStreaming()而非instantiate() - 如需控制内存,可在 JS 胶水里改
memory初始化参数,但更稳妥的做法是避免大数组/切片分配,Go 的 GC 在 wasm 下较弱
用 wazero 或 wapc 运行 Go wasm 更简单?
如果你不是在浏览器里跑,而是在 Node.js、CLI 或服务端运行 Go 编译的 wasm,GOOS=js GOARCH=wasm 编出来的模块依然不行——它依赖浏览器 JS API(setTimeout, document 等)。这时候得换目标:
Go 官方目前**不支持 GOOS=wasi**(WASI 是通用 wasm 接口标准),所以无法直接生成能在 wazero 或 wapc 里跑的 Go wasm。可行路径只有一条:用 TinyGo。
- TinyGo 支持
tinygo build -o main.wasm -target wasi main.go,输出的是标准 WASI 模块,wazero可直接运行 - 但 TinyGo 不兼容全部 Go 标准库(比如
reflect,net/http部分功能缺失) - 若坚持用标准 Go,唯一选择是自己实现 WASI syscall stub,成本远高于换工具链
真要服务端跑 wasm,别硬扛 Go 原生 wasm,TinyGo 是目前最现实的出口。浏览器里用原生 Go wasm 没问题,但每一步胶水、内存、阻塞都得对上,漏一个就静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











