Go 支持将 go 代码编译成 WebAssembly (在浏览器中执行的二进制代码),如下:
GOARCH=wasm GOOS=js go build ...
编译得到的 wasm 文件需要通过 JavaScript engine 执行,比如浏览器内置的 js 引擎。将 go 代码中的函数暴露到浏览器中运行,需要按照规则编写 go 文件和 html 文件。 下面两篇英文文章介绍的非常详细:
Go 代码文件主要约束如下:
package main
import (
"encoding/json"
"errors"
"fmt"
"syscall/js"
)
func main() {
c := make(chan bool)
fmt.Println("Go Web Assembly")
js.Global().Set("formatJSON", formatJSON()) //添加 js 方法
js.Global().Set("updateJSON", updateJSON()) //添加 js 方法
js.Global().Set("updateJSONWithErrorMap", updateJSONWithErorMap()) //添加 js 方法
<-c
}
// 将 go 函数暴露到 js
func formatJSON() js.Func {
jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
return "Invalid no of arguments passed"
}
inputJson := args[0].String()
fmt.Printf("input: %s\n", inputJson)
pretty, err := prettyJson(inputJson)
if err != nil {
fmt.Printf("unable to convert to json: err=%s\n", err)
return err.Error()
}
return pretty
})
return jsonFunc
}
func updateJSON() js.Func {
jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
return errors.New("Invalid no of arguments passed")
}
doc := js.Global().Get("document")
if !doc.Truthy() {
return errors.New("Unable to get document object")
}
output2 := doc.Call("getElementById", "output2")
if !output2.Truthy() {
return errors.New("Unable to get output2 text area")
}
inputJson := args[0].String()
fmt.Printf("input: %s\n", inputJson)
pretty, err := prettyJson(inputJson)
if err != nil {
return fmt.Errorf("unable to convert to json: err=%s\n", err)
}
output2.Set("value", pretty)
return nil
})
return jsonFunc
}
func updateJSONWithErorMap() js.Func {
jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var errStr string
if len(args) != 1 {
errStr = "Invalid no of arguments passed"
fmt.Printf(errStr)
return map[string]interface{}{"error": errStr}
}
doc := js.Global().Get("document")
if !doc.Truthy() {
errStr = "Unable to get document object"
fmt.Printf(errStr)
return map[string]interface{}{"error": errStr}
}
output2 := doc.Call("getElementById", "output2")
if !output2.Truthy() {
errStr = "Unable to get output2 text area"
fmt.Printf(errStr)
return map[string]interface{}{"error": errStr}
}
inputJson := args[0].String()
fmt.Printf("input: %s\n", inputJson)
pretty, err := prettyJson(inputJson)
if err != nil {
errStr = fmt.Sprintf("unable to convert to json: err=%s\n", err)
fmt.Printf(errStr)
return map[string]interface{}{"error": errStr}
}
output2.Set("value", pretty)
return nil
})
return jsonFunc
}
func prettyJson(input string) (string, error) {
var raw interface{}
if err := json.Unmarshal([]byte(input), &raw); err != nil {
return "", err
}
pretty, err := json.MarshalIndent(raw, "", " ")
if err != nil {
return "", err
}
return string(pretty), nil
}
HTML 文件约束如下:
<html>
<head>
<meta charset="utf-8">
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"),go.importObject).then((result)=>{
go.run(result.instance);
});
</script>
</head>
<body>
<textarea id="input" rows="10" cols="80">{"website":"golangbot.com", "tutorials": {"string":"https://golangbot.com/strings/"}}</textarea>
<button type="button" onclick="format(input.value)">format by js</button>
<button type="button" onclick="updateJSON(input.value)">format by go(if err panic)</button>
<button type="button" onclick="format2(input.value)">format by go(return err map)</button>
<br>
<textarea id="output" rows="10" cols="80" placeholder="set by js"></textarea>
<textarea id="output2" rows="10" cols="80" placeholder="set by go"></textarea>
<script>
function format(input){
output.value = formatJSON(input)
}
function format2(input){
let resp = updateJSONWithErrorMap(input)
if ((resp != null)&& ('error' in resp)){
console.log("Go return value: ", resp)
alert(resp.error)
}
}
</script>
</body>
</html>
将 index.html、从 GOROOT 中复制的 wasm_exec.js 和编译得到的 main.wasm 文件,放到同一个目录。然后启动一个 webserver 即可:
├── favicon.ico
├── index.html
├── main.wasm
└── wasm_exec.js
python3 -m http.server 8081