Exploring options in making scalajs javascript available to server

2015-02-06

I am busy with setting up a reference implementation for a migration of an angular application using coffeescript to scalajs. The current back-end (rest api) is written in nodejs and this might also be migrated to a scala based solution in order to share code between the client and server (an awesome feature of scalajs).

Most of the application code written in coffee is being migrated/re-written in scalajs. And this process is going rather smooth.

We have been asked to provide the reference implementation and training for the angular/scalajs client and a new scala based back-end (which will be spray-can with spray-routing).

I will post an article on the actual reference implementation later next week (will provide link «here» when posted), but wanted to already share some experience with approaches in making the compiled javascript code available to the server process.

So far i have implemented three approaches which i thought might be useful to other developers as well.

The "standard" change output approach

The first approach i applied is the one also shown here: play-with-scalajs-example. It seems to be the accepted approach.

What this does is set a sbt key to a location in the server project's classes directory. This way the compiled javascript files are available on the classpath as resources at runtime.

val scalajsOutputDir = Def.settingKey[File]("directory for javascript files output by scalajs")
...
lazy val scalajvmSettings = Seq (
...
scalajsOutputDir := (classDirectory in Compile).value / "public" / "javascripts"
)  ++ (
// ask scalajs project to put its outputs in scalajsOutputDir
Seq(packageScalaJSLauncher, fastOptJS, fullOptJS) map { packageJSKey =>
crossTarget in (scalajs, Compile, packageJSKey) := scalajsOutputDir.value
}
)

The source above is available here

The problem i have (or rather "had") with this is that the compiled javascript sources are not part as resources during the development of the server project. Also i did not like that there was no dependency between the two modules. Pragmatic, but something smells about this approach.

As a jar dependency

On my road in exploring options i considered packing the javascript in a jar and use regular dependency management to make the javascript available to the server project. I made a third sbt module webjar which would just pick up the javascript files from the clientjs project:

lazy val webjar = project.aggregate(client).
settings(
unmanagedResourceDirectories in Compile += (crossTarget in (client)).value,
includeFilter in (Compile, unmanagedResources) := "*.js"
)

This allowed the server project to have a dependency:

lazy val scalajvm = project.dependsOn(webjar)

If i would have continued on this path, i would have to add some mapping of file names during the packaging so that the javascript files are not resources on the root of the classpath. This would not be too difficult using sbt's built-in support for that mapping files

It would even be possible to make it into a webjar.

That was actually the reason for me to cancel this option. I am not a big fan of webjars. And I might set a precedent without knowing. This might have led to all web resources having to go through webjars. The team working on the project has been using bower successfully for a while and i would not like to change that as well. As a matter of fact i personally am also a big fan of bower (especially using it together with browserify).

As generated resources

In an attempt to make things more semantically correct i explored considering the compiled javascript files as generated resources for the server project. Somehow that feels more natural. This also makes the compiled javascript available during development in a natural manner.

compile in Compile <<= (compile in Compile) dependsOn (fastOptJS in(client, Compile)),
resourceGenerators in Compile <+= Def.task {
val files = ((crossTarget in(client, Compile)).value ** ("*.js" || "*.map")).get
val mappings: Seq[(File,String)] = files pair rebase((crossTarget in(client, Compile)).value, ((resourceManaged in  Compile).value / "assets/").getAbsolutePath )
val map: Seq[(File, File)] = mappings.map { case (s, t) => (s, file(t))}
IO.copy(map).toSeq
}

Available as a gist

My IDE (intellij) was able to pick up the "assets" without problem:

And the development-cycle was very smooth using:

> ~ ;client/fastOptJS;service/re-start

Any change to the scalajs code was instantly available in the browser.

Conclusion

I somehow feel the third option is the cleanest, but will probably go for an alternative to the first approach . I believe it is considered the current "standard" in making the javascript available. However i do have to still make up my mind.

Any comments are welcomed

Note Also I am probably moving the public web resources to a top level directory and will not longer serve them from the resource path (using spray's 'getFromDirectory'). This means i no longer have a requirement to make the javascript files available as resources to the server project (i could keep doing it however). The reason for moving the site to its own top-level directory is to ease the migration and have the developer use their comfortable bower/gulp etc during the development of new features.

This article does not necessarily reflect the technical opinion of EDC4IT, but purely of the writer. If you want to discuss about this content, please use thecontact ussection of the site