Open mjohnson9 opened 6 years ago
I was wondering if this is different from https://github.com/gopherjs/websocket/pull/26 .
This is great, thank you for working on this @nightexcessive! I tested this quickly with one of my projects that uses WebSockets substantially, and everything was working as expected when compiled with WebAssembly.
We certainly can't merge this as is and remove the GopherJS support at the same import path, as that would break all existing users. However, I think we can and should support both.
Making a library that works in both WASM and GopherJS is a much larger scope than I wanted to take on. There are differences in execution environment and callbacks that make them behave significantly differently.
I don't quite understand why it'd be difficult. I'll limit discussion to the high-level bindings package only. We currently have the following API at github.com/gopherjs/websocket
:
// Dial opens a new WebSocket connection. It will block until the connection is
// established or fails to connect.
func Dial(url string) (net.Conn, error)
I think we need to follow this Go proverb:
Syscall must always be guarded with build tags.
That applies to syscall/js
and github.com/gopherjs/gopherjs/js
packages, they're basically the equivalent of syscall for the browser.
We can have two implementations of the Dial
function in two separate files, e.g.:
conn_js.go:
// +build js,!wasm
package websocket
import (
// ...
"github.com/gopherjs/gopherjs/js"
"github.com/gopherjs/websocket/websocketjs"
)
func Dial(url string) (net.Conn, error) {
// ... GopherJS implementation
// ... use websocketjs package as before
}
conn_wasm.go:
// +build js,wasm
package websocket
import (
// ...
"syscall/js"
)
func Dial(url string) (net.Conn, error) {
// ... WebAssembly implementation
// ... don't import websocketjs, just copy and directly integrate
// its code, modified for WebAssembly
}
(Optionally, we can have the package level Dial
call a build tag-constrained dial
implementation. That way, it's easier to enforce and document the public API in one place.)
Is this an approach you've considered @nightexcessive?
Edit: To clarify, I'm only suggesting we add WebAssembly support to websocket
package, not the low-level websocketjs
binding. We can/should copy the websocketjs
code modified for WebAssembly into websocket
as unexported code.
I haven't considered that approach. I was considering the gopherwasm/js
package only. I think I've modified it such that it may work with both gopherjs and wasm using gopherwasm/js
. I don't know how to compile the tests to work with gopherjs, however.
I've made it use the gopherwasm/js
package instead of syscall/js
. However, GopherJS can't seem to compile it when using Go 1.11 on Windows.
I haven't considered that approach. I was considering the
gopherwasm/js
package only.
Can we discuss that approach?
My understanding is that using gopherwasm/js
is a good idea in the following situations:
syscall/js
and make it quickly support GopherJS as well.syscall/js
, another using github.com/gopherjs/gopherjs/js
).However, I think it would be better for us to choose to use syscall/js
directly in this package, and maintain two implementation copies (the previous GopherJS one that uses github.com/gopherjs/gopherjs/js
, and the new WebAssembly port that uses syscall/js
directly).
Here's why I think it's better in this context:
That said, using gopherwasm/js
is also viable and not a bad choice. If you have arguments for why you'd prefer to go with gopherwasm/js
approach, I'm happy to hear them.
What do you think?
Since almost all of the functions in both the websocket
and websocketjs
package interact with JavaScript, we'd have two choices:
websocket.conn.RemoteAddr
calls websocket.conn.url
to get the connection URL instead of making the JavaScript call directlyObviously, the former seems more desirable. Regardless of the way we do it, there will almost certainly be an extra layer of abstraction.
Obviously, the former seems more desirable.
Ok, it sounds like you're feeling pretty strongly about avoiding having two implementations of the high-level websocket
package (one using syscall/js
, the other using github.com/gopherjs/gopherjs/js
) and prefer to have one (using github.com/gopherjs/gopherwasm/js
). I'm okay to proceed with that. If there's ever a real need in the future, we can always migrate to the two-implementation approach, since it's just an internal implementation detail (no public API is going to be affected).
Regardless of the way we do it, there will almost certainly be an extra layer of abstraction.
Not sure what extra layer of abstraction you're referring to when syscall/js
and github.com/gopherjs/gopherjs/js
are used directly. Can you clarify?
However, let's talk about the package organization first.
We have two packages here right now:
github.com/gopherjs/websocket
- the high-level package with a single func Dial(url string) (net.Conn, error)
APIgithub.com/gopherjs/websocket/websocketjs
- the low-level package wrapping the WebSocket JavaScript object, with an API that exposes github.com/gopherjs/gopherjs/js
internal to its usersI will argue that we have a hard constraint that we cannot break the API of github.com/gopherjs/websocket/websocketjs
for GopherJS users. This package has existed for a while, and it's inside the gopherjs
organization. I don't think it's viable to break its API and all its existing users this late.
That leaves us with these options of what to do about github.com/gopherjs/websocket/websocketjs
for WebAssembly:
github.com/gopherjs/websocket/websocketjs
with a different API. That means the github.com/gopherjs/websocket/websocketjs
package will have different API depending on whether you're trying to compile it with GopherJS compiler or for WebAssembly.github.com/gopherjs/websocket/websocketjs
at all.Option 1 is not great because the same package would have to be used differently depending on which compiler you're trying to use. It feels confusing and hostile (e.g., godoc.org can't even display documentation for different GOOS/GOARCH values).
I think we should pick option 3, and not try to add WebAssembly support for github.com/gopherjs/websocket/websocketjs
at all. This low-level package was initially created before the high-level one existed, and it was helpful for people who were more familiar with JavaScript and its WebSocket API rather than Go and its net.Conn
interface. By now, people trying to use WebSockets within WebAssembly should be quite comfortable using net.Conn
, which is a much more pleasant and powerful experience.
So my suggestion is we leave github.com/gopherjs/websocket/websocketjs
alone, and only add WebAssembly support to the high-level github.com/gopherjs/websocket
package, where it's possible to do so without changing its API.
Does that make sense, and what do you think? Let me know if anything's confusing.
@dmitshur
I don't have a strong opinion on that, but let me comment...
It gives us more direct control of the underlying syscall package, and all of its features, even if some happen not to be compatible with GopherJS. This is helpful for performance, and ability to make WebAssembly-specific changes. It does mean more code and more work to maintain it, but I don't think the cost is that high and is worth it.
Interesting. Would you give me some examples (sorry if I missed)?
I just think it's better to keep this package as low-overhead as possible, and avoiding an extra abstraction layer is in line with that goal.
I tend not to think the overhead is so big that we need to consider, since GopherJS itself's overhead is much bigger.
looking at how much of conn.go
is touched to support wasm I'd say using an abstraction layer seems like a better choice than duplicating the code.
and some of the minor improvements @nightexcessive already added to the PR as well demonstrate that both targets could benefit from shared improvements when their differences are abstracted away.
if you feel strongly about not using gopherwasm/js
I could write a PR with the abstractions done inside this repo instead, but it feels like it would result in less readable code in the end than what @nightexcessive has written.
However, GopherJS can't seem to compile it when using Go 1.11 on Windows.
any specifics on this?
This is a port to WASM. I'm not sure that, given that this is a gopherjs library, we should officially adopt WASM as the only way of using the library.
Making a library that works in both WASM and GopherJS is a much larger scope than I wanted to take on. There are differences in execution environment and callbacks that make them behave significantly differently.
I've also changed how tests work, given that we can use
go test
for WASM testing. That is documented inHOW_TO_TEST.md
.This passes all of the current tests.
Resolves #25.