Skip to content
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

Added support for Scala JS for the main scripts #930

Closed
wants to merge 7 commits into from

Conversation

dylanowen
Copy link
Contributor

@dylanowen dylanowen commented Jul 17, 2018

Not Ready To Merge

This is a partial implementation of using Scala JS for our site's scripts instead of Vanilla JS.

I'm opening this pull request mainly to get feedback on the direction and to see if it's worth completing. I'm totally fine with tossing this code if we don't think it makes sense for the project.

Should we actually do this?
This is a large change with multiple moving pieces. Should we really be changing something that isn't broken?

Other Concerns

  1. Maintainability: If our maintainers are relying on bundler vs the docker-compose scripts we'll be breaking their workflow.
  2. I'm hoping this makes the code more accessible to scala-lang maintainers but I don't have any insights on if JS actually makes mores sense.
  3. This makes our builds more complex.

Missing Pieces

  1. Compiling the Scala in our build (do we use travis, jenkins, or both?)
    a. sbt fullOptJS I'm using the closure compiler to minify the output but we can turn that off to make prod debugging easier

Anyway let me know if I should continue and if there is any feedback on my implementation.

- 80:4000
command: jekyll serve --incremental
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming that docker-compose is only used for development, but if this isn't the case I could be breaking things here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct it's optional dev-only stuff

/**
* A basic logger for the debugging console
*/
object Logger {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I browsed around for a few scala js logging implementation but didn't find one that fit my needs. I'm open to swapping this out for a different project vs my home-built solution

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps you could put a comment to that effect at the top of the file, and say briefly what the considerations were

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have two actual uses of this logger. I suggest replacing them by a good old println.

@@ -1,6 +1,3 @@
---
---
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this isn't needed after #924

Copy link
Member

@SethTisue SethTisue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My familiarity with Scala.js and front-end stuff in general is limited, but:

Cool! I like it and I lean towards accepting this (once complete). I think being used on scala-lang.org is good bragging rights for Scala.js.

My sense is that is that if we end up having Scala.js-related trouble there will be no shortage of volunteers wanting to help out. And yeah it makes the build more complicated, but I doubt that will affect anyone doing ordinary work on the site.

Interested in hearing other opinions on this. (@heathermiller? @sjrd?)

- 80:4000
command: jekyll serve --incremental
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct it's optional dev-only stuff

/**
* A basic logger for the debugging console
*/
object Logger {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps you could put a comment to that effect at the top of the file, and say briefly what the considerations were

@sjrd
Copy link
Member

sjrd commented Jul 20, 2018

I am skeptical. Even https://www.scala-js.org/ does not use any Scala.js code, and for one good reason: it is a mostly static website, with a couple scripts for isolated functions on the site. Most of the code consists in external libraries. The same applies to scala-lang.org.

Scala.js does not shine on small scripts. You will bloat the download times because the Scala.js code size "tax" will be much larger than the features you are implementing---unlike in larger applications, where Scala.js is appropriate because the tax is dwarfed by the actual codebase of the website.

Just look at the PR stats: +441 for -91. If you're increasing the codebase size by porting to Scala.js, you're likely doing yourself a disservice. One would say that most of it is about build scripts, and that is true. But let us also look at the features themselves. For example, this PR replaces the JavaScript code:

// OS detection
function getOS() {
  var osname = "linux";
  if (navigator.appVersion.indexOf("Win") != -1) osname = "windows";
  if (navigator.appVersion.indexOf("Mac") != -1) osname = "osx";
  if (navigator.appVersion.indexOf("Linux") != -1) osname = "linux";
  if (navigator.appVersion.indexOf("X11") != -1) osname = "unix";
  return osname;
}

$(document).ready(function() {
    if ($(".main-download").length) {
        var os = getOS();
        var intelliJlink = $("#intellij-" + os).text();
        var sbtLink = $("#sbt-" + os).text();
        var stepOneContent = $("#stepOne-" + os).html()
        $("#download-intellij-link").attr("href", intelliJlink);
        $("#download-sbt-link").attr("href", sbtLink);
        $("#download-step-one").html(stepOneContent);
    }
});

by a complex abstraction for the OS in addition to the following Scala.js code:

  private def setupMainDownload(): Unit = JsUtils.findElement(".main-download").foreach(_ => {
    val osLabel: String = OS().label

    val intelliJlink: String = $("#intellij-" + osLabel).text()
    val sbtLink: String = $("#sbt-" + osLabel).text()
    val stepOneContent: String = $("#stepOne-" + osLabel).html()

    $("#download-intellij-link").attr("href", intelliJlink)
    $("#download-sbt-link").attr("href", sbtLink)
    $("#download-step-one").html(stepOneContent)
  })

What have we gained? Virtually nothing. Just a little bit of type-safety, which TypeScript would have also been able to provide, without the Scala.js bloat.

I think the PR was a worthwhile experiment, if only to demonstrate that Scala.js is not the best option for this specific job.

Copy link
Member

@sjrd sjrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are a couple suggestions for improvements, in case you decide to go through with this.


// move our output folder to static
artifactPath in(Compile, fastOptJS) := baseDirectory.value / ".." / ".." / "resources" / "js" / s"scala-${name.value}.js"
artifactPath in(Compile, fullOptJS) := baseDirectory.value / ".." / ".." / "resources" / "js" / s"scala-${name.value}.js"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't configure fastOptJS and fullOptJS to produce the same target file. It will confuse caches (fullOptJS after fastOptJS will not recreate the file, for example).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good to know. What's the recommended way to setup a build like this? (Where I'd like my html file to be static and to just import the compiled output regardless of compilation target)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I think it depends on the web server and/or templating mechanisms that one use. The folks at https://gitter.im/scala-js/scala-js or https://stackoverflow.com/questions/tagged/scala.js might have more experience with that than I do.

@@ -0,0 +1,18 @@
import org.scalajs.core.tools.linker.backend.OutputMode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import is unused.

// move our output folder to static
artifactPath in(Compile, fastOptJS) := baseDirectory.value / ".." / ".." / "resources" / "js" / s"scala-${name.value}.js"
artifactPath in(Compile, fullOptJS) := baseDirectory.value / ".." / ".." / "resources" / "js" / s"scala-${name.value}.js"
scalaJSOptimizerOptions in (Compile, fullOptJS) ~= { _.withUseClosureCompiler(true) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setting is redundant, as it is the default.

scalaJSOptimizerOptions in (Compile, fullOptJS) ~= { _.withUseClosureCompiler(true) }

scalaJSUseMainModuleInitializer := true
emitSourceMaps := false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer the more future-proof

scalaJSLinkerConfig ~= { _.withSourceMap(false) }

/**
* A basic logger for the debugging console
*/
object Logger {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have two actual uses of this logger. I suggest replacing them by a good old println.

else {
last
}
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More simply:

val os = Array(Windows, Mac, Linux, Unix)
  .find(os => appVersion.contains(os.navigator))
  .getOrElse(default)

*/
sealed trait OS {
val navigator: String
val label: String
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to cut down on the boilerplate, consider declaring OS as

sealed abstract class OS(val navigator: String, val label: String)

so that the instances are one-liners:

case object Windows extends OS("Win", "Windows")

@dylanowen
Copy link
Contributor Author

Thanks for the feedback and detailed comments @SethTisue and @sjrd 😄

While this was a fun exercise I'm inclined to agree with @sjrd's analysis: we're better off sticking with vanilla JS.

I'm closing out this PR but I've updated my code with the feedback so if we ever decide Scala.js makes sense we'll know where to find it.

@dylanowen dylanowen closed this Jul 21, 2018
@jvican
Copy link
Member

jvican commented Jul 22, 2018

Thanks for your work Dylan 👍

Just in case you're thinking of playing around with Scala.js more, you can have a look at scala/docs.scala-lang#1107.

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

Successfully merging this pull request may close these issues.

4 participants