Skip to content

Commit 9903b9a

Browse files
committed
first commit
0 parents  commit 9903b9a

File tree

9 files changed

+622
-0
lines changed

9 files changed

+622
-0
lines changed

README.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# go-wasm, makes creating WebAssembly with golang easily
2+
3+
`go-wasm` is a package extending the golang package `syscall/js` and making creating WebAssembly with golang easily
4+
5+
### Usage
6+
7+
The package is fully go-getable, so, just type
8+
9+
`go get github.com/rosbit/go-wasm`
10+
11+
to install.
12+
13+
All the following Wasm result should be built with
14+
`GOOS=js GOARCH=wasm go build`
15+
16+
#### 1. Evaluate expressions
17+
18+
```go
19+
package main
20+
21+
import (
22+
"github.com/rosbit/go-wasm"
23+
"fmt"
24+
)
25+
26+
func main() {
27+
res, _ := wasm.Eval("1 + 2", nil)
28+
fmt.Println("result is:", res)
29+
}
30+
```
31+
32+
#### 2. Go calls JS function
33+
34+
Suppose there's a JS file named `a.js` like this:
35+
36+
```js
37+
function add(a, b) {
38+
return a+b
39+
}
40+
```
41+
42+
one can call the JS function `add()` in Go code like the following:
43+
44+
```go
45+
package main
46+
47+
import (
48+
"github.com/rosbit/go-wasm"
49+
"fmt"
50+
)
51+
52+
var add func(int, int)int
53+
54+
func main() {
55+
if err := wasm.BindFunc("add", &add); err != nil {
56+
fmt.Printf("%v\n", err)
57+
return
58+
}
59+
60+
res := add(1, 2)
61+
fmt.Println("result is:", res)
62+
}
63+
```
64+
65+
#### 3. JS calls Go function
66+
67+
JS calling Go function is also easy. In the Go code, make a Golang function
68+
as JS built-in function by calling `MakeJsFunc("funcname", function)`. There's the example:
69+
70+
```go
71+
package main
72+
73+
import "github.com/rosbit/go-wasm"
74+
75+
// function to be called by JS
76+
func adder(a1 float64, a2 float64) float64 {
77+
return a1 + a2
78+
}
79+
80+
func main() {
81+
wasm.MakeJsFunc("adder", adder) // now "adder" is a global JS function
82+
done := make(chan struct{})
83+
<-done
84+
}
85+
```
86+
87+
In JS code, one can call the registered function directly. There's the example `b.js`.
88+
89+
```js
90+
r = adder(1, 100) # the function "adder" is implemented in Go
91+
console.log(r)
92+
```
93+
94+
#### 4. Make Go struct instance as a JS object
95+
96+
This package provides a function `SetModule` which will convert a Go struct instance into
97+
a JS object. There's the example `c.js`, `m` is the object var provided by Go code:
98+
99+
```js
100+
m.incAge(10)
101+
print(m)
102+
103+
console.log('m.name', m.name)
104+
console.log('m.age', m.age)
105+
```
106+
107+
The Go code is like this:
108+
109+
```go
110+
package main
111+
112+
import "github.com/rosbit/go-wasm"
113+
114+
type M struct {
115+
Name string
116+
Age int
117+
}
118+
func (m *M) IncAge(a int) {
119+
m.Age += a
120+
}
121+
122+
func main() {
123+
wasm.BindObject("m", &M{Name:"rosbit", Age: 1}) // "m" is the object var name
124+
125+
done := make(chan struct{})
126+
<-done
127+
}
128+
```
129+
130+
#### 5. Set many built-in functions and objects at one time
131+
132+
If there're a lot of functions and objects to be registered, a map could be constructed with function `SetGlobals` or put as an
133+
argument for function `Eval`.
134+
135+
```go
136+
package main
137+
138+
import "github.com/rosbit/go-wasm"
139+
import "fmt"
140+
141+
type M struct {
142+
Name string
143+
Age int
144+
}
145+
func (m *M) IncAge(a int) {
146+
m.Age += a
147+
}
148+
149+
func adder(a1 float64, a2 float64) float64 {
150+
return a1 + a2
151+
}
152+
153+
func main() {
154+
vars := map[string]interface{}{
155+
"m": &M{Name:"rosbit", Age:1}, // to JS object
156+
"adder": adder, // to JS built-in function
157+
"a": []int{1,2,3}, // to JS array
158+
}
159+
160+
wasm.SetGlobals(vars)
161+
res := wasm.GetGlobal("a") // get the value of var named "a". Any variables in script could be get by GetGlobal
162+
fmt.Printf("res:", res)
163+
}
164+
```
165+
166+
#### 6. Wrap anything as JS global object
167+
168+
This package also provides a function `SetGlobalObject` which will create a JS variable integrating any
169+
Go values/functions as an object. There's the example `d.js` which will use object `tm` provided by Go code:
170+
171+
```js
172+
a = tm.newA("rosbit", 10)
173+
a.incAge(10)
174+
console.log(a)
175+
176+
tm.printf('a.name: %s\n', a.name)
177+
tm.printf('a.age: %d\n', a.age)
178+
```
179+
180+
The Go code is like this:
181+
182+
```go
183+
package main
184+
185+
import (
186+
"github.com/rosbit/go-wasm"
187+
"fmt"
188+
)
189+
190+
type A struct {
191+
Name string
192+
Age int
193+
}
194+
func (m *A) IncAge(a int) {
195+
m.Age += a
196+
}
197+
func newA(name string, age int) *A {
198+
return &A{Name: name, Age: age}
199+
}
200+
201+
func main() {
202+
wasm.SetGlobalObject("tm", map[string]interface{}{ // object name is "tm"
203+
"newA": newA, // make user defined function as object method named "tm.newA"
204+
"printf": fmt.Printf, // make function in a standard package named "tm.printf"
205+
})
206+
207+
done := make(chan struct{})
208+
<-done
209+
}
210+
```
211+
212+
### Other helper functions
213+
214+
```go
215+
func JSONStringify(v interface{}) string // wasm.JSONStringify([]int{1, 3})
216+
func JSONParse(val string) interface{} // wasm.JSONParse(`{"a":"b","c":1}`)
217+
func CallObjectMethod(objName, objMethod string, args ...interface{}) js.Value
218+
// jsObj := wasm.CallObjectMethod("document", "getElementById", "id")
219+
func CallFunc(funcName string, args ...interface{}) js.Value // wasm.CallFunc("add", 1, 1)
220+
```
221+
222+
### Status
223+
224+
The package is not fully tested, so be careful.
225+
226+
### Contribution
227+
228+
Pull requests are welcome! Also, if you want to discuss something send a pull request with proposal and changes.
229+
230+
__Convention:__ fork the repository and make changes on your fork in a feature branch.

go-func.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package wasm
2+
3+
import (
4+
elutils "github.com/rosbit/go-embedding-utils"
5+
"syscall/js"
6+
)
7+
8+
func bindGoFunc(funcVar interface{}) (goFunc js.Func, err error) {
9+
helper, e := elutils.NewGolangFuncHelper("noname", funcVar)
10+
if e != nil {
11+
err = e
12+
return
13+
}
14+
goFunc = js.FuncOf(wrapGoFunc(helper))
15+
return
16+
}
17+
18+
func wrapGoFunc(helper *elutils.GolangFuncHelper) func(this js.Value, args []js.Value)interface{} {
19+
return func(this js.Value, args []js.Value) (val interface{}) {
20+
getArgs := func(i int) interface{} {
21+
return fromValue(args[i])
22+
}
23+
24+
v, e := helper.CallGolangFunc(len(args), "gofunc", getArgs)
25+
if e != nil || v == nil {
26+
return
27+
}
28+
29+
val = v
30+
return
31+
}
32+
}

go-interface.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package wasm
2+
3+
import (
4+
"reflect"
5+
)
6+
7+
func bindGoInterface(interfaceVar reflect.Value) (goInterface map[string]interface{}) {
8+
t := interfaceVar.Type()
9+
r := make(map[string]interface{})
10+
bindGoMethod(interfaceVar, t, r)
11+
return r
12+
}
13+

go-struct.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package wasm
2+
3+
import (
4+
elutils "github.com/rosbit/go-embedding-utils"
5+
"syscall/js"
6+
"reflect"
7+
"strings"
8+
// "fmt"
9+
)
10+
11+
func bindGoStruct(structVar reflect.Value) (goStruct map[string]interface{}) {
12+
var structE reflect.Value
13+
if structVar.Kind() == reflect.Ptr {
14+
structE = structVar.Elem()
15+
} else {
16+
structE = structVar
17+
}
18+
structT := structE.Type()
19+
20+
if structE == structVar {
21+
// struct is unaddressable, so make a copy of struct to an Elem of struct-pointer.
22+
// NOTE: changes of the copied struct cannot effect the original one. it is recommended to use the pointer of struct.
23+
structVar = reflect.New(structT) // make a struct pointer
24+
structVar.Elem().Set(structE) // copy the old struct
25+
structE = structVar.Elem() // structE is the copied struct
26+
}
27+
28+
goStruct = wrapGoStruct(structVar, structE, structT)
29+
return
30+
}
31+
32+
func lowerFirst(name string) string {
33+
return strings.ToLower(name[:1]) + name[1:]
34+
}
35+
36+
func wrapGoStruct(structVar, structE reflect.Value, structT reflect.Type) map[string]interface{} {
37+
r := make(map[string]interface{})
38+
for i:=0; i<structT.NumField(); i++ {
39+
strField := structT.Field(i)
40+
name := strField.Name
41+
name = lowerFirst(name)
42+
fv := structE.Field(i)
43+
r[name] = toValue(fv.Interface())
44+
}
45+
46+
// receiver is the struct
47+
bindGoMethod(structE, structT, r)
48+
49+
// reciver is the pointer of struct
50+
t := structVar.Type()
51+
bindGoMethod(structVar, t, r)
52+
return r
53+
}
54+
55+
func bindGoMethod(structV reflect.Value, structT reflect.Type, r map[string]interface{}) {
56+
for i := 0; i<structV.NumMethod(); i+=1 {
57+
m := structT.Method(i)
58+
name := lowerFirst(m.Name)
59+
mV := structV.Method(i)
60+
mT := mV.Type()
61+
r[name] = js.FuncOf(wrapGoFunc(elutils.NewGolangFuncHelperDiretly(mV, mT)))
62+
}
63+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/rosbit/go-wasm
2+
3+
go 1.17
4+
5+
require github.com/rosbit/go-embedding-utils v0.1.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/rosbit/go-embedding-utils v0.1.0 h1:UAgyEBTRpxphPRNOGVa2VBxXned5GxohW6XPdTxWlXQ=
2+
github.com/rosbit/go-embedding-utils v0.1.0/go.mod h1:vN49YyUkB9OQI4t/6ofn0+kHYOrn/mAP1cqkzITBoEw=

js-func.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package wasm
2+
3+
import (
4+
elutils "github.com/rosbit/go-embedding-utils"
5+
"syscall/js"
6+
"reflect"
7+
)
8+
9+
func bindFunc(fn *js.Value, funcVarPtr interface{}) (err error) {
10+
helper, e := elutils.NewEmbeddingFuncHelper(funcVarPtr)
11+
if e != nil {
12+
err = e
13+
return
14+
}
15+
helper.BindEmbeddingFunc(wrapFunc(fn, helper))
16+
return
17+
}
18+
19+
func wrapFunc(fn *js.Value, helper *elutils.EmbeddingFuncHelper) elutils.FnGoFunc {
20+
return func(args []reflect.Value) (results []reflect.Value) {
21+
var jsArgs []interface{}
22+
23+
// make js args
24+
itArgs := helper.MakeGoFuncArgs(args)
25+
for arg := range itArgs {
26+
jsArgs = append(jsArgs, arg)
27+
}
28+
29+
// call js function
30+
res := fn.Invoke(jsArgs...)
31+
32+
// convert result to golang
33+
results = helper.ToGolangResults(fromValue(res), res.Type() == js.TypeObject && res.InstanceOf(array), nil)
34+
return
35+
}
36+
}

0 commit comments

Comments
 (0)