-
Notifications
You must be signed in to change notification settings - Fork 601
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
Cross build fs2.io for Scala.js #2453
Conversation
io/js/src/main/scala/fs2/io/net/DatagramSocketGroupPlatform.scala
Outdated
Show resolved
Hide resolved
Alas, I was being naïve thinking wishfully that Websockets === Sockets. Seeing as they're not, implementing fs2.io for browsers is a no-go, and therefore Node.js would be the canonical platform, so I think it's okay to implement specifically for Node.js. Forthcoming ... |
Scala 3 builds are failing for now pending ScalablyTyped/Converter#202 |
This reverts commit baa0f52.
@@ -104,13 +104,33 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq( | |||
ProblemFilters.exclude[ReversedMissingMethodProblem]( | |||
"fs2.io.net.tls.TLSContext.dtlsServerBuilder" | |||
), | |||
ProblemFilters.exclude[Problem]("fs2.io.net.tls.TLSEngine*") | |||
ProblemFilters.exclude[Problem]("fs2.io.net.tls.TLSEngine*"), | |||
ProblemFilters.exclude[MissingClassProblem]("fs2.io.net.Socket$IntCallbackHandler"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all of these exclusions safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They should be. Before we merge I will go through one more time and double-check, I can add comment justifications as well for peace-of-mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
build.sbt
Outdated
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)), | ||
Compile / npmDependencies += "@types/node" -> "16.0.0", | ||
useYarn := true, | ||
stOutputPackage := "fs2.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be fs2.io.js
? Or fs2.io.jsdeps
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whatever you want, really! Happy to make it jsdeps
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, the reason I didn't put it in io
... the JS bindings could be useful outside of io
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After pondering a bit: I think the package should be fs2.internal.jsdeps
and we should not expose these bindings in public APIs. There are lots of them, and because they are auto-generated by Scalably Typed which is technically still beta and they are defined by the Typescript community, I'm just scared whether we will be able to control their binary evolution. The primary purpose for adding these bindings was to help me build the internal implementations safely, not for public consumption.
For the couple places we do expose public APIs of JS-native objects, we can either:
- provide our own bindings (that we control bincompat of), or
- just accept
js.Any
(or a newtype wrapper around that) in the spirit of BYOB (bring-your-own-bindings)
@mpilquist thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to use ST for this the way forward would be to use https://scalablytyped.org/docs/library-developer to generate a library which could be published independently of fs2 releases. If you use very little I'd just copy/paste the necessary parts to be completely in control.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyvindberg thanks for chiming in! I did find that guide very helpful when setting up the current build.
I'm still inclined to use the ST facades as internal API (it would be awesome if there's a way to automatically mark them as package-private?). I do appreciate the (continued) automation ST provides and would prefer not to chase down the copy/paste necessary to take control. Furthermore, the public APIs we provide in their place can be much more idiomatic: Chunk[Byte]
instead of Buffer
, callbacks A => F[B]
instead of (A, B => Unit) => Unit
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, I just jumped in and assumed you used the default application mode - apologies.
In that case if it's fast enough to not upset anyone and marked as internal it's probably a good idea.
By package private, do you mean private[fs2]
for every generated thing? I'd say that would be easy enough to add
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oyvindberg No reason to apologize for docs so good that I don't need your help 😉
Glad you agree! Yes, that's exactly what I mean by package private :)
def reads: Stream[F, Byte] | ||
|
||
/** Indicates that this channel will not read more data. Causes `End-Of-Stream` be signalled to `available`. */ | ||
private[net] trait SocketPlatform[F[_]] { | ||
def endOfInput: F[Unit] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Node doesn't support endOfInput
but does support endOfOutput
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I stared at Node's Socket
API for a very long time and couldn't find it.
https://nodejs.org/api/net.html#net_class_net_socket
This raises an interesting issue: for unsupported methods like this, should we
- make them platform-specific
- raise an exception
- do nothing
(1) Causes downstreams to experience the most pain (i.e. full cross) for what could otherwise be pure crosses. But, it makes authors aware of the missing method at compile-time. (2) and (3) still allow for a pure cross, but then there may be unhappy surprises at runtime.
As a motivating example, consider this seemingly innocuous ember-client
code:
https://github.com/http4s/http4s/blob/main/ember-client/src/main/scala/org/http4s/ember/client/EmberConnection.scala#L30
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think I'm all for (1). Still a bit painful, but downstreams can mitigate (1) by adding implicit syntax extensions to fill in the gaps however they like. Then, their code can purely cross-build.
@mpilquist thank you very much for the review! Does the approval mean you are open to merging if I address your comments? I can follow up with the missing stuff ( |
Generally okay with merging as long as you think we're in a releasable state -- I don't want to get stuck in a situation where the main branch is not releasable until some other PRs come in. Keep in mind binary compatibility too -- e.g., if we merge and then need to release a 3.0.next, then we're stuck with these APIs for a couple of years. Flip side is bincompat is easier to support on JS so maybe that's not a concern. |
Makes sense. I think it's definitely releasable—there's no
I've been wondering about this ... what is bincompat for JS? I think the only new APIs I've introduced for JS are the |
I'm marking this PR as ready-for-review, although it is missing a few things I'd say it's in a "releasable" state. Specifically, by "releasable" I mean I feel confident about the public APIs introduced. I'd be shocked if the internal implementations don't need some fine-tuning and/or re-working so in that sense I would say this is still experimental/beta. All the test suites are happy though. What's missing:
|
This PR lays the groundwork for cross-building fs2.io to Scala.js. My goal is that fs2.io on Scala.js will enable http4s ember on Scala.js in http4s/http4s#4938. I also hope this will unlock other interesting use-cases, such as command-line utilities that can take advantage of JS's fast startup times.
Specifically, I've separated out the typeclasses from the JVM-specific implementations (without compromising binary compatibility). This enables the typeclasses to be cross-built for Scala.js. A subset of the IoSuite is already passing tests on Scala.js.
At this point no JS-specific implementations are provided (besides some stubs). It remains an open question how best to simultaneously support different JS-platforms (primarily, browsers and Node.js). This work can be done piece-by-piece (
net
is higher priority thanfile
at least for http4s) in follow-up PRs, or I'm also happy to continue here if so desired.Thanks in advance for your reviews and feedback!