gopherjs.github.io
gopherjs.github.io copied to clipboard
Gopherjs runtime on frontend - inspired by gopherjs playground
Hello!
I've been experimenting these past 2 weeks on my spare time, trying to expose a runCode
global at the frontend, that will allow users to run arbitrary Go code, purely on the client side, it is heavily inspired by the gopher playground.
I removed the UI parts, and made some modifications, but I seem to be hitting a roadblock, when trying to use
runCode(`
package main
import (
"fmt"
"syscall/js"
)
func main() {
fmt.Println("Hello, playground")
js.Global().Call("alert", "Hello, JavaScript")
js.Global().Get("console").Call("log", "Hello, JS console")
}
`)
I end up getting
$mapIndex is not defined
this error originates from fmt.Println
function only, other fmt functions I've tested work as expected.
Also, I've noticed that in the original code, there's a loading phase first, and then a run, it seems necessary in my case too, (running the code seems to be doing the loading phase + running, which throws some errors, but running again, will just run the code without loading, and result with no console errors) I'm just not sure as to how to implement it.
the altered code can be found here https://github.com/naorzr/gopherjs-runtime/blob/master/playground/main.go
and it can be run and played with by running the index.html
from this repo
https://github.com/naorzr/gopherjs-runtime/tree/master/playground
I haven't tried to run your code, but I see at least one significant misunderstanding, which is probably causing at least some of your issues.
There aren't two phases per se. You provide the compiler with the main package and the importContext
object. That object is supposed to load precompiled archives of imported packages on demand and return them to the compiler. The regular gopherjs compiler uses this to recursively compiler the whole import tree starting with the main package.
So instead of running one attempt to compiler, collecting dependencies, loading them, and making the next attempt you should just load the package when the compiler asks for it, completing the whole thing in a single pass.
Besides that, a couple of general tips:
- GopherJS supports the standard
net/http
client, you could use that instead of thehonnef.co/go/js/xhr
package to load imported packages. - Instead of
js.Global.Call("eval", js.InternalObject("console.log('code: "+string(escaped)+"')"))
you can useprintln(code)
to print to the debug console.fmt.Printf()
works for that purpose too. To callconsole.error()
you can simply dojs.Global.Get("console").Call("error", "my message")
.
So instead of running one attempt to compiler, collecting dependencies, loading them, and making the next attempt you should just load the package when the compiler asks for it, completing the whole thing in a single pass.
if I get it right, do you suggest placing the http.get(...)
logic for getting the package inside the import context?
Ok, so I spent some time looking at your code today, and there were two main issues:
- Your
runCodeAsync()
function wasn't actually async — you were still blocking on a channel at the end, which is why on the first run it would throw an error after kicking off HTTP requests. The second execution would work because the HTTP request would complete in the background and then no blocking operations would happen. - There was some weird desynchronization between the version of runtime the compiler was outputting and the precompiled packages. Not sure why the compiler was picking up the wrong version, but I was able to fix that by upgrading the go.mod dependency to gopherjs 1.18.0-beta3 and running
gopherjs clean
.
On a more general note, part of the GopherJS philosophy is that you could try to write your code as you would with normal Go, as much as possible, and confine JS-aware parts to as few places as possible. That tends to yield a much more readable program. I did some refactoring of your code and you can find a working example here:
https://github.com/nevkontakte/gopherjs.github.io/commit/5408ff2dd5db0cbec64f6017f9229a3aac7c8841#diff-6639521c1bb3cd1e0ee9891ede42f19a89aab52a1417124481590cf761dfe432