Contents

Go WebAssembly 实践

Go-WebAssembly实践

在网页端实现一个json转struct

Hello Word

  1. 创建项目目录

    1
    2
    3
    4
    5
    6
    
    Documents/  
    └── webassembly
        ├── assets
        └── cmd
            ├── server
            └── wasm
    
  2. 在 webassembly初始化go项目

    1
    
    go mod init teswebassembly  
    
  3. webassembly/cmd/wasm下创建main.go

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        fmt.Println("Go Web Assembly")
    }
    
  4. 编译

    1
    2
    
    cd webassembly/cmd/wasm/  
    GOOS=js GOARCH=wasm go build -o  ../../assets/json.wasm  
    

    生成了在浏览器中运行的二进制文件

  5. 获取wasm_exec.js用来调用json.wasm

    1
    
    cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" webassembly/assets/  
    
  6. webassembly/cmd/wasm下创建index.html

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    <html>  
        <head>
            <meta charset="utf-8"/>
            <script src="wasm_exec.js"></script>
            <script>
                const go = new Go();
                WebAssembly.instantiateStreaming(fetch("json.wasm"), go.importObject).then((result) => {
                    go.run(result.instance);
                });
            </script>
        </head>
        <body></body>
    </html>  
    
  7. 目前目录

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    Documents/  
    └── webassembly
        ├── assets
        │   ├── index.html
        │   ├── json.wasm
        │   └── wasm_exec.js
        └── cmd
            ├── server
            └── wasm
                └── main.go
            └── go.mod
    
  8. 做一个简单服务器用来运行index.html,编译器自带也可以

    webassembly/cmd/server下创建main.go

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    package main
    
    import (  
        "fmt"
        "net/http"
    )
    
    func main() {  
        err := http.ListenAndServe(":8080", http.FileServer(http.Dir("../../assets")))
        if err != nil {
            fmt.Println("Failed to start server", err)
            return
        }
    }
    
  9. 运行

    1
    2
    
    cd webassembly/cmd/server/  
    go run main.go  
    

    https://images-jsh.oss-cn-beijing.aliyuncs.com/work/2023/02/06/20230206-103722.png

    在控制台中可以看到打印

功能实现

Json2Go 使用的是 github.com/m-zajac/json2go

webassembly/cmd/wasm/main.go中添加

  1. 添加功能代码

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    import (
    	"github.com/m-zajac/json2go"
    )
    
    func Json2Go(input string) string {
    	parser := json2go.NewJSONParser("Document")
    	parser.FeedBytes([]byte(input))
    	res := parser.String()
    	return res
    }
    
  2. 将函数暴露到Javascript

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    import (
    	"syscall/js"
    )
    
    func main() {
    	fmt.Println("Go Web Assembly")
    	js.Global().Set("json2Go", Json2GoWrapper())
      //避免 Error: Go program has already exited
      <-make(chan bool)
    }
    
    func Json2GoWrapper() js.Func {
    	jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
    		if len(args) != 1 {
    			return "Invalid no of arguments passed"
    		}
    		inputJSON := args[0].String()
    		res := Json2Go(inputJSON)
    		return res
    	})
    	return jsonFunc
    }
    

    syscall/js 编译器报错设置

    https://images-jsh.oss-cn-beijing.aliyuncs.com/work/2023/02/06/20230206-104427.png

  3. 编写ui

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    <html>
    <head>
        <meta charset="utf-8"/>
        <script src="wasm_exec.js"></script>
        <script>
            const go = new Go();
            WebAssembly.instantiateStreaming(fetch("json.wasm"), go.importObject).then((result) => {
                go.run(result.instance);
            });
        </script>
    </head>
    <body>
    <textarea id="jsoninput" name="jsoninput" cols="80" rows="20"></textarea>
    <input id="button" type="submit" name="button" value="pretty json" onclick="json(jsoninput.value)"/>
    <textarea id="jsonoutput" name="jsonoutput" cols="80" rows="20"></textarea>
    </body>
    <script>
        var json = function (input) {
            jsonoutput.value = json2Go(input)
        }
    </script>
    </html>
    
  4. 测试

    https://images-jsh.oss-cn-beijing.aliyuncs.com/work/2023/02/06/20230206-104913.png

DOM操作

  1. 修改webassembly/cmd/wasm/main.goJson2GoWrapper方法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    func Json2GoWrapper() js.Func {
    	jsonfunc := js.FuncOf(func(this js.Value, args []js.Value) any {
    		if len(args) != 1 {
    			result := map[string]any{
    				"error": "Invalid no of arguments passed",
    			}
    			return result
    		}
    		jsDoc := js.Global().Get("document")
    		if !jsDoc.Truthy() {
    			result := map[string]any{
    				"error": "Invalid no of arguments passed",
    			}
    			return result
    		}
    		jsonOuputTextArea := jsDoc.Call("getElementById", "jsonoutput")
    		if !jsonOuputTextArea.Truthy() {
    			result := map[string]any{
    				"error": "Invalid no of arguments passed",
    			}
    			return result
    		}
    		inputJSON := args[0].String()
    		res := Json2Go(inputJSON)
    		jsonOuputTextArea.Set("value", res)
    		return nil
    	})
    	return jsonfunc
    }
    
  2. 修改webassembly/assets/index.html

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    <script>
        var json = function (input) {
            var result = json2Go(input)
            if (( result != null) && ('error' in result)) {
                console.log("Go return value", result)
                jsonoutput.value = ""
                alert(result.error)
            }
            // jsonoutput.value = json2Go(input)
        }
    </script>
    

错误处理

由于js无法处理error类型,对应表:https://pkg.go.dev/syscall/js#ValueOf

https://images-jsh.oss-cn-beijing.aliyuncs.com/work/2023/02/06/20230206-105918.png

所以错误处理返回map[string]interface{}

1
2
3
result := map[string]any{
	"error": "error message",
}

Json参数传递

  1. index.html

    1
    2
    3
    4
    5
    
    <script>
        var json = function (input) {
            jsonoutput.value = json2Go({"json": input})
        }
    </script>
    
  2. Go接收

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    func Json2GoWrapper() js.Func {
    	jsonFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
    		if len(args) != 1 {
    			return "Invalid no of arguments passed"
    		}
    		inputJSON := args[0].Get("json").String()
    		res := Json2Go(inputJSON)
    		return res
    	})
    	return jsonFunc
    }