Creating many Javascript artifacts in SBT

So I already have twenty or so JavaScript files that I create from ScalaJs and that number is probably only going to increase. I originally had them as separate sub projects, but because in Sbt you can’t have sub-sub-projects this became unmanageable. So I now have a single JS sub project and manually add in an extra source directory and set the mainclass as in the example below.

set Compile/unmanagedSourceDirectories += (ThisBuild/baseDirectory).value / "EGrid/JsAppsSrc/EarthApp";
set Compile/mainClass:= Some("ostrat.pSJs.EarthAppJs")

The extra source directory only has as single file with a one line Main object, as Sbt doesn’t allow you to add individual source files. The problem is how do I automate building all the assets. Is there a better way to do this in SBT or should I use Mill or just automate the process via a batch file?

You can define a command that chains all the required sets and builds in sbt. Use an addCommandAlias to do that without fuss. For example as is done here:

I don’t think Mill will help you. You’ll have the same issue there, AFAICT.

The reason I didn’t just chain the setting changes is that

set Compile/unmanagedSourceDirectories += (ThisBuild/baseDirectory).value / "EGrid/JsAppsSrc/EarthApp"

mutates the build state, so each artifact would add another file. I guess in retrospect that shouldn’t effect the final artifacts as the optimiser will strip out unneeded classes, but still I thought the whole point of Sbt is that the build is functional and is normally not mutated after loading. So I decided to use custom configurations.

lazy val Diceless = config("Diceless") extend(Compile)
lazy val Discov = config("Discov") extend(Compile)
lazy val IndRev = config("IndRev") extend(Compile)

lazy val AppsJs = jsProj("Apps").dependsOn(EGridJs).settings(
  inConfig(Diceless)(Defaults.compileSettings),
  inConfig(Diceless)(ScalaJSPlugin.compileConfigSettings),
  Diceless/unmanagedSourceDirectories := (Compile/unmanagedSourceDirectories).value :+ jsAppsDir.value / "DicelessApp",
  Diceless/mainClass:= Some("ostrat.pSJs.DicelessAppJs"),

  inConfig(Discov)(Defaults.compileSettings),
  inConfig(Discov)(ScalaJSPlugin.compileConfigSettings),
  Discov/unmanagedSourceDirectories := (Compile/  unmanagedSourceDirectories).value :+ jsAppsDir.value / "DiscovApp",
  Discov/mainClass:= Some("ostrat.pSJs.DiscovAppJs"),

  inConfig(IndRev)(Defaults.compileSettings),
  inConfig(IndRev)(ScalaJSPlugin.compileConfigSettings),
  IndRev/unmanagedSourceDirectories := (Compile/unmanagedSourceDirectories).value :+ jsAppsDir.value / "IndRevApp",
  IndRev/mainClass:= Some("ostrat.pSJs.IndRevAppJs"),

This does become rather verbose but does maintain the immutability of the build. I wasn’t aware of addCommandAlias. Thanks for the tip. However I presumed I couldn’t use it in this instance as I wanted to copy the files as well. So I just used an ordinary Task.

lazy val allJs = taskKey[Unit]("Task to build all Js assets.")
allJs :=
{ import io.IO.copyFile
  val scStr: String = "scala-" + scalaVersionStr + "/appsjs-"
  (AppsJs / Diceless / fullLinkJS).value
  copyFile(bbDir.value / "AppsJs/target" / (scStr + "Diceless-opt/main.js"), siteDir.value / "earthgames/dicelessapp.js")
  (AppsJs/Discov/fullLinkJS).value
  copyFile(bbDir.value / "AppsJs/target" / (scStr + "Discov-opt/main.js"), siteDir.value / "earthgames/discovapp.js")
  (AppsJs/IndRev/fullLinkJS).value
  copyFile(bbDir.value / "AppsJs/target" / (scStr + "IndRev-opt/main.js"), siteDir.value / "earthgames/indrevapp.js")
  (AppsJs/Sors/fullLinkJS).value

I couldn’t seem to find a way to limit the custom configurations to a single sub-project rather making them global.

Depending on the use case, you might also want to consider emitting multiple ES modules from a single Scala.js project. Especially if all these projects actually have a lot of code in common, and only differ in their entry points. Emitting multiple modules, each with a different entry point, would then share the common code as common ES modules. Producing those files is also a lot faster, since it’s a single linker run.

1 Like