Golang-WASM provides a simple idiomatic, and comprehensive API and bindings for working with WebAssembly for Go and JavaScript developers

Overview

A bridge and bindings for JS DOM API with Go WebAssembly.

Written by Team Ortix - Hamza Ali and Chan Wen Xu.

Go Reference Go Report Card

GOOS=js GOARCH=wasm go get -u github.com/teamortix/golang-wasm/wasm
npm install golang-wasm

⚠️ The documentation is still work in progress.

Why Golang-WASM?

Golang-WASM provides a simple idiomatic, and comprehensive (soon ™️ ) API and bindings for working with WebAssembly.

Golang-WASM also comes with a webpack loader that wraps the entire API so that it is idiomatic for JavaScript developers as well as Go developers.

Here is a small snippet:

// main.go
// Automatically handled with Promise rejects when returning an error!
func divide(x int, y int) (int, error) {
    if y == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return x / y, nil
}

func main() {
    wasm.Expose("divide", divide)
    wasm.Ready()
}
// index.js
import { divide } from "main.go";

const result = await divide(6, 2);
console.log(result); // 3

// Exception thrown: Unhandled rejection in promise: cannot divide by zero.
const error = await divide(6, 0);

When using the webpack loader, everything is bundled for you, and you can directly import the Go file.

Note: the webpack loader expects you to have a valid Go installation on your system, and a valid GOROOT passed.

JS Interop

Examples

You can find our examples in the examples directory. Currently, we have two examples available.

  • basic: Simple usage of the API with the WASM library. This example shows automatically casting types from JS to Go, and how returning errors works.

  • basic (no api): A standalone example of using the JS bridge without any third party libraries in Go. Forces developers to manually type check all parameters and use the unsafe syscall/js directly.

  • (WIP) promises: An example showing how Go can work with functions that call promises, and return its own promises. This example puts an emphasis on the importance of idiomatic code in both languages.

  • (WIP) craco + ReactJS: A simple example demonstrating how Golang-WASM and React can work together, using [craco]https://github.com/gsoft-inc/craco).

Retrieving constants and values from JavaScript

For more information about how Golang-WASM converts Go data to JS, read Auto type casting.

type User struct {
    Name     string `wasm:"name"`
    Email    string `wasm:"emailAddr"`
    Age      int    `wasm:"age"`
    password string // ignored (unexported)
}

const Nothing  = nil         // Exposed as 'nothing'.
const Username = "TeamOrtix" // exposed as username.
const Age      = 3

var hhhapz = User { // exposed as hhhapz.
    Name:     "Hamza Ali",
    Email:    "me (at) hamzantal dot pw",
    Age:      17,
    password: "wouldn't_you_like_to_know"
}
var goodPromise = wasm.NewPromise(func() (interface{}, error) {
    return "success", nil
})
var badPromise = wasm.NewPromise(func() (interface{}, error) {
    return nil, errors.New("failure")
})
import wasm from ...;

await wasm.nothing();  // null
await wasm.username(); // "TeamOrtix"
await wasm.age();      // 3
await wasm.hhhapz();   /* {
    name: "Hamza Ali",
    emailAddr: "me (at) hamzantal dot pw",
    age: 17
} */
await wasm.goodPromise(); // Promise(fulfilled: "success")
await wasm.badPromise();  // Promise(rejected:  Error("failure"))

Note: For the last two, The resulting value of calling goodPromise or badPromise will be a Promise wrapped inside a Promise.

If Go returns multiple promises embedded within each other, the stack will automatically flatten like it does in JS.


Working with functions

When a Go function returns a value, they are handled identically to constants and variables.

⚠️ For functions that can also fail/return errors, please do not use panics!

The working with errors section covers how errors can be sent to JavaScript.

Brief Overview:

func ZeroParamFunc() {
} // await call() => undefined


func StringReturnFunc() string {
    return "Team Ortix"
} // await call() => "Team Ortix"


func StringOrErrorReturnFunc() (string, error) {
    return "Team Ortix", nil
} // await call() => "Team Ortix"


func FailOrNil() (error) {
    return nil
} // await call() => undefined


func FailingFunc() error {
    return errors.New("fail")
} // await call() => Error("fail")


func FailingFunc() (string, error) {
    return "", errors.New("fail")
} // await call() => fail


func SingleParam(str string) string  {
    return str
}
// await call() => Rejected Promise: Error("invalid argument passed into Go function")
// await call("Team Ortix", "fail") => Rejected Promise: Error("invalid argument passed into Go function")
// await call("Team Ortix") => "Team Ortix"


func TakeAnything(v interface{}) interface{}  {
    return v
}
// call(3) => 3
// call(() => {}) => (() => {})
// call({}) => {}


func HigherOrderFunction(f func() string) string {
    return f()
}
// await call(() => {}) // Go will panic and shut down. Higher order functions like this MUST return the same type.
// await call("invalid") => Rejected Promise: Error("invalid argument passed into Go function")
// await call(() => "test") => "test"


func HigherOrderFunctionSafe(f func() (string, error)) string {
    str, err := f()
    if err != nil {
        return "invalid return value!"
    }
    return str
}
// call(() => {}) => "invalid return value!"
// call(() => "Team Ortix") => "Team Ortix"


func FunctionThatThrows(f func()) {
    f()
}
// call(() => { throw new Error("fail")}) // Go will panic in this situation.


// Not implemented yet.
func DidFunctionSucceed(f func() error) bool {
    res := f()
    return res == nil
}
// call(() => {}) => true
// call(() => { throw new Error("fail") }) => false

Auto type casting

  • If a nil value is found, it is converted to JavaScript's null.

  • For Go primitives, marshalling to JS works as one would expect.

    Go Type JS Type
    string String
    uint Number
    int Number
    float Number
    complex {real: Number, imag: Number}
    Symbol unimplemented
    map[string]T Object
    [size]T (array) Array
    []T (slice) Array
  • For Go structs, unexported values inside structs will NOT be marshalled.

    • If a struct has a tag (with the wasm namespace), that will be used as the key.

    • If two properties have the identical key, the value that is declared second in the struct deceleration will overwrite the former value.

  • When converting a Go Map (map[K]V), all keys must be a uint, int or a string.

    • ⚠️ If a different kind of key is found, WASM will panic.
  • If a pointer is found, the pointer is unwrapped till the raw value is found.

  • Slices and arrays are automatically converted to the JavaScript Array object.

  • Marshalling function parameters to Go values has slightly different functionality.

    • If a function parameter is not a concrete type (interface{}), Go returns types in the following fashion:

      JS Type Go Type
      undefined nil
      null nil
      Boolean bool
      Number float64
      String string
      Symbol unimplemented
      Array [size]interface{} (Go array)
      Object map[string]interface{}
      Function func(...interface{}) (interface{}, error)
    • Go pointers will result in the basic value.

    • Structs will be filled.

      • All of the keys in the struct must be keys in the object.

      • If there are keys in the object but not in the struct, they will be skipped.

      • Currently, even if a struct value is a pointer to a type, the value will be nil.

        • You can use this functionality for optional values in structs.
    • Providing a map[string]interface{} will directly fill the map as expected with the keys of the object and the respective values.

    • If a function parameter is a concrete type, Golang-WASM will try to convert the JS Value to the Go type using the table above.

      • If the types do not match, The caller will receive a rejection promise and the function will never be called.

      • Apart from float64, Number will be safely casted to all uint types, int types, and float32.

      • Decoding into complex64 and complex128 is similar to when they are encoded. A JS Object with a real and imag property (type Number) are expected.

Working with errors

  • Functions can only return 0, 1, or 2 values.

    • If the function's last return value is of type error, and the error is non-nil, it will reject with the provided error message.

    • Functions that only return error will return undefined when nil as well.

    • If the second return value is not error, Go will not call the function and instead return an error with the following message:

      a JS function can only return one value

DOM API

Currently, little to none of the DOM-API has been implemented in Go.

The goal for Golang-WASM is to eventually implement a comprehensive section of the DOM-API that will be able to be used in a type safe manner from Go.

When converting the API over, Golang-WASM will make slight modifications to attempt to maintain idiomatic code when writing Go.

Promise API

Here is how Go implements the Promise API:

// Create a new promise and resolve or reject it, based on what the function returns.
func ExpensiveOperation() wasm.Promise {
    // Code is automatically called in a goroutine, similar to how JS does it.
    return wasm.NewPromise(func() (interface{}, error) {
        result, err := // Expensive operation.
        if err != nil {
            return nil, err // Reject the Promise.
        }
        return result, nil // Resolve the Promise.
    })
}

// Wait for a promise to be fulfilled or rejected.
// Note: this must be called inside a goroutine.
// If called within the main thread, Go will deadlock and shut down.
// out: the value that will be set when the promise is fulfilled.
// rej: the value that will be set when the promise is rejected.
// success: whether the promise was fulfilled (true) or rejected (false).
promise := AnotherOperation()
var out int
var rej  error
success, err := promise.Await(&out, &rej)
if err != nil {
    // Something went wrong with converting types or interacting with JS.
}


// Run multiple asynchronous operations at once and wait for them to all end.
promise = wasm.PromiseAllSettled(ExpensiveOperation(), AnotherOperation())

// If you want to make sure all are fulfilled and not rejected, you can use.
promise = wasm.PromiseAll(ExpensiveOperation(), AnotherOperation())

// Wait for any of the promises to fulfill. If none of them fulfill and all reject, this promise will also reject.
promise = wasm.PromiseAny(ExpensiveOperation(), AnotherOperation())

// Run all operations in asynchronous and return the first one to either fulfill or reject.
promise = wasm.PromiseRace(ExpensiveOperation(), AnotherOperation())

How it works

Golang-WASM uses reflection to marshal to and from JS. To understand the different modules of the project, we suggest reading ARCHITECTURE.md.

The implementation of things such as throwing errors from Go and catching thrown errors in JS from Go happen through the usage of wrapper functions. Here is one of such functions that is wrapped when calling a JS function from Go:

const callSafe = (f) => ((...args) => {
     try {
         return {result: f(...args)};
     } catch (e) {
      return {error: e};
    }
})

Go is able to marshal this into a struct:

type SafeCallResult struct {
    Result js.Value `wasm:"result"`
    Error js.Value  `wasm:"error"`
}

Webpack Configuration

See the example basic project for a full configuration.

// webpack.config.js:
module.exports = {
    module: {
        rules: [
            {
                test: /\.go$/,
                use: [ 'golang-wasm' ]
            }
        ]
    },
    ignoreWarnings: [
        {
            module: /wasm_exec.js$/
        }
    ]
};

Hotcode Reload

To ensure that hotcode reloading works, the Webpack loader expects a go.mod to be in the directory of the imported file, or a parent directory.

Go will call go build [directory] where the file is imported.

Given the following directory structure from this project's basic example:

basic
├── dist
│   └── index.html
├── go.mod
├── go.sum
├── package.json
├── package-lock.json
├── src
│   ├── api
│   │   └── main.go
│   └── index.js
└── webpack.config.js
  • Golang-WASM webpack will look for a go.mod file in api/, then src/, and then basic, till the root directory.

    • When it finds a go.mod, the loader will start looking in the directory for changes to any file for hotcode reload.

    • If no go.mod is found, the loader will fail.

    • The loader runs build in the api directory:

      go build .../basic/src/api
      

FAQ

Is it possible to use multiple instances of Web Assembly in the same project

At the moment, this is not supported.

When will the DOM API be implemented?

The DOM API is expanse and large. We can't give a particular date or time. You are free to monitor our progress in this repository.

When will the API be stable?

Once we have comprehensive tests and the confidence that the project can be used in production with reasonable stability, we will push v1.0.0 for the project.

We encourage everyone to submit any errors they may come across and help us with the development process :).

License

MIT.


Created by hhhapz and chanbakjsd.

Comments
  • Consult a technical question: how to debug golang code

    Consult a technical question: how to debug golang code

    Hi, I'm using this lib to developing a tool, but I encountered an error, then I tried copy codes to my golang project and then tried to set breakpoints into golang codes.

    But breakpoints doesn't work, how could I implement this ? or, If I want to contribute to this lib, how to debug this lib locally with VSCode (because it could not be launched when this settings are put into .vscode/settings.json)

    "go.toolsEnvVars": { "GOARCH": "wasm", "GOOS": "js", },

    Thanks a lot.

    The error was:

    panic: syscall/js: call of Value.Invoke on undefined

    goroutine 1 [running]: syscall/js.Value.Invoke(0x0, 0x0, 0x42ac80, 0x1, 0x1, 0x10, 0x10) syscall/js/js.go:417 +0xa yam.plus/example-go-plugin/wasm.toJSFunc(0x14060, 0x333f8, 0x13, 0x0, 0x0) yam.plus/example-go-plugin/wasm/function.go:30 +0xf yam.plus/example-go-plugin/wasm.ToJSValue(0x14060, 0x333f8, 0x98, 0x1) yam.plus/example-go-plugin/wasm/reflect_to.go:64 +0x2d yam.plus/example-go-plugin/wasm.mapToJSObject(0x153e0, 0x424210, 0x15, 0x0, 0x0) yam.plus/example-go-plugin/wasm/reflect_to.go:129 +0x54 yam.plus/example-go-plugin/wasm.ToJSValue(0x153e0, 0x424210, 0x0, 0x0) yam.plus/example-go-plugin/wasm/reflect_to.go:66 +0x34 yam.plus/example-go-plugin/wasm.Object.Set(0x7ff800010000000d, 0x410040, 0x2f03b, 0x1a, 0x153e0, 0x424210) yam.plus/example-go-plugin/wasm/object.go:121 +0x2 yam.plus/example-go-plugin/wasm.Expose(...) yam.plus/example-go-plugin/wasm/wasm.go:42 main.main() command-line-arguments/main.go:40 +0x7

    opened by Code2Life 3
  • Broken for newer go versions

    Broken for newer go versions

    Describe the bug: This package does not compile.

    ..\..\..\go\pkg\mod\github.com\teamortix\golang-wasm\[email protected] 285babe68092\reflect_to.go:34:10: undefined: js.Wrapper
    

    To reproduce: Use any version containing this patch: https://github.com/golang/go/commit/6c0daa733192031eab23d09ed6515c4cd959aa92

    Expected behavior: Compiles.

    Additional context: Will stay like this, so this lib has to be adjusted to work again, see https://github.com/golang/go/issues/50310

    opened by nettnikl 2
  • Reflection rules out tinygo ?

    Reflection rules out tinygo ?

    Just read the architect md and some of the code .

    tinygo does not support reflection and I suspect never will. so not sure how this impacts this project but curious on your thoughts

    opened by gedw99 2
  • [Question]: How to wait an async js function in go

    [Question]: How to wait an async js function in go

    Describe the bug: I Work with 2 wasm module, 1 written in rust, and one in golang, the rust one expose in the global javascript namespace few async js function and i would like to wait one of them from Golang, is that possible with this package ?

    Thanks a lot for your work

    also the package is not working for me: panic: JS wrapper __go_wasm__ is not an object

    opened by Milerius 1
  • fix: use value of lookpath as go executable

    fix: use value of lookpath as go executable

    lookpath() returns a promise, but the current code isn't checking for the resolution of the promise, so it will always pass.

    This change resolves the promise and uses the result as the path of the go binary to execute in the build process.

    opened by bketelsen 0
  • Wasm doesn't load

    Wasm doesn't load

    opened by nunofaria007 0
  • Missing path dashes on Windows

    Missing path dashes on Windows

    Describe the bug: Missing path dashes on Windows

    To reproduce: CRA 5.0.1 craco 7.0.0

    Screenshots: image

    image

    Browser, OS, Go version and Golang-WASM version:

    • OS: Windows 11
    • Go: 1.19.3
    • Golang-WASM: 0.1.0

    Additional context: Quick fix: Replacing \ with / const wasmSavePath = path.join(__dirname, 'wasm_exec.js').replace(/\\/g,"/");

    import goWasm from \'${path.join(__dirname, \'bridge.js\').replace(/\\/g,"/")}'

    opened by nunofaria007 0
  • example with built wasm, without webpack

    example with built wasm, without webpack

    hey there, thanks @hhhapz for bringing this library to my attention from the discord chat. as mentioned there I had a hard to to get it to run in the context of my app since I require a built wasm file and dont use webpack.

    perhaps you people could come up with a small sample (basic sample) showing how to do that?

    opened by zewa666 0
Owner
TeamOrtix
TeamOrtix
This library provides WebAssembly capability for goja Javascript engine

This module provides WebAssembly functions into goja javascript engine.

YC-L 1 Jan 10, 2022
WebAssembly Lightweight Javascript Framework in Go (AngularJS Inspired)

Tango Lightweight WASM HTML / Javascript Framework Intro WebAssembly is nice, Go on the web is nice, so I ported Tangu to Go and WebAssembly. Tangu is

enimatek 7 Nov 18, 2022
wazero: the zero dependency WebAssembly runtime for Go developers

wazero: the zero dependency WebAssembly runtime for Go developers WebAssembly is a way to safely run code compiled in other languages. Runtimes execut

Tetrate Labs 2.1k Nov 17, 2022
Istio wasm api demo with golang

istio-wasm-api-demo 1. Setup the latest Istio Setup k8s cluster: e.g. kind create cluster --name test Download the latest Istioctl from the GitHub rel

Takeshi Yoneda 6 Nov 1, 2022
A WASM Filter for Envoy Proxy written in Golang

envoy-proxy-wasm-filter-golang A WASM Filter for Envoy Proxy written in Golang Build tinygo build -o optimized.wasm -scheduler=none -target=wasi ./mai

Emre Savcı 10 Nov 6, 2022
DOM library for Go and WASM

Go DOM binding (and more) for WebAssembly This library provides a Go API for different Web APIs for WebAssembly target. It's in an active development,

Denys Smirnov 467 Nov 18, 2022
Library to use HTML5 Canvas from Go-WASM, with all drawing within go code

go-canvas go-canvas is a pure go+webassembly Library for efficiently drawing on a html5 canvas element within the browser from go without requiring ca

null 179 Oct 30, 2022
Run WASM tests inside your browser

wasmbrowsertest Run Go wasm tests easily in your browser. If you have a codebase targeting the wasm platform, chances are you would want to test your

Agniva De Sarker 131 Nov 15, 2022
An Experimental Wasm Virtual Machine for Gophers

gasm A minimal implementation of v1 WASM spec compatible virtual machine purely written in go. The vm can be embedded in your go program without any d

Takeshi Yoneda 2.2k Nov 27, 2022
Go Wasm is a in-browser IDE for Go

Go Wasm Go Wasm is a Go development environment with the essentials to write and run code entirely within the browser, using the power of WebAssembly

John Starich 445 Nov 9, 2022
Dom - A Go API for different Web APIs for WebAssembly target

Go DOM binding (and more) for WebAssembly This library provides a Go API for dif

Denys Smirnov 466 Nov 16, 2022
WebAssembly for Proxies (Golang host implementation)

WebAssembly for Proxies (GoLang host implementation) The GoLang implementation for proxy-wasm, enabling developer to run proxy-wasm extensions in Go.

MOSN 38 Nov 9, 2022
Go compiler for small places. Microcontrollers, WebAssembly, and command-line tools. Based on LLVM.

TinyGo - Go compiler for small places TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and comma

TinyGo 11.9k Nov 26, 2022
WebAssembly interop between Go and JS values.

vert Package vert provides WebAssembly interop between Go and JS values. Install GOOS=js GOARCH=wasm go get github.com/norunners/vert Examples Hello W

null 79 Nov 20, 2022
A package to build progressive web apps with Go programming language and WebAssembly.

Go-app is a package for building progressive web apps (PWA) with the Go programming language (Golang) and WebAssembly (Wasm). Shaping a UI is done by

Maxence Charriere 6.6k Nov 17, 2022
A package to build progressive web apps with Go programming language and WebAssembly.

Go-app is a package for building progressive web apps (PWA) with the Go programming language (Golang) and WebAssembly (Wasm). Shaping a UI is done by

Maxence Charriere 6.6k Nov 22, 2022
🐹🕸️ WebAssembly runtime for Go

Wasmer Go Website • Docs • Slack Channel A complete and mature WebAssembly runtime for Go based on Wasmer. Features Easy to use: The wasmer API mimics

Wasmer 2.2k Nov 19, 2022
🐹🕸️ WebAssembly runtime for Go

Wasmer Go Website • Docs • Slack Channel A complete and mature WebAssembly runtime for Go based on Wasmer. Features Easy to use: The wasmer API mimics

Wasmer 2.2k Nov 27, 2022
Vugu: A modern UI library for Go+WebAssembly (experimental)

Vugu: A modern UI library for Go+WebAssembly (experimental)

Vugu 4.5k Nov 18, 2022