feat: Demonstrates TinyGo compatibility

Adds an example that demonstrates TinyGo compatibility, as well as using a server-side HTTP handler as a fallback.
This commit is contained in:
JP Hastings-Spital
2025-01-04 15:52:06 +00:00
committed by Nicolas Lepage
parent d12a255cff
commit ce6765e72a
10 changed files with 204 additions and 5 deletions

42
docs/tinygo/README.md Normal file
View File

@@ -0,0 +1,42 @@
# Compiling with TinyGo
This example demonstrates that go-wasm-http-server can also be compiled with [TinyGo](https://www.tinygo.org), producing significantly smaller WASM blobs, though at the expense of [at least one known bug](https://github.com/tinygo-org/tinygo/issues/1140) and a [reduced standard library](https://tinygo.org/docs/reference/lang-support/stdlib/).
This example also demonstrates how the same code can be used for both server-side execution, and client-side execution in WASM (providing support for clients that cannot interpret WASM).
## Prerequisites
You'll need a version of [TinyGo installed](https://tinygo.org/getting-started/install/). (eg. `brew install tinygo-org/tools/tinygo`)
You'll need to make sure the first line of `sw.js` here has the same tinygo version number as your TinyGo version (this was v0.35.0 at time of writing).
## Build & run
Compile the WASM blob with TinyGo (this has been done for you for this example):
```bash
GOOS=js GOARCH=wasm tinygo build -o api.wasm .
```
Run the server (with Go, not TinyGo):
```bash
$ go run .
Server starting on http://127.0.0.1:<port>
```
## Important notes
You **must** use the TinyGo `wasm_exec.js`, specific to the version of TinyGo used to compile the WASM, in your `sw.js`. For example, if using the JSDelivr CDN:
```js
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
```
Note that the `0.35.0` within the path matches the TinyGo version used:
```sh
$ tinygo version
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
# ^----^
```

BIN
docs/tinygo/api.wasm Normal file

Binary file not shown.

19
docs/tinygo/handlers.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import (
"encoding/json"
"net/http"
"runtime"
)
func goRuntimeHandler(res http.ResponseWriter, req *http.Request) {
res.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(res).Encode(map[string]string{
"os": runtime.GOOS,
"arch": runtime.GOARCH,
"compiler": runtime.Compiler,
"version": runtime.Version(),
}); err != nil {
panic(err)
}
}

44
docs/tinygo/index.html Normal file
View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>go-wasm-http-server tinygo demo</title>
<script>
const sw = navigator.serviceWorker
function attachServiceWorker() {
sw.register('sw.js')
.then(() => {
document.getElementById('wasm-status').innerText = "⚡️ Loaded - Will execute WASM locally"
})
.catch((err) => {
document.getElementById('wasm-status').innerText = "🛑 Error loading service worker — Check console"
console.error(err)
})
}
async function makeQuery() {
const res = await fetch('api/tiny', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
document.getElementById('output').innerText = await res.text()
}
</script>
</head>
<body>
<p>This example demonstrates that go-wasm-http-server can be compiled with <a href="https://www.tinygo.org">TinyGo</em>, producing significantly smaller WASM blobs, at the expense of <a href="https://github.com/tinygo-org/tinygo/issues/1140">at least one known bug</a>, and a <a href="https://tinygo.org/docs/reference/lang-support/stdlib/">reduced standard library</a>.</p>
<dl><dt>WASM HTTP Service Worker:</dt><dd id="wasm-status">☁️ Not loaded — will call server</dd></dl>
<ol>
<li><button onclick="makeQuery()">Call API</button></li>
<li><button onclick="attachServiceWorker()">Attach the service worker</button></li>
<li><span>Call the API again (Step 1)</span></li>
</ol>
<h3>Response:</h3>
<pre id="output"></pre>
</body>
</html>

34
docs/tinygo/server.go Normal file
View File

@@ -0,0 +1,34 @@
//go:build !wasm
// +build !wasm
package main
import (
"embed"
"fmt"
"log"
"net"
"net/http"
)
//go:embed *.html *.js *.wasm
var thisDir embed.FS
func main() {
// Serve all files in this directory statically
http.Handle("/", http.FileServer(http.FS(thisDir)))
// Note that this needs to be mounted at /api/tiny, rather than just /tiny (like in wasm.go)
// because the service worker mounts the WASM server at /api (at the end of sw.js)
http.HandleFunc("/api/tiny", goRuntimeHandler)
// Pick any available port. Note that ServiceWorkers _require_ localhost for non-SSL serving (so other LAN/WAN IPs will prevent the service worker from loading)
listener, err := net.Listen("tcp", ":0")
if err != nil {
log.Fatalf("Unable to claim a port to start server on: %v", err)
}
// Share the port being used & start
fmt.Printf("Server starting on http://127.0.0.1:%d\n", listener.Addr().(*net.TCPAddr).Port)
panic(http.Serve(listener, nil))
}

14
docs/tinygo/sw.js Normal file
View File

@@ -0,0 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
const wasm = 'api.wasm'
addEventListener('install', (event) => {
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
})
addEventListener('activate', (event) => {
event.waitUntil(clients.claim())
})
registerWasmHTTPListener(wasm, { base: 'api' })

18
docs/tinygo/wasm.go Normal file
View File

@@ -0,0 +1,18 @@
//go:build wasm
// +build wasm
package main
import (
"net/http"
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
)
func main() {
http.HandleFunc("/tiny", goRuntimeHandler)
wasmhttp.Serve(nil)
select {}
}