├── .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 | 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 | 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 | 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 | 20 | 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 | 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 | [![Godoc Reference](https://camo.githubusercontent.com/6321d9723db4c8f80466aaa83c19d4afb9fdd208/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f6f616b6d6f756e642f6f616b3f7374617475732e737667)](https://godoc.org/github.com/elliotforbes/go-webassembly-framework) [![Travis Build Status](https://api.travis-ci.org/elliotforbes/oak.svg?branch=master)](https://travis-ci.org/elliotforbes/go-webassembly-framework) [![Go Report Card](https://goreportcard.com/badge/github.com/elliotforbes/oak)](https://goreportcard.com/report/github.com/elliotforbes/go-webassembly-framework) 5 | 6 | 7 | Oak Framework 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 | 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 | 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 | 157 | 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 |
15 |
Windows
16 |
Linux
17 | 18 |
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 | --------------------------------------------------------------------------------