一、 什么是 WebAssembly (Wasm)?
WebAssembly(简称 Wasm)是一种新兴的、可移植的、体积小、加载快的二进制指令格式。它允许我们使用像 C/C++/Rust/Go 这样的高性能语言编写代码,然后编译成一种特殊的文件(.wasm
),这种文件可以在所有现代浏览器中以接近原生的速度运行。
为什么选择 Wasm?
- 高性能:远超 JavaScript,适合计算密集型任务。
- 语言无关:可以用你喜欢的语言编写核心逻辑。
- 安全:运行在浏览器的安全沙箱中。
- 跨平台:一次编译,在 Windows, macOS, Linux, Android, iOS 的浏览器里都能运行!
二、 准备工作
在开始之前,请确保你已经安装了 Go 语言环境(建议版本 1.18 或更高)。你可以在终端运行以下命令来检查:
go version
三、 编写我们的第一个 Go 程序
首先,创建一个项目文件夹,并进入该文件夹。
mkdir go-wasm-demo
cd go-wasm-demo
接下来,我们需要将这个文件夹初始化为一个 Go 模块。这是现代 Go 开发的最佳实践,也是避免各种奇怪编译问题的关键。
go mod init go-wasm-demo
执行后,你会看到目录下生成了一个 go.mod
文件。
然后,创建我们的 Go 程序入口文件 main.go
。
# 在 Windows PowerShell 中创建文件
New-Item main.go
# 在 Linux/macOS 中创建文件
touch main.go
将以下代码粘贴到 main.go
文件中:
package main
import (
"fmt"
"syscall/js"
)
// add 函数将接收两个来自 JavaScript 的数字,并返回它们的和。
func add(this js.Value, i []js.Value) interface{} {
val1 := i[0].Int()
val2 := i[1].Int()
fmt.Printf("Go received: %d, %d\n", val1, val2)
return js.ValueOf(val1 + val2)
}
// registerCallbacks 函数用于将我们的 Go 函数“暴露”给 JavaScript。
func registerCallbacks() {
js.Global().Set("addFunction", js.FuncOf(add))
}
func main() {
fmt.Println("Go WebAssembly Initialized!")
registerCallbacks()
// 创建一个空 channel,防止 Go 程序在 main 函数执行完毕后立刻退出。
c := make(chan struct{}, 0)
<-c
}
四、 编译 Go 程序为 Wasm
这是最关键的一步。我们需要告诉 Go 编译器,我们的目标不是普通的操作系统,而是浏览器环境。
打开你的终端(确保仍在 go-wasm-demo
目录下),运行以下命令:
# 在 Windows PowerShell 中运行:
$env:CGO_ENABLED="0"; $env:GOOS="js"; $env:GOARCH="wasm"; go build -o main.wasm main.go
# 在 Linux/macOS 中运行:
CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -o main.wasm main.go
命令解析:
CGO_ENABLED=0
: 强制禁用 CGO。GOOS=js
: 设置目标操作系统为js
(JavaScript 环境)。GOARCH=wasm
: 设置目标架构为wasm
(WebAssembly)。-o main.wasm
: 指定输出文件的名字为main.wasm
。
命令执行成功后,你的项目文件夹里就会出现一个 main.wasm
文件。
【调试技巧:如何验证
.wasm
文件是否正确?】
如果后续步骤中浏览器报错“expected magic word…”,说明你的.wasm
文件格式不正确。你可以用下面的命令来检查文件头的前8个字节:# 在 Windows PowerShell 中运行: Get-Content -Encoding Byte -TotalCount 8 main.wasm
- 正确的输出 应该是
0, 97, 115, 109, ...
(代表\0asm
)。- 错误的输出 可能是
33, 60, 97, 114, ...
(代表!<arch>
),这通常意味着你忘记了go mod init
或者编译命令有误。
五、 创建前端页面来运行 Wasm
现在,我们需要一个 HTML 页面来加载和运行我们的 main.wasm
文件。
1. 获取 wasm_exec.js
这个 “胶水” 文件由 Go 官方提供,它知道如何加载和运行 Go 编译的 Wasm 模块。最佳实践是从你本地的 Go 安装目录中直接复制它,因为这能保证该文件与你的 Go 编译器版本完美匹配。
wasm_exec.js
文件通常位于 Go 安装根目录(GOROOT
)下的 misc/wasm/
或 html/
文件夹中。
在终端中运行下面的命令来自动复制它:
# 在 Windows PowerShell 中运行:
Copy-Item -Path (Join-Path (go env GOROOT) "misc/wasm/wasm_exec.js") -Destination . -ErrorAction SilentlyContinue
Copy-Item -Path (Join-Path (go env GOROOT) "html/wasm_exec.js") -Destination . -ErrorAction SilentlyContinue
# 在 Linux/macOS 中运行 (会自动尝试两个路径):
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . 2>/dev/null || cp "$(go env GOROOT)/html/wasm_exec.js" .
避坑指南:如果以上命令运行后,文件夹里依然没有
wasm_exec.js
文件(可能是因为你的 Go 安装不完整),你可以使用 Plan B:通过文件资源管理器,在你的 Go 安装目录(通过go env GOROOT
查看路径)下手动搜索wasm_exec.js
并复制过来。
2. 创建 index.html
在项目文件夹中创建 index.html
文件,并粘贴以下内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go Wasm Demo</title>
<script src="wasm_exec.js"></script>
<script>
async function runWasm() {
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch("main.wasm"),
go.importObject
);
go.run(result.instance);
const sum = addFunction(50, 25);
console.log("Result from Go:", sum);
document.getElementById("result").innerText = `50 + 25 = ${sum}`;
}
runWasm();
</script>
</head>
<body>
<h1>Go + WebAssembly Demo</h1>
<p>从 Go 计算得到的结果是: <strong id="result">正在计算...</strong></p>
<p>请按 F12 打开浏览器控制台查看更多日志。</p>
</body>
</html>
六、 运行与见证奇迹
由于浏览器的安全策略,你不能直接双击打开 index.html
。我们需要一个本地 Web 服务器。
【小贴士:一键启动本地 HTTP 服务器】
你不需要安装 Nginx 或 Apache 等复杂的服务器软件。使用下面这些内置于编程语言的命令,就可以在当前文件夹快速启动一个 HTTP 服务器。使用 Python (推荐,大部分系统自带):
# 如果你用的是 Python 3 python -m http.server 8080 # 如果你用的是 Python 2 python -m SimpleHTTPServer 8080
使用 Node.js (需要先安装
serve
):# 全局安装 serve (只需一次) npm install -g serve # 在项目文件夹中运行 serve .
现在,让我们用 Python 来启动服务器。在终端中(确保仍在 go-wasm-demo
目录下),运行:
python -m http.server 8080
终端会显示 Serving HTTP on 0.0.0.0 port 8080 ...
。
打开你的浏览器,访问 http://localhost:8080
。
你应该能看到:
- 网页上显示:“从 Go 计算得到的结果是: 75”
- 按 F12 打开开发者工具,在控制台 (Console) 中,你会看到:
Go WebAssembly Initialized!
Go received: 50, 25
Result from Go: 75
恭喜你!你已经成功地在浏览器里运行了你的 Go 程序!
七、 总结
通过本教程,我们学习了从编写 Go 代码、编译为 WebAssembly,到最终在网页中运行的完整流程。我们解决了 Go 模块化和编译环境配置等关键问题,并掌握了如何使用命令行工具进行调试。这为你打开了客户端高性能计算的大门。
WebAssembly 是一项强大的技术,它正在改变 Web 开发的格局。希望这篇教程能成为你探索这个新世界的起点。