├── .gitignore ├── go.mod ├── example └── main.go ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dImrich/tinygo-wasmserve 2 | 3 | go 1.14 -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | // +build example 2 | 3 | package main 4 | 5 | import ( 6 | "syscall/js" 7 | ) 8 | 9 | func main() { 10 | p := js.Global().Get("document").Call("createElement", "p") 11 | p.Set("innerText", "Hello, World!") 12 | js.Global().Get("document").Get("body").Call("appendChild", p) 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyGo WasmServe 2 | 3 | An HTTP server for TinyGo Wasm testing like `gopherjs serve` 4 | 5 | Fork of [hajimehoshi/wasmserve][https://github.com/hajimehoshi/wasmserve] 6 | 7 | ## Installation 8 | 9 | ```sh 10 | go get -u github.com/dImrich/tinygo-wasmserve 11 | ``` 12 | 13 | ## Usage 14 | 15 | ``` 16 | Usage of tinygo-wasmserve 17 | -allow-origin string 18 | Allow specified origin (or * for all origins) to make requests to this server 19 | -http string 20 | HTTP bind address to serve (default ":8080") 21 | -tags string 22 | Build tags 23 | -no-debug bool 24 | Disable outputting debug symbols. Avoiding debug symbols can have a big impact on generated binary size, reducing them by more than half. 25 | ``` 26 | 27 | ## Example 28 | 29 | Running a remote package 30 | 31 | ```sh 32 | # Be careful that `-tags=example` is required to run the below example application. 33 | tinygo-wasmserve -tags=example github.com/dImrich/tinygo-wasmserve/example 34 | ``` 35 | 36 | And open `http://localhost:8080/` on your browser. 37 | 38 | ## Example 2 39 | 40 | Running a local package 41 | 42 | ```sh 43 | git clone https://github.com/hajimehoshi/ebiten # This might take several minutes. 44 | cd ebiten 45 | tinygo-wasmserve -tags=example ./examples/sprites 46 | ``` 47 | 48 | And open `http://localhost:8080/` on your browser. 49 | 50 | ## Known issue with Windows Subsystem for Linux (WSL) 51 | 52 | This application sometimes does not work under WSL, due to bugs in WSL, see https://github.com/hajimehoshi/wasmserve/issues/5 for details. 53 | 54 | [https://github.com/hajimehoshi/wasmserve]: https://github.com/hajimehoshi/wasmserve -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const indexHTML = ` 17 | 32 | ` 33 | 34 | var ( 35 | flagHTTP = flag.String("http", ":8080", "HTTP bind address to serve") 36 | flagTags = flag.String("tags", "", "Build tags") 37 | flagAllowOrigin = flag.String("allow-origin", "", "Allow specified origin (or * for all origins) to make requests to this server") 38 | flagNoDebug = flag.Bool("no-debug", false, "Disable outputting debug symbols. Avoiding debug symbols can have a big impact on generated binary size, reducing them by more than half.") 39 | ) 40 | 41 | var tmpOutputDir = "" 42 | 43 | func ensureTmpOutputDir() (string, error) { 44 | if tmpOutputDir != "" { 45 | return tmpOutputDir, nil 46 | } 47 | 48 | tmp, err := ioutil.TempDir("", "") 49 | if err != nil { 50 | return "", err 51 | } 52 | tmpOutputDir = tmp 53 | return tmpOutputDir, nil 54 | } 55 | 56 | 57 | func handle(w http.ResponseWriter, r *http.Request) { 58 | if *flagAllowOrigin != "" { 59 | w.Header().Set("Access-Control-Allow-Origin", *flagAllowOrigin) 60 | } 61 | 62 | output, err := ensureTmpOutputDir() 63 | if err != nil { 64 | http.Error(w, err.Error(), http.StatusInternalServerError) 65 | return 66 | } 67 | 68 | upath := r.URL.Path[1:] 69 | fpath := filepath.Join(".", filepath.Base(upath)) 70 | workdir := "." 71 | 72 | if !strings.HasSuffix(r.URL.Path, "/") { 73 | fi, err := os.Stat(fpath) 74 | if err != nil && !os.IsNotExist(err) { 75 | http.Error(w, err.Error(), http.StatusInternalServerError) 76 | return 77 | } 78 | if fi != nil && fi.IsDir() { 79 | http.Redirect(w, r, r.URL.Path+"/", http.StatusSeeOther) 80 | return 81 | } 82 | } 83 | 84 | switch filepath.Base(fpath) { 85 | case "index.html", ".": 86 | if _, err := os.Stat(fpath); err != nil && !os.IsNotExist(err) { 87 | http.Error(w, err.Error(), http.StatusInternalServerError) 88 | return 89 | } 90 | http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader([]byte(indexHTML))) 91 | return 92 | case "wasm_exec.js": 93 | if _, err := os.Stat(fpath); err != nil && !os.IsNotExist(err) { 94 | http.Error(w, err.Error(), http.StatusInternalServerError) 95 | return 96 | } 97 | tinygoRootEnv := exec.Command("tinygo", "env", "TINYGOROOT") 98 | rootEnv, err := tinygoRootEnv.CombinedOutput() 99 | if err != nil { 100 | http.Error(w, err.Error(), http.StatusInternalServerError) 101 | return 102 | } 103 | f := filepath.Join(strings.TrimSuffix(string(rootEnv), "\n"), "targets", "wasm_exec.js") 104 | http.ServeFile(w, r, f) 105 | return 106 | case "main.wasm": 107 | if _, err := os.Stat(fpath); err != nil && !os.IsNotExist(err) { 108 | http.Error(w, err.Error(), http.StatusInternalServerError) 109 | return 110 | } 111 | // tinygo build -o main.wasm -target wasm . 112 | args := []string{"build", "-o", filepath.Join(output, "main.wasm"), "-target", "wasm"} 113 | if *flagNoDebug { 114 | args = append(args, "-no-debug") 115 | } 116 | if *flagTags != "" { 117 | args = append(args, "-tags", *flagTags) 118 | } 119 | if len(flag.Args()) > 0 { 120 | args = append(args, flag.Args()[0]) 121 | } else { 122 | args = append(args, ".") 123 | } 124 | log.Print("tinygo ", strings.Join(args, " ")) 125 | cmdBuild := exec.Command("tinygo", args...) 126 | cmdBuild.Dir = workdir 127 | out, err := cmdBuild.CombinedOutput() 128 | if err != nil { 129 | log.Print(err) 130 | log.Print(string(out)) 131 | http.Error(w, string(out), http.StatusInternalServerError) 132 | return 133 | } 134 | if len(out) > 0 { 135 | log.Print(string(out)) 136 | } 137 | 138 | f, err := os.Open(filepath.Join(output, "main.wasm")) 139 | if err != nil { 140 | http.Error(w, err.Error(), http.StatusInternalServerError) 141 | return 142 | } 143 | defer f.Close() 144 | http.ServeContent(w, r, "main.wasm", time.Now(), f) 145 | return 146 | } 147 | 148 | http.ServeFile(w, r, filepath.Join(".", r.URL.Path)) 149 | } 150 | 151 | func main() { 152 | flag.Parse() 153 | http.HandleFunc("/", handle) 154 | log.Fatal(http.ListenAndServe(*flagHTTP, nil)) 155 | } --------------------------------------------------------------------------------