├── .gitignore
├── storage
└── storage.go
├── logo.jpg
├── component
└── component.go
├── examples
├── blog
│ ├── lib.wasm
│ ├── static
│ │ ├── entrypoint.js
│ │ └── wasm_exec.js
│ ├── components
│ │ ├── home.go
│ │ └── about.go
│ ├── main.go
│ └── index.html
├── SelfQuoting
│ ├── lib.wasm
│ ├── static
│ │ ├── entrypoint.js
│ │ └── wasm_exec.js
│ ├── README.dm
│ ├── main.go
│ └── index.html
├── calculator
│ ├── lib.wasm
│ ├── static
│ │ ├── entrypoint.js
│ │ └── wasm_exec.js
│ ├── index.html
│ └── main.go
├── template
│ ├── static
│ │ ├── entrypoint.js
│ │ ├── app.css
│ │ └── wasm_exec.js
│ ├── index.html
│ └── main.go
└── test-project
│ ├── static
│ ├── entrypoint.js
│ ├── app.css
│ └── wasm_exec.js
│ ├── index.html
│ └── main.go
├── .travis.gofmt.sh
├── Makefile
├── .travis.yml
├── main.go
├── go.mod
├── cli
└── commands
│ ├── build.go
│ ├── generate.go
│ ├── start.go
│ └── new.go
├── http
└── http.go
├── cmd
└── oak-cli
│ └── main.go
├── router
└── router.go
├── go.sum
└── readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | oak
2 | oak-cli
3 |
--------------------------------------------------------------------------------
/storage/storage.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | var state interface{}
4 |
--------------------------------------------------------------------------------
/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/HEAD/logo.jpg
--------------------------------------------------------------------------------
/component/component.go:
--------------------------------------------------------------------------------
1 | package component
2 |
3 | type Component interface {
4 | Render() string
5 | }
6 |
--------------------------------------------------------------------------------
/examples/blog/lib.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/HEAD/examples/blog/lib.wasm
--------------------------------------------------------------------------------
/examples/SelfQuoting/lib.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/HEAD/examples/SelfQuoting/lib.wasm
--------------------------------------------------------------------------------
/examples/calculator/lib.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/HEAD/examples/calculator/lib.wasm
--------------------------------------------------------------------------------
/.travis.gofmt.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ -n "$(gofmt -l .)" ]; then
4 | echo "Go code is not formatted:"
5 | gofmt -d .
6 | exit 1
7 | fi
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | GOOS=js GOARCH=wasm go test main.go
3 | build-cli:
4 | GOOS=darwin GOARCH=amd64 go build -o oak cmd/oak-cli/main.go
5 |
6 |
--------------------------------------------------------------------------------
/examples/blog/static/entrypoint.js:
--------------------------------------------------------------------------------
1 | const go = new Go();
2 | WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
3 | go.run(result.instance);
4 | });
--------------------------------------------------------------------------------
/examples/calculator/static/entrypoint.js:
--------------------------------------------------------------------------------
1 | const go = new Go();
2 | WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
3 | go.run(result.instance);
4 | });
--------------------------------------------------------------------------------
/examples/template/static/entrypoint.js:
--------------------------------------------------------------------------------
1 | const go = new Go();
2 | WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
3 | go.run(result.instance);
4 | });
--------------------------------------------------------------------------------
/examples/SelfQuoting/static/entrypoint.js:
--------------------------------------------------------------------------------
1 | const go = new Go();
2 | WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
3 | go.run(result.instance);
4 | });
--------------------------------------------------------------------------------
/examples/test-project/static/entrypoint.js:
--------------------------------------------------------------------------------
1 | const go = new Go();
2 | WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
3 | go.run(result.instance);
4 | });
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # .travis.yml
2 | language: go
3 |
4 | sudo: false
5 |
6 | go:
7 | - "1.11"
8 |
9 | script:
10 | - ./.travis.gofmt.sh
11 | - make build
12 | - make build-cli
13 | - echo "placeholder"
--------------------------------------------------------------------------------
/examples/blog/components/home.go:
--------------------------------------------------------------------------------
1 | package components
2 |
3 | type HomeComponent struct{}
4 |
5 | var Home HomeComponent
6 |
7 | func (h HomeComponent) Render() string {
8 | return "
Home Component "
9 | }
10 |
--------------------------------------------------------------------------------
/examples/SelfQuoting/README.dm:
--------------------------------------------------------------------------------
1 | go 1.15 required for the example because wasm_exec.js is updated
2 |
3 | if you see
4 | localhost/:1 Uncaught (in promise) LinkError: WebAssembly.instantiate(): Import #1 module="go" function="runtime.resetMemoryDataView" error: function import requires a callable
5 | error
6 | this is because you are using wasm_exec.js from the wrong GO version
--------------------------------------------------------------------------------
/examples/calculator/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Go wasm
7 |
8 |
9 |
10 |
11 |
12 |
Starting Project
13 |
14 | My Cool Func
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package oak
2 |
3 | import (
4 | "syscall/js"
5 |
6 | "github.com/elliotforbes/go-webassembly-framework/http"
7 | )
8 |
9 | func registerCallbacks() {
10 | http.RegisterCallbacks()
11 | }
12 |
13 | func RegisterFunction(functionName string, function func(this js.Value, i []js.Value) interface{}) {
14 | js.Global().Set(functionName, js.FuncOf(function))
15 | }
16 |
17 | func Start() {
18 | println("Oak Framework Initialized")
19 | registerCallbacks()
20 | }
21 |
--------------------------------------------------------------------------------
/examples/template/static/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | background-color: #f1efed;
10 | }
11 |
12 | .app {
13 | text-align: center;
14 | }
15 |
16 | .app h2 {
17 | font-size: 4rem;
18 | }
19 |
--------------------------------------------------------------------------------
/examples/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Go wasm
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Oak Starter Project
14 |
15 | Test Function
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/test-project/static/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | background-color: #f1efed;
10 | }
11 |
12 | .app {
13 | text-align: center;
14 | }
15 |
16 | .app h2 {
17 | font-size: 4rem;
18 | }
19 |
--------------------------------------------------------------------------------
/examples/test-project/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Go wasm
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Oak Starter Project
14 |
15 | Test Function
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/elliotforbes/go-webassembly-framework
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/fatih/color v1.7.0
7 | github.com/fsnotify/fsnotify v1.4.7
8 | github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae // indirect
9 | github.com/mattn/go-colorable v0.0.9 // indirect
10 | github.com/mattn/go-isatty v0.0.4 // indirect
11 | github.com/spf13/cobra v0.0.3
12 | github.com/spf13/pflag v1.0.3 // indirect
13 | golang.org/x/sys v0.0.0-20190105165716-badf5585203e // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/examples/template/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "syscall/js"
5 |
6 | oak "github.com/elliotforbes/go-webassembly-framework"
7 | )
8 |
9 | func mycoolfunc(this js.Value, i []js.Value) interface{} {
10 | println("My Awesome Function")
11 | return nil
12 | }
13 |
14 | func main() {
15 | // Starts the Oak framework
16 | oak.Start()
17 |
18 | // registers custom functions
19 | oak.RegisterFunction("coolfunc", mycoolfunc)
20 |
21 | // keeps our app running
22 | done := make(chan struct{}, 0)
23 | <-done
24 | }
25 |
--------------------------------------------------------------------------------
/examples/test-project/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "syscall/js"
5 |
6 | "github.com/elliotforbes/go-webassembly-framework"
7 | )
8 |
9 | func mycoolfunc(this js.Value, i []js.Value) interface{} {
10 | println("My Awesome Function")
11 | return nil
12 | }
13 |
14 | func main() {
15 | // Starts the Oak framework
16 | oak.Start()
17 |
18 | // registers custom functions
19 | oak.RegisterFunction("coolfunc", mycoolfunc)
20 |
21 | // keeps our app running
22 | done := make(chan struct{}, 0)
23 | <-done
24 | }
25 |
--------------------------------------------------------------------------------
/examples/calculator/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "syscall/js"
5 |
6 | oak "github.com/elliotforbes/go-webassembly-framework"
7 | )
8 |
9 | func mycoolfunc(this js.Value, i []js.Value) interface{} {
10 | println("My Awesome Function")
11 | return nil
12 | }
13 |
14 | func main() {
15 | // Starts the Oak framework
16 | oak.Start()
17 |
18 | // registers custom functions
19 | oak.RegisterFunction("coolfunc", mycoolfunc)
20 |
21 | // keeps our app running
22 | done := make(chan struct{}, 0)
23 | <-done
24 | }
25 |
--------------------------------------------------------------------------------
/cli/commands/build.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "os/exec"
5 |
6 | "github.com/fatih/color"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | var BuildCmd = &cobra.Command{
11 | Use: "build",
12 | Short: "builds a lightweight compressed WebAssembly binary",
13 | Run: buildExecute,
14 | }
15 |
16 | func buildExecute(cmd *cobra.Command, args []string) {
17 | out, err := exec.Command("env", "tinygo", "build", "-o", "lib.wasm", "-target", "wasm", "./main.go").Output()
18 | if err != nil {
19 | color.Red("err: ", err)
20 | }
21 | color.Green(string(out[:]))
22 | }
23 |
--------------------------------------------------------------------------------
/examples/blog/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/elliotforbes/go-webassembly-framework"
5 | "github.com/elliotforbes/go-webassembly-framework/examples/blog/components"
6 | "github.com/elliotforbes/go-webassembly-framework/router"
7 | )
8 |
9 | func main() {
10 | // Starts the Oak framework
11 | oak.Start()
12 |
13 | // Starts our Router
14 | router.NewRouter()
15 | router.RegisterRoute("home", components.Home)
16 | router.RegisterRoute("about", components.About)
17 |
18 | // keeps our app running
19 | done := make(chan struct{}, 0)
20 | <-done
21 | }
22 |
--------------------------------------------------------------------------------
/examples/blog/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Blog
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
A Simple Blo
16 |
17 |
18 |
19 |
Home
20 |
About
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/http/http.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "syscall/js"
5 | )
6 |
7 | func RegisterCallbacks() {
8 | js.Global().Set("get", js.FuncOf(GetRequest))
9 | }
10 |
11 | func GetRequest(this js.Value, i []js.Value) interface{} {
12 | // req, err := http.NewRequest("GET", "http://localhost:8080", nil)
13 | // req.Header.Add("js.fetch:mode", "cors")
14 | // if err != nil {
15 | // fmt.Println(err)
16 | // return nil
17 | // }
18 |
19 | // resp, err := http.DefaultClient.Do(req)
20 | // if err != nil {
21 | // fmt.Println(err)
22 | // return nil
23 | // }
24 | // defer resp.Body.Close()
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/examples/blog/components/about.go:
--------------------------------------------------------------------------------
1 | package components
2 |
3 | import (
4 | "syscall/js"
5 |
6 | oak "github.com/elliotforbes/go-webassembly-framework"
7 | )
8 |
9 | type AboutComponent struct{}
10 |
11 | var About AboutComponent
12 |
13 | func init() {
14 | oak.RegisterFunction("coolFunc", CoolFunc)
15 | }
16 |
17 | func CoolFunc(this js.Value, i []js.Value) interface{} {
18 | println("does stuff")
19 | return nil
20 | }
21 |
22 | func (a AboutComponent) Render() string {
23 | return `
24 |
About Component Actually Works
25 | Cool Func
26 | `
27 | }
28 |
--------------------------------------------------------------------------------
/cli/commands/generate.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/fatih/color"
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | var GenerateCmd = &cobra.Command{
9 | Use: "generate",
10 | Short: "Generate components, routes, and services with a simple command",
11 | Long: `Generate components, routes, and services with a simple command`,
12 | Args: cobra.MinimumNArgs(1),
13 | Run: generateExecute,
14 | }
15 |
16 | func generateExecute(cmd *cobra.Command, args []string) {
17 | switch args[0] {
18 | case "component":
19 | color.Green("Generating a New Component")
20 | case "service":
21 | color.Green("No idea how this will work but sure")
22 | default:
23 | color.Red("%s - is not a valid object\n", args[0])
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/cmd/oak-cli/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 |
7 | "github.com/elliotforbes/go-webassembly-framework/cli/commands"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | var rootCmd = &cobra.Command{
12 | Use: "oak",
13 | Short: "Oak is a framework for building WebAssembly Applications in Go",
14 | Long: `A fully functioning WebAssembly Framework written in Go which allows you to create fast,
15 | powerful web applications in your favorite language`,
16 | Run: func(cmd *cobra.Command, args []string) {
17 | fmt.Println("Hello World")
18 | },
19 | }
20 |
21 | func main() {
22 | runtime.GOMAXPROCS(runtime.NumCPU())
23 | rootCmd.AddCommand(commands.StartCmd)
24 | rootCmd.AddCommand(commands.NewCmd)
25 | rootCmd.AddCommand(commands.GenerateCmd)
26 | rootCmd.AddCommand(commands.BuildCmd)
27 | rootCmd.Execute()
28 | }
29 |
--------------------------------------------------------------------------------
/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "syscall/js"
5 |
6 | "github.com/elliotforbes/go-webassembly-framework/component"
7 | )
8 |
9 | type Router struct {
10 | Routes map[string]component.Component
11 | }
12 |
13 | var router Router
14 |
15 | func init() {
16 | router.Routes = make(map[string]component.Component)
17 | }
18 |
19 | func NewRouter() {
20 | js.Global().Set("Link", js.FuncOf(Link))
21 | js.Global().Get("document").Call("getElementById", "view").Set("innerHTML", "")
22 | }
23 |
24 | func RegisterRoute(path string, component component.Component) {
25 | router.Routes[path] = component
26 | }
27 |
28 | func Link(this js.Value, i []js.Value) interface{} {
29 | println("Link Hit")
30 |
31 | comp := router.Routes[i[0].String()]
32 | html := comp.Render()
33 |
34 | js.Global().Get("document").Call("getElementById", "view").Set("innerHTML", html)
35 | return nil
36 | }
37 |
--------------------------------------------------------------------------------
/examples/SelfQuoting/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | // "fmt"
5 | oak "github.com/elliotforbes/go-webassembly-framework"
6 | "syscall/js"
7 | )
8 |
9 | //we set some defaults here
10 | var disk string = "100"
11 | var os string = "Windows"
12 |
13 | func HandleClickDisco(this js.Value, i []js.Value) interface{} {
14 | sliderValue := js.Global().Get("document").Call("getElementById", "DiskSlider").Get("value")
15 | // fmt.Println(sliderValue)
16 | disk = sliderValue.String()
17 | QuoteResults()
18 | return nil
19 | }
20 |
21 | func HandleOsChange(this js.Value, i []js.Value) interface{} {
22 | osSelected := js.Global().Get("document").Call("getElementsByName", "so").Index(0).Get("checked").Bool()
23 | if osSelected == true {
24 | os = "Windows"
25 | } else {
26 | os = "Linux"
27 | }
28 | QuoteResults()
29 | return nil
30 | }
31 |
32 | //Print inside QuoteResults div the quote results.
33 | func QuoteResults() {
34 | // fmt.Println("You choice a Host with " + disk + " GB of disk with " + os)
35 | js.Global().Get("document").Call("getElementById", "QuoteResults").Set("innerHTML", "You choice a Host with "+disk+" GB of disk with "+os)
36 | }
37 |
38 | func main() {
39 | // Starts the Oak framework
40 | oak.Start()
41 |
42 | // registers custom functions
43 | oak.RegisterFunction("HandleDiskElement", HandleClickDisco)
44 | oak.RegisterFunction("HandleOsElement", HandleOsChange)
45 | QuoteResults() //call the quoteresults to print the defaults
46 |
47 | // keeps our app running
48 | done := make(chan struct{}, 0)
49 | <-done
50 | }
51 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/elliotforbes/oak v0.0.0-20190106195849-ca415a357901 h1:UzxXbKhFo1twzMWyhIkI1DDd2aIM8Py34osRYKlDALA=
2 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
3 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
4 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
5 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
6 | github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae h1:PeVNzgTRtWGm6fVic5i21t+n5ptPGCZuMcSPVMyTWjs=
7 | github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae/go.mod h1:BbhqyaehKPCLD83cqfRYdm177Ylm1cdGHu3txjbQSQI=
8 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
9 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
10 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
11 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
12 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
13 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
14 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
15 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
16 | golang.org/x/sys v0.0.0-20190105165716-badf5585203e h1:34JZ+d5RsKTBgJnMpK4m6gzXRZ6H99pKveztnOI3+JA=
17 | golang.org/x/sys v0.0.0-20190105165716-badf5585203e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
18 |
--------------------------------------------------------------------------------
/cli/commands/start.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "os"
8 | "os/exec"
9 | "path/filepath"
10 |
11 | "github.com/fatih/color"
12 | "github.com/fsnotify/fsnotify"
13 | "github.com/spf13/cobra"
14 | )
15 |
16 | var StartCmd = &cobra.Command{
17 | Use: "start",
18 | Short: "this starts a live-server which runs on localhost:8080",
19 | Long: `This runs a local development server which serves your WebAssembly application
20 | running on port 8080. This will automatically watch for changes to your .go source
21 | files and automatically recompile the lib.wasm file every time a change is made
22 | `,
23 | Run: runServer,
24 | }
25 |
26 | func runServer(cmd *cobra.Command, args []string) {
27 | color.Green("Starting Development Server on http://localhost:8080")
28 | go http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))
29 |
30 | watcher, err := fsnotify.NewWatcher()
31 | if err != nil {
32 | fmt.Println("ERROR", err)
33 | }
34 | defer watcher.Close()
35 |
36 | done := make(chan bool)
37 |
38 | go func() {
39 | for {
40 | select {
41 | case event := <-watcher.Events:
42 | color.Cyan("New Change Detected")
43 | if event.Op&fsnotify.Write == fsnotify.Write {
44 | color.Green("modified file: %s\n", event.Name)
45 |
46 | if filepath.Ext(event.Name) == ".go" {
47 | err := recompile()
48 | if err != nil {
49 | color.Red("Failed to Recompile")
50 | os.Exit(1)
51 | }
52 | }
53 |
54 | }
55 |
56 | case err := <-watcher.Errors:
57 | fmt.Println("Error: ", err)
58 | }
59 | }
60 | }()
61 |
62 | err = watcher.Add(".")
63 | if err != nil {
64 | log.Fatal(err)
65 | }
66 |
67 | <-done
68 | }
69 |
70 | func recompile() error {
71 | out, err := exec.Command("env", "GOOS=js", "GOARCH=wasm", "go", "build", "-o", "lib.wasm", "main.go").Output()
72 | if err != nil {
73 | fmt.Println("err: ", err)
74 | return err
75 | }
76 | fmt.Println(string(out[:]))
77 | color.Green("Successfully Recompiled WebAssembly Binary...")
78 | return nil
79 | }
80 |
--------------------------------------------------------------------------------
/cli/commands/new.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "io/ioutil"
5 | "net/http"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/fatih/color"
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | var files = map[string]string{
14 | "static/app.css": "https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/master/examples/template/static/app.css",
15 | "static/entrypoint.js": "https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/master/examples/template/static/entrypoint.js",
16 | "static/wasm_exec.js": "https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/master/examples/template/static/wasm_exec.js",
17 | "main.go": "https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/master/examples/template/main.go",
18 | "index.html": "https://raw.githubusercontent.com/elliotforbes/go-webassembly-framework/master/examples/template/index.html",
19 | }
20 |
21 | var NewCmd = &cobra.Command{
22 | Use: "new",
23 | Short: "Automatically creates a new WebAssembly Project",
24 | Long: `Automatically generates the files and folder structure needed
25 | for a new WebAssembly application.`,
26 | Args: cobra.MinimumNArgs(1),
27 | Run: newExecute,
28 | }
29 |
30 | func newExecute(cmd *cobra.Command, args []string) {
31 | color.Green("Generating New Oak WebAssembly Project: %s\n", args[0])
32 |
33 | err := createDirectories(args[0])
34 | if err != nil {
35 | color.Red("Error Creating new WebAssembly Project")
36 | os.Exit(1)
37 | }
38 |
39 | err = getFiles(args[0])
40 | if err != nil {
41 | color.Red("Error Creating new WebAssembly Project")
42 | os.Exit(1)
43 | }
44 |
45 | }
46 |
47 | func createDirectories(projectname string) error {
48 | basepath := filepath.Join(projectname)
49 |
50 | dirs := []string{
51 | filepath.Join(basepath, "components"),
52 | filepath.Join(basepath, "static"),
53 | }
54 |
55 | for _, dir := range dirs {
56 | if err := os.MkdirAll(dir, 0777); err != nil {
57 | color.Red("Error when generating new WebAssembly Project")
58 | color.Red("Error: %+v\n", err)
59 | return err
60 | }
61 | color.Green("Successfully Created Directory: %s\n", dir)
62 | }
63 | return nil
64 | }
65 |
66 | func getFiles(projectname string) error {
67 | for key, location := range files {
68 | resp, err := http.Get(location)
69 | if err != nil {
70 | color.Red("Error: %s\n", err)
71 | return err
72 | }
73 | responseData, err := ioutil.ReadAll(resp.Body)
74 | if err != nil {
75 | color.Red("Error: %s\n", err)
76 | return err
77 | }
78 |
79 | err = ioutil.WriteFile(filepath.Join(projectname, key), responseData, 0777)
80 | if err != nil {
81 | color.Red("Error: %s\n", err)
82 | return err
83 | }
84 | color.Green("Successfully Created File: %s\n", key)
85 |
86 | }
87 | return nil
88 | }
89 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | Oak - The Go WebAssembly Framework
2 | ===================================
3 |
4 | [](https://godoc.org/github.com/elliotforbes/go-webassembly-framework) [](https://travis-ci.org/elliotforbes/go-webassembly-framework) [](https://goreportcard.com/report/github.com/elliotforbes/go-webassembly-framework)
5 |
6 |
7 |
8 |
9 | With the advent of Go supporting WebAssembly, I thought I'd take a crack at building a really simple Go based WebAssembly framework that allows you to build simple frontend applications in Go, without having to dive too deep into the bushes.
10 |
11 | ---
12 |
13 | ## Goals
14 |
15 | * Easier frontend application development using Go
16 |
17 | ## Tutorial
18 |
19 | A tutorial describing Oak is avaiable here:
20 | https://tutorialedge.net/golang/writing-frontend-web-framework-webassembly-go/
21 |
22 | ## CLI
23 |
24 | If you want to easily run the example in this project, I suggest you try out the new `Oak CLI` which attempts to simplify the task of writing WebAssembly applications in Go.
25 |
26 | ```s
27 | $ make build-cli
28 | $ cd examples/blog
29 | $ ./oak start
30 | Starting Server
31 | 2019/01/06 12:00:37 listening on ":8080"...
32 | ```
33 |
34 | ## Simple Example
35 |
36 | Let's take a look at how this framework could be used in a very simple example. We'll be create a really simple app that features on function, `mycoolfunc()`. We'll kick off our Oak framework within our `main()` function and then we'll register our `coolfunc()` function.
37 |
38 | ```go
39 | package main
40 |
41 | import (
42 | "syscall/js"
43 |
44 | "github.com/elliotforbes/oak"
45 | )
46 |
47 | func mycoolfunc(i []js.Value) {
48 | println("My Awesome Function")
49 | }
50 |
51 | func main() {
52 | oak.Start()
53 | oak.RegisterFunction("coolfunc", mycoolfunc)
54 | // keeps our app running
55 | done := make(chan struct{}, 0)
56 | <-done
57 | }
58 | ```
59 |
60 | We can then call our `coolfunc()` function from our `index.html` like so:
61 |
62 | ```html
63 |
64 |
65 |
66 |
67 |
68 | Go wasm
69 |
70 |
71 |
72 |
73 | Super Simple Example
74 | My Cool Func
75 |
76 |
77 |
78 | ```
79 |
80 | ## Components
81 |
82 | ```go
83 | package components
84 |
85 | import (
86 | "syscall/js"
87 |
88 | "github.com/elliotforbes/oak"
89 | )
90 |
91 | type AboutComponent struct{}
92 |
93 | var About AboutComponent
94 |
95 | func init() {
96 | oak.RegisterFunction("coolFunc", CoolFunc)
97 | }
98 |
99 | func CoolFunc(i []js.Value) {
100 | println("does stuff")
101 | }
102 |
103 | func (a AboutComponent) Render() string {
104 | return `
105 |
About Component Actually Works
106 | Cool Func
107 | `
108 | }
109 | ```
110 |
111 | ## Routing
112 |
113 | ```go
114 | package main
115 |
116 | import (
117 | "github.com/elliotforbes/oak"
118 | "github.com/elliotforbes/oak/router"
119 |
120 | "github.com/elliotforbes/oak/examples/blog/components"
121 | )
122 |
123 | func main() {
124 | // Starts the Oak framework
125 | oak.Start()
126 |
127 | // Starts our Router
128 | router.NewRouter()
129 | router.RegisterRoute("about", aboutComponent)
130 |
131 | // keeps our app running
132 | done := make(chan struct{}, 0)
133 | <-done
134 | }
135 | ```
136 |
137 | ```html
138 |
139 |
140 |
141 |
142 |
143 | Blog
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
A Simple Blog
153 |
154 |
155 |
156 |
Home
157 |
About
158 |
159 |
160 |
161 |
162 |
163 | ```
164 |
--------------------------------------------------------------------------------
/examples/SelfQuoting/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quotizer
7 |
8 |
9 |
10 |
11 |
12 |
13 | Seleccione un sistema operativo
14 |
19 |
20 |
21 |
GB
22 |
23 |
24 |
25 |
Disco
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/blog/static/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
7 | const isNodeJS = typeof process !== "undefined";
8 | if (isNodeJS) {
9 | global.require = require;
10 | global.fs = require("fs");
11 |
12 | const nodeCrypto = require("crypto");
13 | global.crypto = {
14 | getRandomValues(b) {
15 | nodeCrypto.randomFillSync(b);
16 | },
17 | };
18 |
19 | global.performance = {
20 | now() {
21 | const [sec, nsec] = process.hrtime();
22 | return sec * 1000 + nsec / 1000000;
23 | },
24 | };
25 |
26 | const util = require("util");
27 | global.TextEncoder = util.TextEncoder;
28 | global.TextDecoder = util.TextDecoder;
29 | } else {
30 | if (typeof window !== "undefined") {
31 | window.global = window;
32 | } else if (typeof self !== "undefined") {
33 | self.global = self;
34 | } else {
35 | throw new Error("cannot export Go (neither window nor self is defined)");
36 | }
37 |
38 | let outputBuf = "";
39 | global.fs = {
40 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
41 | writeSync(fd, buf) {
42 | outputBuf += decoder.decode(buf);
43 | const nl = outputBuf.lastIndexOf("\n");
44 | if (nl != -1) {
45 | console.log(outputBuf.substr(0, nl));
46 | outputBuf = outputBuf.substr(nl + 1);
47 | }
48 | return buf.length;
49 | },
50 | openSync(path, flags, mode) {
51 | const err = new Error("not implemented");
52 | err.code = "ENOSYS";
53 | throw err;
54 | },
55 | };
56 | }
57 |
58 | const encoder = new TextEncoder("utf-8");
59 | const decoder = new TextDecoder("utf-8");
60 |
61 | global.Go = class {
62 | constructor() {
63 | this.argv = ["js"];
64 | this.env = {};
65 | this.exit = (code) => {
66 | if (code !== 0) {
67 | console.warn("exit code:", code);
68 | }
69 | };
70 | this._callbackTimeouts = new Map();
71 | this._nextCallbackTimeoutID = 1;
72 |
73 | const mem = () => {
74 | // The buffer may change when requesting more memory.
75 | return new DataView(this._inst.exports.mem.buffer);
76 | }
77 |
78 | const setInt64 = (addr, v) => {
79 | mem().setUint32(addr + 0, v, true);
80 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
81 | }
82 |
83 | const getInt64 = (addr) => {
84 | const low = mem().getUint32(addr + 0, true);
85 | const high = mem().getInt32(addr + 4, true);
86 | return low + high * 4294967296;
87 | }
88 |
89 | const loadValue = (addr) => {
90 | const f = mem().getFloat64(addr, true);
91 | if (!isNaN(f)) {
92 | return f;
93 | }
94 |
95 | const id = mem().getUint32(addr, true);
96 | return this._values[id];
97 | }
98 |
99 | const storeValue = (addr, v) => {
100 | const nanHead = 0x7FF80000;
101 |
102 | if (typeof v === "number") {
103 | if (isNaN(v)) {
104 | mem().setUint32(addr + 4, nanHead, true);
105 | mem().setUint32(addr, 0, true);
106 | return;
107 | }
108 | mem().setFloat64(addr, v, true);
109 | return;
110 | }
111 |
112 | switch (v) {
113 | case undefined:
114 | mem().setUint32(addr + 4, nanHead, true);
115 | mem().setUint32(addr, 1, true);
116 | return;
117 | case null:
118 | mem().setUint32(addr + 4, nanHead, true);
119 | mem().setUint32(addr, 2, true);
120 | return;
121 | case true:
122 | mem().setUint32(addr + 4, nanHead, true);
123 | mem().setUint32(addr, 3, true);
124 | return;
125 | case false:
126 | mem().setUint32(addr + 4, nanHead, true);
127 | mem().setUint32(addr, 4, true);
128 | return;
129 | }
130 |
131 | let ref = this._refs.get(v);
132 | if (ref === undefined) {
133 | ref = this._values.length;
134 | this._values.push(v);
135 | this._refs.set(v, ref);
136 | }
137 | let typeFlag = 0;
138 | switch (typeof v) {
139 | case "string":
140 | typeFlag = 1;
141 | break;
142 | case "symbol":
143 | typeFlag = 2;
144 | break;
145 | case "function":
146 | typeFlag = 3;
147 | break;
148 | }
149 | mem().setUint32(addr + 4, nanHead | typeFlag, true);
150 | mem().setUint32(addr, ref, true);
151 | }
152 |
153 | const loadSlice = (addr) => {
154 | const array = getInt64(addr + 0);
155 | const len = getInt64(addr + 8);
156 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
157 | }
158 |
159 | const loadSliceOfValues = (addr) => {
160 | const array = getInt64(addr + 0);
161 | const len = getInt64(addr + 8);
162 | const a = new Array(len);
163 | for (let i = 0; i < len; i++) {
164 | a[i] = loadValue(array + i * 8);
165 | }
166 | return a;
167 | }
168 |
169 | const loadString = (addr) => {
170 | const saddr = getInt64(addr + 0);
171 | const len = getInt64(addr + 8);
172 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
173 | }
174 |
175 | const timeOrigin = Date.now() - performance.now();
176 | this.importObject = {
177 | go: {
178 | // func wasmExit(code int32)
179 | "runtime.wasmExit": (sp) => {
180 | const code = mem().getInt32(sp + 8, true);
181 | this.exited = true;
182 | delete this._inst;
183 | delete this._values;
184 | delete this._refs;
185 | this.exit(code);
186 | },
187 |
188 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
189 | "runtime.wasmWrite": (sp) => {
190 | const fd = getInt64(sp + 8);
191 | const p = getInt64(sp + 16);
192 | const n = mem().getInt32(sp + 24, true);
193 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
194 | },
195 |
196 | // func nanotime() int64
197 | "runtime.nanotime": (sp) => {
198 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
199 | },
200 |
201 | // func walltime() (sec int64, nsec int32)
202 | "runtime.walltime": (sp) => {
203 | const msec = (new Date).getTime();
204 | setInt64(sp + 8, msec / 1000);
205 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
206 | },
207 |
208 | // func scheduleCallback(delay int64) int32
209 | "runtime.scheduleCallback": (sp) => {
210 | const id = this._nextCallbackTimeoutID;
211 | this._nextCallbackTimeoutID++;
212 | this._callbackTimeouts.set(id, setTimeout(
213 | () => { this._resolveCallbackPromise(); },
214 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
215 | ));
216 | mem().setInt32(sp + 16, id, true);
217 | },
218 |
219 | // func clearScheduledCallback(id int32)
220 | "runtime.clearScheduledCallback": (sp) => {
221 | const id = mem().getInt32(sp + 8, true);
222 | clearTimeout(this._callbackTimeouts.get(id));
223 | this._callbackTimeouts.delete(id);
224 | },
225 |
226 | // func getRandomData(r []byte)
227 | "runtime.getRandomData": (sp) => {
228 | crypto.getRandomValues(loadSlice(sp + 8));
229 | },
230 |
231 | // func stringVal(value string) ref
232 | "syscall/js.stringVal": (sp) => {
233 | storeValue(sp + 24, loadString(sp + 8));
234 | },
235 |
236 | // func valueGet(v ref, p string) ref
237 | "syscall/js.valueGet": (sp) => {
238 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
239 | },
240 |
241 | // func valueSet(v ref, p string, x ref)
242 | "syscall/js.valueSet": (sp) => {
243 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
244 | },
245 |
246 | // func valueIndex(v ref, i int) ref
247 | "syscall/js.valueIndex": (sp) => {
248 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
249 | },
250 |
251 | // valueSetIndex(v ref, i int, x ref)
252 | "syscall/js.valueSetIndex": (sp) => {
253 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
254 | },
255 |
256 | // func valueCall(v ref, m string, args []ref) (ref, bool)
257 | "syscall/js.valueCall": (sp) => {
258 | try {
259 | const v = loadValue(sp + 8);
260 | const m = Reflect.get(v, loadString(sp + 16));
261 | const args = loadSliceOfValues(sp + 32);
262 | storeValue(sp + 56, Reflect.apply(m, v, args));
263 | mem().setUint8(sp + 64, 1);
264 | } catch (err) {
265 | storeValue(sp + 56, err);
266 | mem().setUint8(sp + 64, 0);
267 | }
268 | },
269 |
270 | // func valueInvoke(v ref, args []ref) (ref, bool)
271 | "syscall/js.valueInvoke": (sp) => {
272 | try {
273 | const v = loadValue(sp + 8);
274 | const args = loadSliceOfValues(sp + 16);
275 | storeValue(sp + 40, Reflect.apply(v, undefined, args));
276 | mem().setUint8(sp + 48, 1);
277 | } catch (err) {
278 | storeValue(sp + 40, err);
279 | mem().setUint8(sp + 48, 0);
280 | }
281 | },
282 |
283 | // func valueNew(v ref, args []ref) (ref, bool)
284 | "syscall/js.valueNew": (sp) => {
285 | try {
286 | const v = loadValue(sp + 8);
287 | const args = loadSliceOfValues(sp + 16);
288 | storeValue(sp + 40, Reflect.construct(v, args));
289 | mem().setUint8(sp + 48, 1);
290 | } catch (err) {
291 | storeValue(sp + 40, err);
292 | mem().setUint8(sp + 48, 0);
293 | }
294 | },
295 |
296 | // func valueLength(v ref) int
297 | "syscall/js.valueLength": (sp) => {
298 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
299 | },
300 |
301 | // valuePrepareString(v ref) (ref, int)
302 | "syscall/js.valuePrepareString": (sp) => {
303 | const str = encoder.encode(String(loadValue(sp + 8)));
304 | storeValue(sp + 16, str);
305 | setInt64(sp + 24, str.length);
306 | },
307 |
308 | // valueLoadString(v ref, b []byte)
309 | "syscall/js.valueLoadString": (sp) => {
310 | const str = loadValue(sp + 8);
311 | loadSlice(sp + 16).set(str);
312 | },
313 |
314 | // func valueInstanceOf(v ref, t ref) bool
315 | "syscall/js.valueInstanceOf": (sp) => {
316 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
317 | },
318 |
319 | "debug": (value) => {
320 | console.log(value);
321 | },
322 | }
323 | };
324 | }
325 |
326 | async run(instance) {
327 | this._inst = instance;
328 | this._values = [ // TODO: garbage collection
329 | NaN,
330 | undefined,
331 | null,
332 | true,
333 | false,
334 | global,
335 | this._inst.exports.mem,
336 | this,
337 | ];
338 | this._refs = new Map();
339 | this._callbackShutdown = false;
340 | this.exited = false;
341 |
342 | const mem = new DataView(this._inst.exports.mem.buffer)
343 |
344 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
345 | let offset = 4096;
346 |
347 | const strPtr = (str) => {
348 | let ptr = offset;
349 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
350 | offset += str.length + (8 - (str.length % 8));
351 | return ptr;
352 | };
353 |
354 | const argc = this.argv.length;
355 |
356 | const argvPtrs = [];
357 | this.argv.forEach((arg) => {
358 | argvPtrs.push(strPtr(arg));
359 | });
360 |
361 | const keys = Object.keys(this.env).sort();
362 | argvPtrs.push(keys.length);
363 | keys.forEach((key) => {
364 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
365 | });
366 |
367 | const argv = offset;
368 | argvPtrs.forEach((ptr) => {
369 | mem.setUint32(offset, ptr, true);
370 | mem.setUint32(offset + 4, 0, true);
371 | offset += 8;
372 | });
373 |
374 | while (true) {
375 | const callbackPromise = new Promise((resolve) => {
376 | this._resolveCallbackPromise = () => {
377 | if (this.exited) {
378 | throw new Error("bad callback: Go program has already exited");
379 | }
380 | setTimeout(resolve, 0); // make sure it is asynchronous
381 | };
382 | });
383 | this._inst.exports.run(argc, argv);
384 | if (this.exited) {
385 | break;
386 | }
387 | await callbackPromise;
388 | }
389 | }
390 |
391 | static _makeCallbackHelper(id, pendingCallbacks, go) {
392 | return function() {
393 | pendingCallbacks.push({ id: id, args: arguments });
394 | go._resolveCallbackPromise();
395 | };
396 | }
397 |
398 | static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
399 | return function(event) {
400 | if (preventDefault) {
401 | event.preventDefault();
402 | }
403 | if (stopPropagation) {
404 | event.stopPropagation();
405 | }
406 | if (stopImmediatePropagation) {
407 | event.stopImmediatePropagation();
408 | }
409 | fn(event);
410 | };
411 | }
412 | }
413 | })();
414 |
--------------------------------------------------------------------------------
/examples/calculator/static/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
7 | const isNodeJS = typeof process !== "undefined";
8 | if (isNodeJS) {
9 | global.require = require;
10 | global.fs = require("fs");
11 |
12 | const nodeCrypto = require("crypto");
13 | global.crypto = {
14 | getRandomValues(b) {
15 | nodeCrypto.randomFillSync(b);
16 | },
17 | };
18 |
19 | global.performance = {
20 | now() {
21 | const [sec, nsec] = process.hrtime();
22 | return sec * 1000 + nsec / 1000000;
23 | },
24 | };
25 |
26 | const util = require("util");
27 | global.TextEncoder = util.TextEncoder;
28 | global.TextDecoder = util.TextDecoder;
29 | } else {
30 | if (typeof window !== "undefined") {
31 | window.global = window;
32 | } else if (typeof self !== "undefined") {
33 | self.global = self;
34 | } else {
35 | throw new Error("cannot export Go (neither window nor self is defined)");
36 | }
37 |
38 | let outputBuf = "";
39 | global.fs = {
40 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
41 | writeSync(fd, buf) {
42 | outputBuf += decoder.decode(buf);
43 | const nl = outputBuf.lastIndexOf("\n");
44 | if (nl != -1) {
45 | console.log(outputBuf.substr(0, nl));
46 | outputBuf = outputBuf.substr(nl + 1);
47 | }
48 | return buf.length;
49 | },
50 | openSync(path, flags, mode) {
51 | const err = new Error("not implemented");
52 | err.code = "ENOSYS";
53 | throw err;
54 | },
55 | };
56 | }
57 |
58 | const encoder = new TextEncoder("utf-8");
59 | const decoder = new TextDecoder("utf-8");
60 |
61 | global.Go = class {
62 | constructor() {
63 | this.argv = ["js"];
64 | this.env = {};
65 | this.exit = (code) => {
66 | if (code !== 0) {
67 | console.warn("exit code:", code);
68 | }
69 | };
70 | this._callbackTimeouts = new Map();
71 | this._nextCallbackTimeoutID = 1;
72 |
73 | const mem = () => {
74 | // The buffer may change when requesting more memory.
75 | return new DataView(this._inst.exports.mem.buffer);
76 | }
77 |
78 | const setInt64 = (addr, v) => {
79 | mem().setUint32(addr + 0, v, true);
80 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
81 | }
82 |
83 | const getInt64 = (addr) => {
84 | const low = mem().getUint32(addr + 0, true);
85 | const high = mem().getInt32(addr + 4, true);
86 | return low + high * 4294967296;
87 | }
88 |
89 | const loadValue = (addr) => {
90 | const f = mem().getFloat64(addr, true);
91 | if (!isNaN(f)) {
92 | return f;
93 | }
94 |
95 | const id = mem().getUint32(addr, true);
96 | return this._values[id];
97 | }
98 |
99 | const storeValue = (addr, v) => {
100 | const nanHead = 0x7FF80000;
101 |
102 | if (typeof v === "number") {
103 | if (isNaN(v)) {
104 | mem().setUint32(addr + 4, nanHead, true);
105 | mem().setUint32(addr, 0, true);
106 | return;
107 | }
108 | mem().setFloat64(addr, v, true);
109 | return;
110 | }
111 |
112 | switch (v) {
113 | case undefined:
114 | mem().setUint32(addr + 4, nanHead, true);
115 | mem().setUint32(addr, 1, true);
116 | return;
117 | case null:
118 | mem().setUint32(addr + 4, nanHead, true);
119 | mem().setUint32(addr, 2, true);
120 | return;
121 | case true:
122 | mem().setUint32(addr + 4, nanHead, true);
123 | mem().setUint32(addr, 3, true);
124 | return;
125 | case false:
126 | mem().setUint32(addr + 4, nanHead, true);
127 | mem().setUint32(addr, 4, true);
128 | return;
129 | }
130 |
131 | let ref = this._refs.get(v);
132 | if (ref === undefined) {
133 | ref = this._values.length;
134 | this._values.push(v);
135 | this._refs.set(v, ref);
136 | }
137 | let typeFlag = 0;
138 | switch (typeof v) {
139 | case "string":
140 | typeFlag = 1;
141 | break;
142 | case "symbol":
143 | typeFlag = 2;
144 | break;
145 | case "function":
146 | typeFlag = 3;
147 | break;
148 | }
149 | mem().setUint32(addr + 4, nanHead | typeFlag, true);
150 | mem().setUint32(addr, ref, true);
151 | }
152 |
153 | const loadSlice = (addr) => {
154 | const array = getInt64(addr + 0);
155 | const len = getInt64(addr + 8);
156 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
157 | }
158 |
159 | const loadSliceOfValues = (addr) => {
160 | const array = getInt64(addr + 0);
161 | const len = getInt64(addr + 8);
162 | const a = new Array(len);
163 | for (let i = 0; i < len; i++) {
164 | a[i] = loadValue(array + i * 8);
165 | }
166 | return a;
167 | }
168 |
169 | const loadString = (addr) => {
170 | const saddr = getInt64(addr + 0);
171 | const len = getInt64(addr + 8);
172 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
173 | }
174 |
175 | const timeOrigin = Date.now() - performance.now();
176 | this.importObject = {
177 | go: {
178 | // func wasmExit(code int32)
179 | "runtime.wasmExit": (sp) => {
180 | const code = mem().getInt32(sp + 8, true);
181 | this.exited = true;
182 | delete this._inst;
183 | delete this._values;
184 | delete this._refs;
185 | this.exit(code);
186 | },
187 |
188 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
189 | "runtime.wasmWrite": (sp) => {
190 | const fd = getInt64(sp + 8);
191 | const p = getInt64(sp + 16);
192 | const n = mem().getInt32(sp + 24, true);
193 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
194 | },
195 |
196 | // func nanotime() int64
197 | "runtime.nanotime": (sp) => {
198 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
199 | },
200 |
201 | // func walltime() (sec int64, nsec int32)
202 | "runtime.walltime": (sp) => {
203 | const msec = (new Date).getTime();
204 | setInt64(sp + 8, msec / 1000);
205 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
206 | },
207 |
208 | // func scheduleCallback(delay int64) int32
209 | "runtime.scheduleCallback": (sp) => {
210 | const id = this._nextCallbackTimeoutID;
211 | this._nextCallbackTimeoutID++;
212 | this._callbackTimeouts.set(id, setTimeout(
213 | () => { this._resolveCallbackPromise(); },
214 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
215 | ));
216 | mem().setInt32(sp + 16, id, true);
217 | },
218 |
219 | // func clearScheduledCallback(id int32)
220 | "runtime.clearScheduledCallback": (sp) => {
221 | const id = mem().getInt32(sp + 8, true);
222 | clearTimeout(this._callbackTimeouts.get(id));
223 | this._callbackTimeouts.delete(id);
224 | },
225 |
226 | // func getRandomData(r []byte)
227 | "runtime.getRandomData": (sp) => {
228 | crypto.getRandomValues(loadSlice(sp + 8));
229 | },
230 |
231 | // func stringVal(value string) ref
232 | "syscall/js.stringVal": (sp) => {
233 | storeValue(sp + 24, loadString(sp + 8));
234 | },
235 |
236 | // func valueGet(v ref, p string) ref
237 | "syscall/js.valueGet": (sp) => {
238 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
239 | },
240 |
241 | // func valueSet(v ref, p string, x ref)
242 | "syscall/js.valueSet": (sp) => {
243 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
244 | },
245 |
246 | // func valueIndex(v ref, i int) ref
247 | "syscall/js.valueIndex": (sp) => {
248 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
249 | },
250 |
251 | // valueSetIndex(v ref, i int, x ref)
252 | "syscall/js.valueSetIndex": (sp) => {
253 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
254 | },
255 |
256 | // func valueCall(v ref, m string, args []ref) (ref, bool)
257 | "syscall/js.valueCall": (sp) => {
258 | try {
259 | const v = loadValue(sp + 8);
260 | const m = Reflect.get(v, loadString(sp + 16));
261 | const args = loadSliceOfValues(sp + 32);
262 | storeValue(sp + 56, Reflect.apply(m, v, args));
263 | mem().setUint8(sp + 64, 1);
264 | } catch (err) {
265 | storeValue(sp + 56, err);
266 | mem().setUint8(sp + 64, 0);
267 | }
268 | },
269 |
270 | // func valueInvoke(v ref, args []ref) (ref, bool)
271 | "syscall/js.valueInvoke": (sp) => {
272 | try {
273 | const v = loadValue(sp + 8);
274 | const args = loadSliceOfValues(sp + 16);
275 | storeValue(sp + 40, Reflect.apply(v, undefined, args));
276 | mem().setUint8(sp + 48, 1);
277 | } catch (err) {
278 | storeValue(sp + 40, err);
279 | mem().setUint8(sp + 48, 0);
280 | }
281 | },
282 |
283 | // func valueNew(v ref, args []ref) (ref, bool)
284 | "syscall/js.valueNew": (sp) => {
285 | try {
286 | const v = loadValue(sp + 8);
287 | const args = loadSliceOfValues(sp + 16);
288 | storeValue(sp + 40, Reflect.construct(v, args));
289 | mem().setUint8(sp + 48, 1);
290 | } catch (err) {
291 | storeValue(sp + 40, err);
292 | mem().setUint8(sp + 48, 0);
293 | }
294 | },
295 |
296 | // func valueLength(v ref) int
297 | "syscall/js.valueLength": (sp) => {
298 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
299 | },
300 |
301 | // valuePrepareString(v ref) (ref, int)
302 | "syscall/js.valuePrepareString": (sp) => {
303 | const str = encoder.encode(String(loadValue(sp + 8)));
304 | storeValue(sp + 16, str);
305 | setInt64(sp + 24, str.length);
306 | },
307 |
308 | // valueLoadString(v ref, b []byte)
309 | "syscall/js.valueLoadString": (sp) => {
310 | const str = loadValue(sp + 8);
311 | loadSlice(sp + 16).set(str);
312 | },
313 |
314 | // func valueInstanceOf(v ref, t ref) bool
315 | "syscall/js.valueInstanceOf": (sp) => {
316 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
317 | },
318 |
319 | "debug": (value) => {
320 | console.log(value);
321 | },
322 | }
323 | };
324 | }
325 |
326 | async run(instance) {
327 | this._inst = instance;
328 | this._values = [ // TODO: garbage collection
329 | NaN,
330 | undefined,
331 | null,
332 | true,
333 | false,
334 | global,
335 | this._inst.exports.mem,
336 | this,
337 | ];
338 | this._refs = new Map();
339 | this._callbackShutdown = false;
340 | this.exited = false;
341 |
342 | const mem = new DataView(this._inst.exports.mem.buffer)
343 |
344 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
345 | let offset = 4096;
346 |
347 | const strPtr = (str) => {
348 | let ptr = offset;
349 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
350 | offset += str.length + (8 - (str.length % 8));
351 | return ptr;
352 | };
353 |
354 | const argc = this.argv.length;
355 |
356 | const argvPtrs = [];
357 | this.argv.forEach((arg) => {
358 | argvPtrs.push(strPtr(arg));
359 | });
360 |
361 | const keys = Object.keys(this.env).sort();
362 | argvPtrs.push(keys.length);
363 | keys.forEach((key) => {
364 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
365 | });
366 |
367 | const argv = offset;
368 | argvPtrs.forEach((ptr) => {
369 | mem.setUint32(offset, ptr, true);
370 | mem.setUint32(offset + 4, 0, true);
371 | offset += 8;
372 | });
373 |
374 | while (true) {
375 | const callbackPromise = new Promise((resolve) => {
376 | this._resolveCallbackPromise = () => {
377 | if (this.exited) {
378 | throw new Error("bad callback: Go program has already exited");
379 | }
380 | setTimeout(resolve, 0); // make sure it is asynchronous
381 | };
382 | });
383 | this._inst.exports.run(argc, argv);
384 | if (this.exited) {
385 | break;
386 | }
387 | await callbackPromise;
388 | }
389 | }
390 |
391 | static _makeCallbackHelper(id, pendingCallbacks, go) {
392 | return function() {
393 | pendingCallbacks.push({ id: id, args: arguments });
394 | go._resolveCallbackPromise();
395 | };
396 | }
397 |
398 | static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
399 | return function(event) {
400 | if (preventDefault) {
401 | event.preventDefault();
402 | }
403 | if (stopPropagation) {
404 | event.stopPropagation();
405 | }
406 | if (stopImmediatePropagation) {
407 | event.stopImmediatePropagation();
408 | }
409 | fn(event);
410 | };
411 | }
412 | }
413 | })();
414 |
--------------------------------------------------------------------------------
/examples/template/static/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
7 | const isNodeJS = typeof process !== "undefined";
8 | if (isNodeJS) {
9 | global.require = require;
10 | global.fs = require("fs");
11 |
12 | const nodeCrypto = require("crypto");
13 | global.crypto = {
14 | getRandomValues(b) {
15 | nodeCrypto.randomFillSync(b);
16 | },
17 | };
18 |
19 | global.performance = {
20 | now() {
21 | const [sec, nsec] = process.hrtime();
22 | return sec * 1000 + nsec / 1000000;
23 | },
24 | };
25 |
26 | const util = require("util");
27 | global.TextEncoder = util.TextEncoder;
28 | global.TextDecoder = util.TextDecoder;
29 | } else {
30 | if (typeof window !== "undefined") {
31 | window.global = window;
32 | } else if (typeof self !== "undefined") {
33 | self.global = self;
34 | } else {
35 | throw new Error("cannot export Go (neither window nor self is defined)");
36 | }
37 |
38 | let outputBuf = "";
39 | global.fs = {
40 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
41 | writeSync(fd, buf) {
42 | outputBuf += decoder.decode(buf);
43 | const nl = outputBuf.lastIndexOf("\n");
44 | if (nl != -1) {
45 | console.log(outputBuf.substr(0, nl));
46 | outputBuf = outputBuf.substr(nl + 1);
47 | }
48 | return buf.length;
49 | },
50 | openSync(path, flags, mode) {
51 | const err = new Error("not implemented");
52 | err.code = "ENOSYS";
53 | throw err;
54 | },
55 | };
56 | }
57 |
58 | const encoder = new TextEncoder("utf-8");
59 | const decoder = new TextDecoder("utf-8");
60 |
61 | global.Go = class {
62 | constructor() {
63 | this.argv = ["js"];
64 | this.env = {};
65 | this.exit = (code) => {
66 | if (code !== 0) {
67 | console.warn("exit code:", code);
68 | }
69 | };
70 | this._callbackTimeouts = new Map();
71 | this._nextCallbackTimeoutID = 1;
72 |
73 | const mem = () => {
74 | // The buffer may change when requesting more memory.
75 | return new DataView(this._inst.exports.mem.buffer);
76 | }
77 |
78 | const setInt64 = (addr, v) => {
79 | mem().setUint32(addr + 0, v, true);
80 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
81 | }
82 |
83 | const getInt64 = (addr) => {
84 | const low = mem().getUint32(addr + 0, true);
85 | const high = mem().getInt32(addr + 4, true);
86 | return low + high * 4294967296;
87 | }
88 |
89 | const loadValue = (addr) => {
90 | const f = mem().getFloat64(addr, true);
91 | if (!isNaN(f)) {
92 | return f;
93 | }
94 |
95 | const id = mem().getUint32(addr, true);
96 | return this._values[id];
97 | }
98 |
99 | const storeValue = (addr, v) => {
100 | const nanHead = 0x7FF80000;
101 |
102 | if (typeof v === "number") {
103 | if (isNaN(v)) {
104 | mem().setUint32(addr + 4, nanHead, true);
105 | mem().setUint32(addr, 0, true);
106 | return;
107 | }
108 | mem().setFloat64(addr, v, true);
109 | return;
110 | }
111 |
112 | switch (v) {
113 | case undefined:
114 | mem().setUint32(addr + 4, nanHead, true);
115 | mem().setUint32(addr, 1, true);
116 | return;
117 | case null:
118 | mem().setUint32(addr + 4, nanHead, true);
119 | mem().setUint32(addr, 2, true);
120 | return;
121 | case true:
122 | mem().setUint32(addr + 4, nanHead, true);
123 | mem().setUint32(addr, 3, true);
124 | return;
125 | case false:
126 | mem().setUint32(addr + 4, nanHead, true);
127 | mem().setUint32(addr, 4, true);
128 | return;
129 | }
130 |
131 | let ref = this._refs.get(v);
132 | if (ref === undefined) {
133 | ref = this._values.length;
134 | this._values.push(v);
135 | this._refs.set(v, ref);
136 | }
137 | let typeFlag = 0;
138 | switch (typeof v) {
139 | case "string":
140 | typeFlag = 1;
141 | break;
142 | case "symbol":
143 | typeFlag = 2;
144 | break;
145 | case "function":
146 | typeFlag = 3;
147 | break;
148 | }
149 | mem().setUint32(addr + 4, nanHead | typeFlag, true);
150 | mem().setUint32(addr, ref, true);
151 | }
152 |
153 | const loadSlice = (addr) => {
154 | const array = getInt64(addr + 0);
155 | const len = getInt64(addr + 8);
156 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
157 | }
158 |
159 | const loadSliceOfValues = (addr) => {
160 | const array = getInt64(addr + 0);
161 | const len = getInt64(addr + 8);
162 | const a = new Array(len);
163 | for (let i = 0; i < len; i++) {
164 | a[i] = loadValue(array + i * 8);
165 | }
166 | return a;
167 | }
168 |
169 | const loadString = (addr) => {
170 | const saddr = getInt64(addr + 0);
171 | const len = getInt64(addr + 8);
172 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
173 | }
174 |
175 | const timeOrigin = Date.now() - performance.now();
176 | this.importObject = {
177 | go: {
178 | // func wasmExit(code int32)
179 | "runtime.wasmExit": (sp) => {
180 | const code = mem().getInt32(sp + 8, true);
181 | this.exited = true;
182 | delete this._inst;
183 | delete this._values;
184 | delete this._refs;
185 | this.exit(code);
186 | },
187 |
188 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
189 | "runtime.wasmWrite": (sp) => {
190 | const fd = getInt64(sp + 8);
191 | const p = getInt64(sp + 16);
192 | const n = mem().getInt32(sp + 24, true);
193 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
194 | },
195 |
196 | // func nanotime() int64
197 | "runtime.nanotime": (sp) => {
198 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
199 | },
200 |
201 | // func walltime() (sec int64, nsec int32)
202 | "runtime.walltime": (sp) => {
203 | const msec = (new Date).getTime();
204 | setInt64(sp + 8, msec / 1000);
205 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
206 | },
207 |
208 | // func scheduleCallback(delay int64) int32
209 | "runtime.scheduleCallback": (sp) => {
210 | const id = this._nextCallbackTimeoutID;
211 | this._nextCallbackTimeoutID++;
212 | this._callbackTimeouts.set(id, setTimeout(
213 | () => { this._resolveCallbackPromise(); },
214 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
215 | ));
216 | mem().setInt32(sp + 16, id, true);
217 | },
218 |
219 | // func clearScheduledCallback(id int32)
220 | "runtime.clearScheduledCallback": (sp) => {
221 | const id = mem().getInt32(sp + 8, true);
222 | clearTimeout(this._callbackTimeouts.get(id));
223 | this._callbackTimeouts.delete(id);
224 | },
225 |
226 | // func getRandomData(r []byte)
227 | "runtime.getRandomData": (sp) => {
228 | crypto.getRandomValues(loadSlice(sp + 8));
229 | },
230 |
231 | // func stringVal(value string) ref
232 | "syscall/js.stringVal": (sp) => {
233 | storeValue(sp + 24, loadString(sp + 8));
234 | },
235 |
236 | // func valueGet(v ref, p string) ref
237 | "syscall/js.valueGet": (sp) => {
238 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
239 | },
240 |
241 | // func valueSet(v ref, p string, x ref)
242 | "syscall/js.valueSet": (sp) => {
243 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
244 | },
245 |
246 | // func valueIndex(v ref, i int) ref
247 | "syscall/js.valueIndex": (sp) => {
248 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
249 | },
250 |
251 | // valueSetIndex(v ref, i int, x ref)
252 | "syscall/js.valueSetIndex": (sp) => {
253 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
254 | },
255 |
256 | // func valueCall(v ref, m string, args []ref) (ref, bool)
257 | "syscall/js.valueCall": (sp) => {
258 | try {
259 | const v = loadValue(sp + 8);
260 | const m = Reflect.get(v, loadString(sp + 16));
261 | const args = loadSliceOfValues(sp + 32);
262 | storeValue(sp + 56, Reflect.apply(m, v, args));
263 | mem().setUint8(sp + 64, 1);
264 | } catch (err) {
265 | storeValue(sp + 56, err);
266 | mem().setUint8(sp + 64, 0);
267 | }
268 | },
269 |
270 | // func valueInvoke(v ref, args []ref) (ref, bool)
271 | "syscall/js.valueInvoke": (sp) => {
272 | try {
273 | const v = loadValue(sp + 8);
274 | const args = loadSliceOfValues(sp + 16);
275 | storeValue(sp + 40, Reflect.apply(v, undefined, args));
276 | mem().setUint8(sp + 48, 1);
277 | } catch (err) {
278 | storeValue(sp + 40, err);
279 | mem().setUint8(sp + 48, 0);
280 | }
281 | },
282 |
283 | // func valueNew(v ref, args []ref) (ref, bool)
284 | "syscall/js.valueNew": (sp) => {
285 | try {
286 | const v = loadValue(sp + 8);
287 | const args = loadSliceOfValues(sp + 16);
288 | storeValue(sp + 40, Reflect.construct(v, args));
289 | mem().setUint8(sp + 48, 1);
290 | } catch (err) {
291 | storeValue(sp + 40, err);
292 | mem().setUint8(sp + 48, 0);
293 | }
294 | },
295 |
296 | // func valueLength(v ref) int
297 | "syscall/js.valueLength": (sp) => {
298 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
299 | },
300 |
301 | // valuePrepareString(v ref) (ref, int)
302 | "syscall/js.valuePrepareString": (sp) => {
303 | const str = encoder.encode(String(loadValue(sp + 8)));
304 | storeValue(sp + 16, str);
305 | setInt64(sp + 24, str.length);
306 | },
307 |
308 | // valueLoadString(v ref, b []byte)
309 | "syscall/js.valueLoadString": (sp) => {
310 | const str = loadValue(sp + 8);
311 | loadSlice(sp + 16).set(str);
312 | },
313 |
314 | // func valueInstanceOf(v ref, t ref) bool
315 | "syscall/js.valueInstanceOf": (sp) => {
316 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
317 | },
318 |
319 | "debug": (value) => {
320 | console.log(value);
321 | },
322 | }
323 | };
324 | }
325 |
326 | async run(instance) {
327 | this._inst = instance;
328 | this._values = [ // TODO: garbage collection
329 | NaN,
330 | undefined,
331 | null,
332 | true,
333 | false,
334 | global,
335 | this._inst.exports.mem,
336 | this,
337 | ];
338 | this._refs = new Map();
339 | this._callbackShutdown = false;
340 | this.exited = false;
341 |
342 | const mem = new DataView(this._inst.exports.mem.buffer)
343 |
344 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
345 | let offset = 4096;
346 |
347 | const strPtr = (str) => {
348 | let ptr = offset;
349 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
350 | offset += str.length + (8 - (str.length % 8));
351 | return ptr;
352 | };
353 |
354 | const argc = this.argv.length;
355 |
356 | const argvPtrs = [];
357 | this.argv.forEach((arg) => {
358 | argvPtrs.push(strPtr(arg));
359 | });
360 |
361 | const keys = Object.keys(this.env).sort();
362 | argvPtrs.push(keys.length);
363 | keys.forEach((key) => {
364 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
365 | });
366 |
367 | const argv = offset;
368 | argvPtrs.forEach((ptr) => {
369 | mem.setUint32(offset, ptr, true);
370 | mem.setUint32(offset + 4, 0, true);
371 | offset += 8;
372 | });
373 |
374 | while (true) {
375 | const callbackPromise = new Promise((resolve) => {
376 | this._resolveCallbackPromise = () => {
377 | if (this.exited) {
378 | throw new Error("bad callback: Go program has already exited");
379 | }
380 | setTimeout(resolve, 0); // make sure it is asynchronous
381 | };
382 | });
383 | this._inst.exports.run(argc, argv);
384 | if (this.exited) {
385 | break;
386 | }
387 | await callbackPromise;
388 | }
389 | }
390 |
391 | static _makeCallbackHelper(id, pendingCallbacks, go) {
392 | return function() {
393 | pendingCallbacks.push({ id: id, args: arguments });
394 | go._resolveCallbackPromise();
395 | };
396 | }
397 |
398 | static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
399 | return function(event) {
400 | if (preventDefault) {
401 | event.preventDefault();
402 | }
403 | if (stopPropagation) {
404 | event.stopPropagation();
405 | }
406 | if (stopImmediatePropagation) {
407 | event.stopImmediatePropagation();
408 | }
409 | fn(event);
410 | };
411 | }
412 | }
413 | })();
414 |
--------------------------------------------------------------------------------
/examples/test-project/static/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
7 | const isNodeJS = typeof process !== "undefined";
8 | if (isNodeJS) {
9 | global.require = require;
10 | global.fs = require("fs");
11 |
12 | const nodeCrypto = require("crypto");
13 | global.crypto = {
14 | getRandomValues(b) {
15 | nodeCrypto.randomFillSync(b);
16 | },
17 | };
18 |
19 | global.performance = {
20 | now() {
21 | const [sec, nsec] = process.hrtime();
22 | return sec * 1000 + nsec / 1000000;
23 | },
24 | };
25 |
26 | const util = require("util");
27 | global.TextEncoder = util.TextEncoder;
28 | global.TextDecoder = util.TextDecoder;
29 | } else {
30 | if (typeof window !== "undefined") {
31 | window.global = window;
32 | } else if (typeof self !== "undefined") {
33 | self.global = self;
34 | } else {
35 | throw new Error("cannot export Go (neither window nor self is defined)");
36 | }
37 |
38 | let outputBuf = "";
39 | global.fs = {
40 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
41 | writeSync(fd, buf) {
42 | outputBuf += decoder.decode(buf);
43 | const nl = outputBuf.lastIndexOf("\n");
44 | if (nl != -1) {
45 | console.log(outputBuf.substr(0, nl));
46 | outputBuf = outputBuf.substr(nl + 1);
47 | }
48 | return buf.length;
49 | },
50 | openSync(path, flags, mode) {
51 | const err = new Error("not implemented");
52 | err.code = "ENOSYS";
53 | throw err;
54 | },
55 | };
56 | }
57 |
58 | const encoder = new TextEncoder("utf-8");
59 | const decoder = new TextDecoder("utf-8");
60 |
61 | global.Go = class {
62 | constructor() {
63 | this.argv = ["js"];
64 | this.env = {};
65 | this.exit = (code) => {
66 | if (code !== 0) {
67 | console.warn("exit code:", code);
68 | }
69 | };
70 | this._callbackTimeouts = new Map();
71 | this._nextCallbackTimeoutID = 1;
72 |
73 | const mem = () => {
74 | // The buffer may change when requesting more memory.
75 | return new DataView(this._inst.exports.mem.buffer);
76 | }
77 |
78 | const setInt64 = (addr, v) => {
79 | mem().setUint32(addr + 0, v, true);
80 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
81 | }
82 |
83 | const getInt64 = (addr) => {
84 | const low = mem().getUint32(addr + 0, true);
85 | const high = mem().getInt32(addr + 4, true);
86 | return low + high * 4294967296;
87 | }
88 |
89 | const loadValue = (addr) => {
90 | const f = mem().getFloat64(addr, true);
91 | if (!isNaN(f)) {
92 | return f;
93 | }
94 |
95 | const id = mem().getUint32(addr, true);
96 | return this._values[id];
97 | }
98 |
99 | const storeValue = (addr, v) => {
100 | const nanHead = 0x7FF80000;
101 |
102 | if (typeof v === "number") {
103 | if (isNaN(v)) {
104 | mem().setUint32(addr + 4, nanHead, true);
105 | mem().setUint32(addr, 0, true);
106 | return;
107 | }
108 | mem().setFloat64(addr, v, true);
109 | return;
110 | }
111 |
112 | switch (v) {
113 | case undefined:
114 | mem().setUint32(addr + 4, nanHead, true);
115 | mem().setUint32(addr, 1, true);
116 | return;
117 | case null:
118 | mem().setUint32(addr + 4, nanHead, true);
119 | mem().setUint32(addr, 2, true);
120 | return;
121 | case true:
122 | mem().setUint32(addr + 4, nanHead, true);
123 | mem().setUint32(addr, 3, true);
124 | return;
125 | case false:
126 | mem().setUint32(addr + 4, nanHead, true);
127 | mem().setUint32(addr, 4, true);
128 | return;
129 | }
130 |
131 | let ref = this._refs.get(v);
132 | if (ref === undefined) {
133 | ref = this._values.length;
134 | this._values.push(v);
135 | this._refs.set(v, ref);
136 | }
137 | let typeFlag = 0;
138 | switch (typeof v) {
139 | case "string":
140 | typeFlag = 1;
141 | break;
142 | case "symbol":
143 | typeFlag = 2;
144 | break;
145 | case "function":
146 | typeFlag = 3;
147 | break;
148 | }
149 | mem().setUint32(addr + 4, nanHead | typeFlag, true);
150 | mem().setUint32(addr, ref, true);
151 | }
152 |
153 | const loadSlice = (addr) => {
154 | const array = getInt64(addr + 0);
155 | const len = getInt64(addr + 8);
156 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
157 | }
158 |
159 | const loadSliceOfValues = (addr) => {
160 | const array = getInt64(addr + 0);
161 | const len = getInt64(addr + 8);
162 | const a = new Array(len);
163 | for (let i = 0; i < len; i++) {
164 | a[i] = loadValue(array + i * 8);
165 | }
166 | return a;
167 | }
168 |
169 | const loadString = (addr) => {
170 | const saddr = getInt64(addr + 0);
171 | const len = getInt64(addr + 8);
172 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
173 | }
174 |
175 | const timeOrigin = Date.now() - performance.now();
176 | this.importObject = {
177 | go: {
178 | // func wasmExit(code int32)
179 | "runtime.wasmExit": (sp) => {
180 | const code = mem().getInt32(sp + 8, true);
181 | this.exited = true;
182 | delete this._inst;
183 | delete this._values;
184 | delete this._refs;
185 | this.exit(code);
186 | },
187 |
188 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
189 | "runtime.wasmWrite": (sp) => {
190 | const fd = getInt64(sp + 8);
191 | const p = getInt64(sp + 16);
192 | const n = mem().getInt32(sp + 24, true);
193 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
194 | },
195 |
196 | // func nanotime() int64
197 | "runtime.nanotime": (sp) => {
198 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
199 | },
200 |
201 | // func walltime() (sec int64, nsec int32)
202 | "runtime.walltime": (sp) => {
203 | const msec = (new Date).getTime();
204 | setInt64(sp + 8, msec / 1000);
205 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
206 | },
207 |
208 | // func scheduleCallback(delay int64) int32
209 | "runtime.scheduleCallback": (sp) => {
210 | const id = this._nextCallbackTimeoutID;
211 | this._nextCallbackTimeoutID++;
212 | this._callbackTimeouts.set(id, setTimeout(
213 | () => { this._resolveCallbackPromise(); },
214 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
215 | ));
216 | mem().setInt32(sp + 16, id, true);
217 | },
218 |
219 | // func clearScheduledCallback(id int32)
220 | "runtime.clearScheduledCallback": (sp) => {
221 | const id = mem().getInt32(sp + 8, true);
222 | clearTimeout(this._callbackTimeouts.get(id));
223 | this._callbackTimeouts.delete(id);
224 | },
225 |
226 | // func getRandomData(r []byte)
227 | "runtime.getRandomData": (sp) => {
228 | crypto.getRandomValues(loadSlice(sp + 8));
229 | },
230 |
231 | // func stringVal(value string) ref
232 | "syscall/js.stringVal": (sp) => {
233 | storeValue(sp + 24, loadString(sp + 8));
234 | },
235 |
236 | // func valueGet(v ref, p string) ref
237 | "syscall/js.valueGet": (sp) => {
238 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
239 | },
240 |
241 | // func valueSet(v ref, p string, x ref)
242 | "syscall/js.valueSet": (sp) => {
243 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
244 | },
245 |
246 | // func valueIndex(v ref, i int) ref
247 | "syscall/js.valueIndex": (sp) => {
248 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
249 | },
250 |
251 | // valueSetIndex(v ref, i int, x ref)
252 | "syscall/js.valueSetIndex": (sp) => {
253 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
254 | },
255 |
256 | // func valueCall(v ref, m string, args []ref) (ref, bool)
257 | "syscall/js.valueCall": (sp) => {
258 | try {
259 | const v = loadValue(sp + 8);
260 | const m = Reflect.get(v, loadString(sp + 16));
261 | const args = loadSliceOfValues(sp + 32);
262 | storeValue(sp + 56, Reflect.apply(m, v, args));
263 | mem().setUint8(sp + 64, 1);
264 | } catch (err) {
265 | storeValue(sp + 56, err);
266 | mem().setUint8(sp + 64, 0);
267 | }
268 | },
269 |
270 | // func valueInvoke(v ref, args []ref) (ref, bool)
271 | "syscall/js.valueInvoke": (sp) => {
272 | try {
273 | const v = loadValue(sp + 8);
274 | const args = loadSliceOfValues(sp + 16);
275 | storeValue(sp + 40, Reflect.apply(v, undefined, args));
276 | mem().setUint8(sp + 48, 1);
277 | } catch (err) {
278 | storeValue(sp + 40, err);
279 | mem().setUint8(sp + 48, 0);
280 | }
281 | },
282 |
283 | // func valueNew(v ref, args []ref) (ref, bool)
284 | "syscall/js.valueNew": (sp) => {
285 | try {
286 | const v = loadValue(sp + 8);
287 | const args = loadSliceOfValues(sp + 16);
288 | storeValue(sp + 40, Reflect.construct(v, args));
289 | mem().setUint8(sp + 48, 1);
290 | } catch (err) {
291 | storeValue(sp + 40, err);
292 | mem().setUint8(sp + 48, 0);
293 | }
294 | },
295 |
296 | // func valueLength(v ref) int
297 | "syscall/js.valueLength": (sp) => {
298 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
299 | },
300 |
301 | // valuePrepareString(v ref) (ref, int)
302 | "syscall/js.valuePrepareString": (sp) => {
303 | const str = encoder.encode(String(loadValue(sp + 8)));
304 | storeValue(sp + 16, str);
305 | setInt64(sp + 24, str.length);
306 | },
307 |
308 | // valueLoadString(v ref, b []byte)
309 | "syscall/js.valueLoadString": (sp) => {
310 | const str = loadValue(sp + 8);
311 | loadSlice(sp + 16).set(str);
312 | },
313 |
314 | // func valueInstanceOf(v ref, t ref) bool
315 | "syscall/js.valueInstanceOf": (sp) => {
316 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
317 | },
318 |
319 | "debug": (value) => {
320 | console.log(value);
321 | },
322 | }
323 | };
324 | }
325 |
326 | async run(instance) {
327 | this._inst = instance;
328 | this._values = [ // TODO: garbage collection
329 | NaN,
330 | undefined,
331 | null,
332 | true,
333 | false,
334 | global,
335 | this._inst.exports.mem,
336 | this,
337 | ];
338 | this._refs = new Map();
339 | this._callbackShutdown = false;
340 | this.exited = false;
341 |
342 | const mem = new DataView(this._inst.exports.mem.buffer)
343 |
344 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
345 | let offset = 4096;
346 |
347 | const strPtr = (str) => {
348 | let ptr = offset;
349 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
350 | offset += str.length + (8 - (str.length % 8));
351 | return ptr;
352 | };
353 |
354 | const argc = this.argv.length;
355 |
356 | const argvPtrs = [];
357 | this.argv.forEach((arg) => {
358 | argvPtrs.push(strPtr(arg));
359 | });
360 |
361 | const keys = Object.keys(this.env).sort();
362 | argvPtrs.push(keys.length);
363 | keys.forEach((key) => {
364 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
365 | });
366 |
367 | const argv = offset;
368 | argvPtrs.forEach((ptr) => {
369 | mem.setUint32(offset, ptr, true);
370 | mem.setUint32(offset + 4, 0, true);
371 | offset += 8;
372 | });
373 |
374 | while (true) {
375 | const callbackPromise = new Promise((resolve) => {
376 | this._resolveCallbackPromise = () => {
377 | if (this.exited) {
378 | throw new Error("bad callback: Go program has already exited");
379 | }
380 | setTimeout(resolve, 0); // make sure it is asynchronous
381 | };
382 | });
383 | this._inst.exports.run(argc, argv);
384 | if (this.exited) {
385 | break;
386 | }
387 | await callbackPromise;
388 | }
389 | }
390 |
391 | static _makeCallbackHelper(id, pendingCallbacks, go) {
392 | return function() {
393 | pendingCallbacks.push({ id: id, args: arguments });
394 | go._resolveCallbackPromise();
395 | };
396 | }
397 |
398 | static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
399 | return function(event) {
400 | if (preventDefault) {
401 | event.preventDefault();
402 | }
403 | if (stopPropagation) {
404 | event.stopPropagation();
405 | }
406 | if (stopImmediatePropagation) {
407 | event.stopImmediatePropagation();
408 | }
409 | fn(event);
410 | };
411 | }
412 | }
413 | })();
414 |
--------------------------------------------------------------------------------
/examples/SelfQuoting/static/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map multiple JavaScript environments to a single common API,
7 | // preferring web standards over Node.js API.
8 | //
9 | // Environments considered:
10 | // - Browsers
11 | // - Node.js
12 | // - Electron
13 | // - Parcel
14 |
15 | if (typeof global !== "undefined") {
16 | // global already exists
17 | } else if (typeof window !== "undefined") {
18 | window.global = window;
19 | } else if (typeof self !== "undefined") {
20 | self.global = self;
21 | } else {
22 | throw new Error("cannot export Go (neither global, window nor self is defined)");
23 | }
24 |
25 | if (!global.require && typeof require !== "undefined") {
26 | global.require = require;
27 | }
28 |
29 | if (!global.fs && global.require) {
30 | const fs = require("fs");
31 | if (Object.keys(fs) !== 0) {
32 | global.fs = fs;
33 | }
34 | }
35 |
36 | const enosys = () => {
37 | const err = new Error("not implemented");
38 | err.code = "ENOSYS";
39 | return err;
40 | };
41 |
42 | if (!global.fs) {
43 | let outputBuf = "";
44 | global.fs = {
45 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
46 | writeSync(fd, buf) {
47 | outputBuf += decoder.decode(buf);
48 | const nl = outputBuf.lastIndexOf("\n");
49 | if (nl != -1) {
50 | console.log(outputBuf.substr(0, nl));
51 | outputBuf = outputBuf.substr(nl + 1);
52 | }
53 | return buf.length;
54 | },
55 | write(fd, buf, offset, length, position, callback) {
56 | if (offset !== 0 || length !== buf.length || position !== null) {
57 | callback(enosys());
58 | return;
59 | }
60 | const n = this.writeSync(fd, buf);
61 | callback(null, n);
62 | },
63 | chmod(path, mode, callback) { callback(enosys()); },
64 | chown(path, uid, gid, callback) { callback(enosys()); },
65 | close(fd, callback) { callback(enosys()); },
66 | fchmod(fd, mode, callback) { callback(enosys()); },
67 | fchown(fd, uid, gid, callback) { callback(enosys()); },
68 | fstat(fd, callback) { callback(enosys()); },
69 | fsync(fd, callback) { callback(null); },
70 | ftruncate(fd, length, callback) { callback(enosys()); },
71 | lchown(path, uid, gid, callback) { callback(enosys()); },
72 | link(path, link, callback) { callback(enosys()); },
73 | lstat(path, callback) { callback(enosys()); },
74 | mkdir(path, perm, callback) { callback(enosys()); },
75 | open(path, flags, mode, callback) { callback(enosys()); },
76 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
77 | readdir(path, callback) { callback(enosys()); },
78 | readlink(path, callback) { callback(enosys()); },
79 | rename(from, to, callback) { callback(enosys()); },
80 | rmdir(path, callback) { callback(enosys()); },
81 | stat(path, callback) { callback(enosys()); },
82 | symlink(path, link, callback) { callback(enosys()); },
83 | truncate(path, length, callback) { callback(enosys()); },
84 | unlink(path, callback) { callback(enosys()); },
85 | utimes(path, atime, mtime, callback) { callback(enosys()); },
86 | };
87 | }
88 |
89 | if (!global.process) {
90 | global.process = {
91 | getuid() { return -1; },
92 | getgid() { return -1; },
93 | geteuid() { return -1; },
94 | getegid() { return -1; },
95 | getgroups() { throw enosys(); },
96 | pid: -1,
97 | ppid: -1,
98 | umask() { throw enosys(); },
99 | cwd() { throw enosys(); },
100 | chdir() { throw enosys(); },
101 | }
102 | }
103 |
104 | if (!global.crypto) {
105 | const nodeCrypto = require("crypto");
106 | global.crypto = {
107 | getRandomValues(b) {
108 | nodeCrypto.randomFillSync(b);
109 | },
110 | };
111 | }
112 |
113 | if (!global.performance) {
114 | global.performance = {
115 | now() {
116 | const [sec, nsec] = process.hrtime();
117 | return sec * 1000 + nsec / 1000000;
118 | },
119 | };
120 | }
121 |
122 | if (!global.TextEncoder) {
123 | global.TextEncoder = require("util").TextEncoder;
124 | }
125 |
126 | if (!global.TextDecoder) {
127 | global.TextDecoder = require("util").TextDecoder;
128 | }
129 |
130 | // End of polyfills for common API.
131 |
132 | const encoder = new TextEncoder("utf-8");
133 | const decoder = new TextDecoder("utf-8");
134 |
135 | global.Go = class {
136 | constructor() {
137 | this.argv = ["js"];
138 | this.env = {};
139 | this.exit = (code) => {
140 | if (code !== 0) {
141 | console.warn("exit code:", code);
142 | }
143 | };
144 | this._exitPromise = new Promise((resolve) => {
145 | this._resolveExitPromise = resolve;
146 | });
147 | this._pendingEvent = null;
148 | this._scheduledTimeouts = new Map();
149 | this._nextCallbackTimeoutID = 1;
150 |
151 | const setInt64 = (addr, v) => {
152 | this.mem.setUint32(addr + 0, v, true);
153 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
154 | }
155 |
156 | const getInt64 = (addr) => {
157 | const low = this.mem.getUint32(addr + 0, true);
158 | const high = this.mem.getInt32(addr + 4, true);
159 | return low + high * 4294967296;
160 | }
161 |
162 | const loadValue = (addr) => {
163 | const f = this.mem.getFloat64(addr, true);
164 | if (f === 0) {
165 | return undefined;
166 | }
167 | if (!isNaN(f)) {
168 | return f;
169 | }
170 |
171 | const id = this.mem.getUint32(addr, true);
172 | return this._values[id];
173 | }
174 |
175 | const storeValue = (addr, v) => {
176 | const nanHead = 0x7FF80000;
177 |
178 | if (typeof v === "number" && v !== 0) {
179 | if (isNaN(v)) {
180 | this.mem.setUint32(addr + 4, nanHead, true);
181 | this.mem.setUint32(addr, 0, true);
182 | return;
183 | }
184 | this.mem.setFloat64(addr, v, true);
185 | return;
186 | }
187 |
188 | if (v === undefined) {
189 | this.mem.setFloat64(addr, 0, true);
190 | return;
191 | }
192 |
193 | let id = this._ids.get(v);
194 | if (id === undefined) {
195 | id = this._idPool.pop();
196 | if (id === undefined) {
197 | id = this._values.length;
198 | }
199 | this._values[id] = v;
200 | this._goRefCounts[id] = 0;
201 | this._ids.set(v, id);
202 | }
203 | this._goRefCounts[id]++;
204 | let typeFlag = 0;
205 | switch (typeof v) {
206 | case "object":
207 | if (v !== null) {
208 | typeFlag = 1;
209 | }
210 | break;
211 | case "string":
212 | typeFlag = 2;
213 | break;
214 | case "symbol":
215 | typeFlag = 3;
216 | break;
217 | case "function":
218 | typeFlag = 4;
219 | break;
220 | }
221 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
222 | this.mem.setUint32(addr, id, true);
223 | }
224 |
225 | const loadSlice = (addr) => {
226 | const array = getInt64(addr + 0);
227 | const len = getInt64(addr + 8);
228 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
229 | }
230 |
231 | const loadSliceOfValues = (addr) => {
232 | const array = getInt64(addr + 0);
233 | const len = getInt64(addr + 8);
234 | const a = new Array(len);
235 | for (let i = 0; i < len; i++) {
236 | a[i] = loadValue(array + i * 8);
237 | }
238 | return a;
239 | }
240 |
241 | const loadString = (addr) => {
242 | const saddr = getInt64(addr + 0);
243 | const len = getInt64(addr + 8);
244 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
245 | }
246 |
247 | const timeOrigin = Date.now() - performance.now();
248 | this.importObject = {
249 | go: {
250 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
251 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
252 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
253 | // This changes the SP, thus we have to update the SP used by the imported function.
254 |
255 | // func wasmExit(code int32)
256 | "runtime.wasmExit": (sp) => {
257 | const code = this.mem.getInt32(sp + 8, true);
258 | this.exited = true;
259 | delete this._inst;
260 | delete this._values;
261 | delete this._goRefCounts;
262 | delete this._ids;
263 | delete this._idPool;
264 | this.exit(code);
265 | },
266 |
267 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
268 | "runtime.wasmWrite": (sp) => {
269 | const fd = getInt64(sp + 8);
270 | const p = getInt64(sp + 16);
271 | const n = this.mem.getInt32(sp + 24, true);
272 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
273 | },
274 |
275 | // func resetMemoryDataView()
276 | "runtime.resetMemoryDataView": (sp) => {
277 | this.mem = new DataView(this._inst.exports.mem.buffer);
278 | },
279 |
280 | // func nanotime1() int64
281 | "runtime.nanotime1": (sp) => {
282 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
283 | },
284 |
285 | // func walltime1() (sec int64, nsec int32)
286 | "runtime.walltime1": (sp) => {
287 | const msec = (new Date).getTime();
288 | setInt64(sp + 8, msec / 1000);
289 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
290 | },
291 |
292 | // func scheduleTimeoutEvent(delay int64) int32
293 | "runtime.scheduleTimeoutEvent": (sp) => {
294 | const id = this._nextCallbackTimeoutID;
295 | this._nextCallbackTimeoutID++;
296 | this._scheduledTimeouts.set(id, setTimeout(
297 | () => {
298 | this._resume();
299 | while (this._scheduledTimeouts.has(id)) {
300 | // for some reason Go failed to register the timeout event, log and try again
301 | // (temporary workaround for https://github.com/golang/go/issues/28975)
302 | console.warn("scheduleTimeoutEvent: missed timeout event");
303 | this._resume();
304 | }
305 | },
306 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
307 | ));
308 | this.mem.setInt32(sp + 16, id, true);
309 | },
310 |
311 | // func clearTimeoutEvent(id int32)
312 | "runtime.clearTimeoutEvent": (sp) => {
313 | const id = this.mem.getInt32(sp + 8, true);
314 | clearTimeout(this._scheduledTimeouts.get(id));
315 | this._scheduledTimeouts.delete(id);
316 | },
317 |
318 | // func getRandomData(r []byte)
319 | "runtime.getRandomData": (sp) => {
320 | crypto.getRandomValues(loadSlice(sp + 8));
321 | },
322 |
323 | // func finalizeRef(v ref)
324 | "syscall/js.finalizeRef": (sp) => {
325 | const id = this.mem.getUint32(sp + 8, true);
326 | this._goRefCounts[id]--;
327 | if (this._goRefCounts[id] === 0) {
328 | const v = this._values[id];
329 | this._values[id] = null;
330 | this._ids.delete(v);
331 | this._idPool.push(id);
332 | }
333 | },
334 |
335 | // func stringVal(value string) ref
336 | "syscall/js.stringVal": (sp) => {
337 | storeValue(sp + 24, loadString(sp + 8));
338 | },
339 |
340 | // func valueGet(v ref, p string) ref
341 | "syscall/js.valueGet": (sp) => {
342 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
343 | sp = this._inst.exports.getsp(); // see comment above
344 | storeValue(sp + 32, result);
345 | },
346 |
347 | // func valueSet(v ref, p string, x ref)
348 | "syscall/js.valueSet": (sp) => {
349 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
350 | },
351 |
352 | // func valueDelete(v ref, p string)
353 | "syscall/js.valueDelete": (sp) => {
354 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
355 | },
356 |
357 | // func valueIndex(v ref, i int) ref
358 | "syscall/js.valueIndex": (sp) => {
359 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
360 | },
361 |
362 | // valueSetIndex(v ref, i int, x ref)
363 | "syscall/js.valueSetIndex": (sp) => {
364 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
365 | },
366 |
367 | // func valueCall(v ref, m string, args []ref) (ref, bool)
368 | "syscall/js.valueCall": (sp) => {
369 | try {
370 | const v = loadValue(sp + 8);
371 | const m = Reflect.get(v, loadString(sp + 16));
372 | const args = loadSliceOfValues(sp + 32);
373 | const result = Reflect.apply(m, v, args);
374 | sp = this._inst.exports.getsp(); // see comment above
375 | storeValue(sp + 56, result);
376 | this.mem.setUint8(sp + 64, 1);
377 | } catch (err) {
378 | storeValue(sp + 56, err);
379 | this.mem.setUint8(sp + 64, 0);
380 | }
381 | },
382 |
383 | // func valueInvoke(v ref, args []ref) (ref, bool)
384 | "syscall/js.valueInvoke": (sp) => {
385 | try {
386 | const v = loadValue(sp + 8);
387 | const args = loadSliceOfValues(sp + 16);
388 | const result = Reflect.apply(v, undefined, args);
389 | sp = this._inst.exports.getsp(); // see comment above
390 | storeValue(sp + 40, result);
391 | this.mem.setUint8(sp + 48, 1);
392 | } catch (err) {
393 | storeValue(sp + 40, err);
394 | this.mem.setUint8(sp + 48, 0);
395 | }
396 | },
397 |
398 | // func valueNew(v ref, args []ref) (ref, bool)
399 | "syscall/js.valueNew": (sp) => {
400 | try {
401 | const v = loadValue(sp + 8);
402 | const args = loadSliceOfValues(sp + 16);
403 | const result = Reflect.construct(v, args);
404 | sp = this._inst.exports.getsp(); // see comment above
405 | storeValue(sp + 40, result);
406 | this.mem.setUint8(sp + 48, 1);
407 | } catch (err) {
408 | storeValue(sp + 40, err);
409 | this.mem.setUint8(sp + 48, 0);
410 | }
411 | },
412 |
413 | // func valueLength(v ref) int
414 | "syscall/js.valueLength": (sp) => {
415 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
416 | },
417 |
418 | // valuePrepareString(v ref) (ref, int)
419 | "syscall/js.valuePrepareString": (sp) => {
420 | const str = encoder.encode(String(loadValue(sp + 8)));
421 | storeValue(sp + 16, str);
422 | setInt64(sp + 24, str.length);
423 | },
424 |
425 | // valueLoadString(v ref, b []byte)
426 | "syscall/js.valueLoadString": (sp) => {
427 | const str = loadValue(sp + 8);
428 | loadSlice(sp + 16).set(str);
429 | },
430 |
431 | // func valueInstanceOf(v ref, t ref) bool
432 | "syscall/js.valueInstanceOf": (sp) => {
433 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
434 | },
435 |
436 | // func copyBytesToGo(dst []byte, src ref) (int, bool)
437 | "syscall/js.copyBytesToGo": (sp) => {
438 | const dst = loadSlice(sp + 8);
439 | const src = loadValue(sp + 32);
440 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
441 | this.mem.setUint8(sp + 48, 0);
442 | return;
443 | }
444 | const toCopy = src.subarray(0, dst.length);
445 | dst.set(toCopy);
446 | setInt64(sp + 40, toCopy.length);
447 | this.mem.setUint8(sp + 48, 1);
448 | },
449 |
450 | // func copyBytesToJS(dst ref, src []byte) (int, bool)
451 | "syscall/js.copyBytesToJS": (sp) => {
452 | const dst = loadValue(sp + 8);
453 | const src = loadSlice(sp + 16);
454 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
455 | this.mem.setUint8(sp + 48, 0);
456 | return;
457 | }
458 | const toCopy = src.subarray(0, dst.length);
459 | dst.set(toCopy);
460 | setInt64(sp + 40, toCopy.length);
461 | this.mem.setUint8(sp + 48, 1);
462 | },
463 |
464 | "debug": (value) => {
465 | console.log(value);
466 | },
467 | }
468 | };
469 | }
470 |
471 | async run(instance) {
472 | this._inst = instance;
473 | this.mem = new DataView(this._inst.exports.mem.buffer);
474 | this._values = [ // JS values that Go currently has references to, indexed by reference id
475 | NaN,
476 | 0,
477 | null,
478 | true,
479 | false,
480 | global,
481 | this,
482 | ];
483 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
484 | this._ids = new Map([ // mapping from JS values to reference ids
485 | [0, 1],
486 | [null, 2],
487 | [true, 3],
488 | [false, 4],
489 | [global, 5],
490 | [this, 6],
491 | ]);
492 | this._idPool = []; // unused ids that have been garbage collected
493 | this.exited = false; // whether the Go program has exited
494 |
495 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
496 | let offset = 4096;
497 |
498 | const strPtr = (str) => {
499 | const ptr = offset;
500 | const bytes = encoder.encode(str + "\0");
501 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
502 | offset += bytes.length;
503 | if (offset % 8 !== 0) {
504 | offset += 8 - (offset % 8);
505 | }
506 | return ptr;
507 | };
508 |
509 | const argc = this.argv.length;
510 |
511 | const argvPtrs = [];
512 | this.argv.forEach((arg) => {
513 | argvPtrs.push(strPtr(arg));
514 | });
515 | argvPtrs.push(0);
516 |
517 | const keys = Object.keys(this.env).sort();
518 | keys.forEach((key) => {
519 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
520 | });
521 | argvPtrs.push(0);
522 |
523 | const argv = offset;
524 | argvPtrs.forEach((ptr) => {
525 | this.mem.setUint32(offset, ptr, true);
526 | this.mem.setUint32(offset + 4, 0, true);
527 | offset += 8;
528 | });
529 |
530 | this._inst.exports.run(argc, argv);
531 | if (this.exited) {
532 | this._resolveExitPromise();
533 | }
534 | await this._exitPromise;
535 | }
536 |
537 | _resume() {
538 | if (this.exited) {
539 | throw new Error("Go program has already exited");
540 | }
541 | this._inst.exports.resume();
542 | if (this.exited) {
543 | this._resolveExitPromise();
544 | }
545 | }
546 |
547 | _makeFuncWrapper(id) {
548 | const go = this;
549 | return function () {
550 | const event = { id: id, this: this, args: arguments };
551 | go._pendingEvent = event;
552 | go._resume();
553 | return event.result;
554 | };
555 | }
556 | }
557 |
558 | if (
559 | global.require &&
560 | global.require.main === module &&
561 | global.process &&
562 | global.process.versions &&
563 | !global.process.versions.electron
564 | ) {
565 | if (process.argv.length < 3) {
566 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
567 | process.exit(1);
568 | }
569 |
570 | const go = new Go();
571 | go.argv = process.argv.slice(2);
572 | go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
573 | go.exit = process.exit;
574 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
575 | process.on("exit", (code) => { // Node.js exits if no event handler is pending
576 | if (code === 0 && !go.exited) {
577 | // deadlock, make Go print error and stack traces
578 | go._pendingEvent = { id: 0 };
579 | go._resume();
580 | }
581 | });
582 | return go.run(result.instance);
583 | }).catch((err) => {
584 | console.error(err);
585 | process.exit(1);
586 | });
587 | }
588 | })();
589 |
--------------------------------------------------------------------------------