Skip to content

Problems with Go 1.11rc1 [SOLVED] #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
carlca opened this issue Aug 15, 2018 · 20 comments
Closed

Problems with Go 1.11rc1 [SOLVED] #1

carlca opened this issue Aug 15, 2018 · 20 comments

Comments

@carlca
Copy link

carlca commented Aug 15, 2018

Golang 1.11rc1
macOS 10.13.6

Hi Dave, I've run go get -u github.com/dave/wasmgo on Go 1.11rc1 and as a result of Modules it had downloaded it to go/pkg/mod/github.com/dave/[email protected].

I switched into the helloworld folder where there is a main.go.

To save me having to type the full path, since I was there already, I tweaked the instructions to wasmgo deploy helloworld. Not surprisingly, since there is no source file called helloworld, this didn't work.

What do I have to do to get this to work?

Cheers,
Carl

@dave
Copy link
Owner

dave commented Aug 15, 2018

If you're in the correct directory, try just wasmgo deploy

@dave
Copy link
Owner

dave commented Aug 15, 2018

if there's no package argument, is uses . and it just passes this to the go build command, so wasmgo deploy will run go build .

@carlca
Copy link
Author

carlca commented Aug 15, 2018

Cool! It works, thanks a lot! And is it genuinely building a server-side wasm file and serving that?

@dave
Copy link
Owner

dave commented Aug 15, 2018

It's actually very simple - no magic going on at all... It does this:

  1. Runs go build on your machine to create a WASM binary.
  2. Uses a template to create the "loader js".
  3. Uses a template to create the index page.
  4. Works out the SHA1 hash of all three files, and asks the server if any of them are already present in the GCS bucket.
  5. If the server replies that the files are missing, it sends the contents of the files to the server.
  6. All the server does is verifies that the SHA1 hash is what the client says it is, and stores the file in the GCS bucket.

It's a very simple system... The complexity will come in future when we get the package splitting working... that'll be tricky.

@carlca
Copy link
Author

carlca commented Aug 15, 2018

It makes life very easy, at least for simple examples. I was experimenting with deploying to zeit.io using their now tool, but I am pleased to say that you have helped me deploy my first cloud-based wasm app. Well, it wasn't my app, that will follow!

Thanks again for this!

@dave
Copy link
Owner

dave commented Aug 15, 2018

No problem! Next I'm going to do a wasmgo serve mode that'll run a local development server and recompile each time you refresh the browser...

@carlca carlca closed this as completed Aug 16, 2018
@carlca carlca changed the title Problems with Go 1.11rc1 Problems with Go 1.11rc1 [SOLVED] Aug 16, 2018
@carlca
Copy link
Author

carlca commented Aug 16, 2018

Just one question, if I may. Do you have, or do you know of any more complicated examples of Golang WASM code? I've tried googling but the ones I have found so far all fail to build, or If they do build, they don't run properly at jsgo.io

@dave
Copy link
Owner

dave commented Aug 16, 2018

It would be great if you can show me any examples of projects that should work but don't in jsgo.io... It's always worth remembering that jsgo only serves the wasm file - if the project depends on assets being served (CSS etc) they will need to be served from somewhere else. I've usually used rawgit.com for this...

I haven't actually done much WASM examples, but I did a lot of GopherJS things and they should be pretty trivial to convert... I'll have a go later today.

@carlca
Copy link
Author

carlca commented Aug 16, 2018 via email

@carlca
Copy link
Author

carlca commented Aug 16, 2018

Okay, here is an example of some code that builds but which doesn't appear to run properly when deployed...

package main // import "lazyhackergo.com/wasmgo"

import (
	"math"
	"strconv"
	"syscall/js"
	"time"

	"lazyhackergo.com/browser"
)

var signal = make(chan int)

// Example of a callback function that can be registered.
func cb(args []js.Value) {
	println("callback")
}

// cbQuit is a function that gets attached to browser event.
func cbQuit(e js.Value) {
	println("got Quit event callback!")
	window := browser.GetWindow()
	window.Document.GetElementById("runButton").SetProperty("disabled", false)
	window.Document.GetElementById("quit").SetAttribute("disabled", true)
	signal <- 0
}

// keepalive waits for a specific value as a signal to quit.  Browsers still run
// javascript and wasm in a single thread so until the wasm module releases
// control, the entire browser window is blocked waiting for the module to
// finish.  It looks like that while waiting for the blocked channel, the
// browser window gets control back and can continue its event loop.
func keepalive() {
	for {
		m := <-signal
		if m == 0 {
			println("quit signal received")
			break
		}
	}
	// select {} also seems to work but the following doesn't:
	// select {
	//    case m <-signal:
	//       // do something
	//    default:
	//       // wait
	// }
}

func main() {
	q := js.NewEventCallback(js.StopImmediatePropagation, cbQuit)
	// defer q.Close()

	c := js.NewCallback(cb)
	// defer c.Close()
	browser.Invoke(c) //js.ValueOf(c).Invoke()

	window := browser.GetWindow()

	// Disable the Run button so the module doesn't get executed again while it
	// is running.  If it runs while a previous instance is still running then
	// the browswer will give an error.
	window.Document.GetElementById("runButton").SetAttribute("disabled", true)

	// Attach a browser event to the quit button so it calls our Go code when
	// it is clicked.  Also enable the Quit button now that the module is running.
	window.Document.GetElementById("quit").AddEventListener(browser.EventClick, q)
	window.Document.GetElementById("quit").SetProperty("disabled", false)
	//js.Global.Get("document").Call("getElementById", "quit").Call("addEventListener", "click", js.ValueOf(q))

	window.Alert("Triggered from Go WASM module")
	window.Console.Info("hello, browser console")

	canvas, err := window.Document.GetElementById("testcanvas").ToCanvas()
	if err != nil {
		window.Console.Warn(err.Error())
	}

	// Draw a cicule in the canvas.
	canvas.Clear()
	canvas.BeginPath()
	canvas.Arc(100, 75, 50, 0, 2*math.Pi, false)
	canvas.Stroke()

	// A Go routine that prints its counter to the canvas.
	canvas.Font("30px Arial")
	time.Sleep(5 * time.Second)
	go func() {
		for i := 0; i < 10; i++ {

			canvas.Clear()
			canvas.FillText(strconv.Itoa(i), 10, 50)
			time.Sleep(1 * time.Second) // sleep allows the browser to take control otherwise the whole UI gets frozen.
		}
		canvas.Clear()
		canvas.FillText("Stop counting!", 10, 50)

	}()
	window.Console.Info("Go routine running while this message is printed to the console.")

	keepalive()
}

@carlca
Copy link
Author

carlca commented Aug 16, 2018

Just to show that not everything is bad in example-land, this one works...

package main

import(
	"syscall/js"

	"github.com/PaulRosset/go-hacknews"
)

func topStories() []hacknews.Post {
	// This is an example from:
	// https://github.com/PaulRosset/go-hacknews
	// Copyright (c) 2017 Paul Rosset
	// See LICENSE in the link above.
	init := hacknews.Initializer{Story: "topstories", NbPosts: 10}
	codes, err := init.GetCodesStory()
	if err != nil {
		panic(err)
	}
	posts, err := init.GetPostStory(codes)
	if err != nil {
		panic(err)
	}
	return posts
}

var document = js.Global().Get("document")

func renderPost(post hacknews.Post, parent js.Value) {
	// Creates the following HTML elements
	// <li>
	//   <a href="{URL}">{Title}</a>
	// </li>
	li := document.Call("createElement", "li")
	a := document.Call("createElement", "a")
	text := document.Call("createTextNode", post.Title)
	a.Set("href", post.Url)
	a.Call("appendChild", text)
	li.Call("appendChild", a)
	parent.Call("appendChild", li)
}

func renderPosts(posts []hacknews.Post, parent js.Value) {
	ul := document.Call("createElement", "ul")
	parent.Call("appendChild", ul)
	for _, post := range(posts) {
		renderPost(post, ul)
	}
}

func main() {
	posts := topStories()
	body := document.Get("body")
	renderPosts(posts, body)
}

@dave
Copy link
Owner

dave commented Aug 16, 2018

The first example is looking for a DOM element called "runButton"... I believe in the example index page in the Go repo it has a run button instead of running the code when the page loads... This will need some tweaks...

@carlca
Copy link
Author

carlca commented Aug 16, 2018

This one doesn't build at all...

package main

import (
	"syscall/js"

	"github.com/shurcooL/github_flavored_markdown"
)

var document = js.Global().Get("document")

func getElementByID(id string) js.Value {
	return document.Call("getElementById", id)
}

func renderEditor(parent js.Value) js.Value {
	editorMarkup := `
		<div id="editor" style="display: flex; flex-flow: row wrap;">
			<textarea id="markdown" style="width: 50%; height: 400px"></textarea>
			<div id="preview" style="width: 50%;"></div>
			<button id="render">Render Markdown</button>
		</div>
	`
	parent.Call("insertAdjacentHTML", "beforeend", editorMarkup)
	return getElementByID("editor")
}

func main() {
	quit := make(chan struct{}, 0)

	// See example 2: Enable the stop button
	stopButton := getElementByID("stop")
	stopButton.Set("disabled", false)
	stopButton.Set("onclick", js.NewCallback(func([]js.Value) {
		println("stopping")
		stopButton.Set("disabled", true)
		quit <- struct{}{}
	}))

	// Simple markdown editor
	editor := renderEditor(document.Get("body"))
	markdown := getElementByID("markdown")
	preview := getElementByID("preview")
	renderButton := getElementByID("render")
	renderButton.Set("onclick", js.NewCallback(func([]js.Value) {
		// Process markdown input and show the result
		md := markdown.Get("value").String()
		html := github_flavored_markdown.Markdown([]byte(md))
		preview.Set("innerHTML", string(html))
	}))

	<-quit
	editor.Call("remove")
}

@carlca
Copy link
Author

carlca commented Aug 16, 2018

You may be interested to know that I've been building these with Go Modules activated (GO111MODULE=on) and after running go mod init <reponame> for each one, that aspect of it seems to work perfectly.

@dave
Copy link
Owner

dave commented Aug 16, 2018

OK so I just deployed a new version... Do a go get -u github.com/dave/wasmgo... now you might be able to get the first example working if you add an index.wasmgo.html with the following contents:

<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>

<head>
	<meta charset="utf-8">
	<title>Go wasm</title>
</head>

<body>
	<script src="{{ .Script }}"></script>
	<script>
		if (!WebAssembly.instantiateStreaming) { // polyfill
			WebAssembly.instantiateStreaming = async (resp, importObject) => {
				const source = await (await resp).arrayBuffer();
				return await WebAssembly.instantiate(source, importObject);
			};
		}
		const go = new Go();
		let mod, inst;
		WebAssembly.instantiateStreaming(fetch("{{ .Binary }}"), go.importObject).then((result) => {
			mod = result.module;
			inst = result.instance;
			document.getElementById("runButton").disabled = false;
		});
		async function run() {
			console.clear();
			await go.run(inst);
			inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
		}
	</script>

	<button onClick="run();" id="runButton" disabled>Run</button>
</body>

</html>

@dave
Copy link
Owner

dave commented Aug 16, 2018

The markdown example built fine and deployed for me but doesn't run...

 $ wasmgo -v -c=go1.11beta3 deploy
Compiling...
Querying server...
Files required: 3.
Bundling required files...
Sending payload: 1137KB.
Storing, 1 to go.
Storing, 2 to go.
Storing, 3 to go.
Storing, 2 to go.
Storing, 1 to go.
Storing, 0 to go.
Sending done.
https://jsgo.io/c4bc3cab491b3879df546d95a2e9394dce1ed079

... it doesn't look like a wasmgo failure though... I'd want to test this locally to check it runs ok.

@carlca
Copy link
Author

carlca commented Aug 16, 2018

Good work Dave! I can confirm that the example with the run button and the additional html file now works perfectly, if unspectacularly!

@carlca
Copy link
Author

carlca commented Aug 16, 2018

I don't know how familiar you are with Go Modules, but I've had some issues updating the package. Details are at golang/go#27028. Hopefully, Russ Cox will see the issue and answer it. We'll see!

@dave
Copy link
Owner

dave commented Aug 16, 2018

I’m afraid I haven’t looked into modules yet. Is there something I need to add as a package author? I assume not until I want to enforce dependencies...

@carlca
Copy link
Author

carlca commented Aug 16, 2018

No worries Dave! I'll see if Russ Cox answers my question and I'll keep you posted - it's an area that every Golang package author will need to address sooner or later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants