Go 1.11+ 原生支持 WebAssembly,但需显式指定 GOOS=js 和 GOARCH=wasm 编译,且必须配合 wasm_exec.js 胶水代码运行;main 函数须阻塞(如 select{}),不支持 cgo,标准库功能受限。

Go 1.11+ 原生支持 WebAssembly,但默认不启用
Go 从 1.11 版本起内置了 wasm 目标平台,但需要显式指定 GOOS 和 GOARCH 才能触发编译。直接运行 go build 不会生成 wasm 文件——它会静默忽略目标平台,甚至可能输出一个空文件或报错 build constraints exclude all Go files。
关键点是:必须用 GOOS=js GOARCH=wasm go build -o main.wasm main.go,且 Go 源码中不能依赖非纯 Go 的标准库(如 os/exec、net/http 的某些底层实现)。
-
GOOS=js是固定写法,不是web或wasm;GOARCH=wasm才是目标架构 - Go 1.20+ 默认使用
tinygo风格的 JS 支持层,但依然依赖$GOROOT/misc/wasm/wasm_exec.js - 编译出的
.wasm文件不能直接运行,必须配合 JavaScript 胶水代码加载
必须搭配 wasm_exec.js 才能运行
Go 编译出的 main.wasm 本身不含运行时初始化逻辑,也不处理 I/O、定时器、GC 触发等。它依赖 wasm_exec.js 提供的宿主环境桥接。这个文件随 Go 安装自带,路径通常是 $GOROOT/misc/wasm/wasm_exec.js。
常见错误是直接在 HTML 中 fetch 并 instantiate wasm,结果报错 runtime: failed to create new OS thread 或卡在 Go program has not yet been initialized——根本原因是没加载胶水脚本,或没按约定调用 go.run()。
- 需将
wasm_exec.js复制到项目静态资源目录(如./static/wasm_exec.js) - HTML 中先
<script src="wasm_exec.js"></script>,再创建const go = new Go();实例 - 加载 wasm 后必须调用
go.run(instance),否则 Go runtime 不启动 - 若用
WebAssembly.instantiateStreaming,注意 MIME 类型要配对(application/wasm)
main 函数必须阻塞,否则程序立即退出
Go 的 wasm 程序不像服务端那样有事件循环自动维持进程。一旦 main() 函数返回,WebAssembly 实例就终止,所有 goroutine 被强制清理——哪怕还有 http.Server 在 listen 或 timer 在等待。
典型表现是:控制台看到 “Program exited”、JS 控制台无报错但页面没响应、goroutine 里的 fmt.Println 只输出一次就消失。
- 最简解法:
select {}让main()永久阻塞 - 若需响应外部事件(如按钮点击),应通过
syscall/js.FuncOf注册回调,并在回调里启动 goroutine,但main()仍需阻塞 - 不要用
time.Sleep替代阻塞——它在 wasm 下精度差,且无法防止 runtime 退出 - 使用
js.Global().Get("console").Call("log", "hello")替代fmt.Println可避免部分 runtime 初始化问题
不支持 cgo,且 net/http 等包功能受限
WebAssembly 目标下 cgo 被完全禁用(CGO_ENABLED=0 强制生效),所以任何含 // #include 或调用 C 函数的代码都会编译失败。同时,标准库中依赖操作系统能力的部分被 stub 化或 panic。
例如:net/http 可以发起请求,但仅限 fetch API 支持的协议(HTTP/HTTPS),不支持 Unix socket、自定义 TLS 配置、或 http.Transport 的底层字段;os.OpenFile 直接 panic;time.Ticker 可用,但 time.AfterFunc 在某些旧浏览器上有兼容性问题。
- 避免使用
os/exec、os/user、net/rpc、plugin等包 - 文件操作请改用
syscall/js调用浏览器 File API - HTTP 请求建议用
fetch+syscall/js封装,比原生http.Client更可控 - 若需高性能 JSON 处理,优先用
encoding/json,而非第三方 cgo 绑定库
WebAssembly 不是“把 Go 服务端代码扔进浏览器就能跑”,它本质是受限沙箱环境。真正难的不是编译那条命令,而是把 Go 的并发模型、I/O 抽象和浏览器事件循环对齐——尤其是 goroutine 生命周期和 JS Promise 的衔接,这点官方文档几乎不提,只能靠踩坑验证。


















