Can I define/piece together a javascript class within golang?

Viewed 200

I have experience in Go but am totally green when it comes to Javascript. I've started playing around with Go's js/wasm target. I was wondering whether it's possible to define a JS class within golang. The syscall/js package provides no direct way to do so, but according to a tutorial, a JS class is essentially just a constructor function and a prototype. So I attempted to build the Rectangle toy example from the tutorial in Go:

package main

import (
    "syscall/js"
)

// Attempt to create a simple javascript class:
//
// class Rectangle {
//   constructor(height, width) {
//     this.height = height;
//     this.width = width;
//   }
//
//   // Method
//   area() {
//     return this.height * this.width;
//   }
// }

func rectangleArea(this js.Value, args []js.Value) interface{} {
    height := this.Get("height").Float()
    width := this.Get("width").Float()
    return height * width
}

func rectangleConstructor(this js.Value, args []js.Value) interface{} {
    this.Set("height", args[0])
    this.Set("width", args[1])
    return this
}

func main() {
    const name = "Rectangle"
    rectangle := js.FuncOf(rectangleConstructor)
    rectangle.Set("prototype", map[string]interface{}{
        "constructor": rectangle,
        "area":        js.FuncOf(rectangleArea),
    })
    js.Global().Set(name, rectangle)
    select {}
}

It seems to work in my browser's Javascript console: I can instantiate a Rectangle, extend the class, and so on. Cool. The one thing that worries me is that direct comparison between my Rectangle class and an identical Rectangle2 class defined in the JS console yields differences:

> Rectangle
function _makeFuncWrapper()
​  length: 0
​  name: ""
​  prototype: Object { constructor: _makeFuncWrapper(), area: _makeFuncWrapper() }
​​    area: function _makeFuncWrapper()​​
    constructor: function _makeFuncWrapper()​​
    <prototype>: Object { … }
​  <prototype>: function ()

> Rectangle2
class Rectangle2 { constructor(height, width) }
  length: 2
  name: "Rectangle2"
  prototype: Object { … }
    area: function area()
    constructor: class Rectangle2 { constructor(height, width) }
    <prototype>: Object { … }
  <prototype>: function ()

Of course, the differences might all be due to special Go/wasm magic.

Long story short, my question is: Is what I'm doing a sound and sane way to define JS classes from within Go? If not, is there a better way?

1 Answers

Browser-specific stuff, like creating a tag with a class inside, you'll do like the code below, interacting with the browser.

However, things that are not browser specific, continue in go native.

//go:build js

package main

import (
    "strconv"
    "syscall/js"
)

//native go code
type Retangle struct {
    width  int64
    height int64
}

func (r *Retangle) Init(width, height int64) {
    r.width = width
    r.height = height
}

func (r Retangle) Area() int64 {
    return r.width * r.height
}

func main() {
    var class = //"<![CDATA[\n"+
    "div {\n" +
    "color: red;\n" +
    "}\n"
    //"]]>",
    
    rect := Retangle{}
    rect.Init(10, 20)
    
    //browser js code
    var tagStyle js.Value
    tagStyle = js.Global().Get("document").Call("createElement", "style")
    tagStyle.Set("innerHTML", class)
    
    js.Global().Get("document").Get("body").Call("appendChild", tagStyle)
    
    var tagDiv js.Value
    tagDiv = js.Global().Get("document").Call("createElement", "div")
    tagDiv.Set("innerHTML", "área: "+strconv.FormatInt(rect.Area(), 10))
    
    js.Global().Get("document").Get("body").Call("appendChild", tagDiv)
}

Can you understand when to use one and the other?

Makefile:

GO ?= go

.PHONY: help
## this help command
help:
    @printf "Opções de comandos\n\n"
    @awk '/^[a-zA-Z\-\_0-9]+:/ { \
        helpMessage = match(lastLine, /^## (.*)/); \
        if (helpMessage) { \
            helpCommand = substr($$1, 0, index($$1, ":")-1); \
            helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
            printf "make %-30s ## %s\n", helpCommand, helpMessage; \
        } \
    } \
    { lastLine = $$0 }' $(MAKEFILE_LIST)

.PHONY: build
## build this example
build:
    $(GO) mod tidy
    GOARCH=wasm GOOS=js $(GO) build -o main.wasm

Html output:

<style>
div {
    color: red;
}
</style>
<div>área: 200</div>
Related