Tuesday, February 4, 2014

Play 2.2 subprojects and their assets

Yes, I still like Play! very much. But sometimes it is frustrating in its lackluster documentation. I spent literally hours trying to understand how to work with sub-projects. The fact that I could not find a step-by-step tutorial did not help (note to self - create one!)
I followed the documentation and created the top project, then subprojects. Then created dependencies. Btw, if you want to generate the eclipse projects from console make sure you do not aggregate the projects. For some reason, whenever I add aggregate(subproj1, subproj2) to the top level poject in build.sbt it will omit some projects.
All good, projects compile, and run. But now it is important to take a look at the packages - make sure you refactor your code and move all controllers in subproj1 into their own package (ie. controllers.subproj1). Then, do the same for the scala.html files in views. You should end up with something like this:
multiproj
 build.sbt
 app
   controllers
     Application.java
   views
     index.scala.html
     main.scala.html
   conf
     application.conf
     routes
   public
     images
        ...
     stylesheets
        ...
     javascripts
        ...
   modules
     core
       build.sbt
       app
         controllers.core
           Application.java
         views.core
           index.scala.html
           main.scala.html
       conf
         application.conf
         core.routes
       public
          ...
     test
       build.sbt
       app
         controllers.test
           Application.java
         views.test
           index.scala.html
           main.scala.html
       conf
         application.conf
         core.routes
       public
          ...

and the top build.sbt looks like this:
name := "multiproj"

version := "1.0-SNAPSHOT"

libraryDependencies ++= Seq(
 javaCore, cache
)     

play.Project.playJavaSettings

lazy val root = project.in(file(".")).dependsOn(core, test)

lazy val core = project.in(file("modules/core"))

lazy val test = project.in(file("modules/test")).dependsOn(core)

and the top routes file:
GET     /                           controllers.Application.index()
-> /core core.Routes
-> /test test.Routes
# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)

Obviously, the core and test routes files must use proper packages when referring to the controller classes. So far, it is not that bad. But what about the resources? Notice how the same top level public folder is invoked above. Well, there is an extra step - create an Assets class in the controllers.core and controllers.test that will help scope the lookup to the project directory. Play documentation nicely provides the contents of this class:
package controllers.test;

import play.api.mvc.Action;
import play.api.mvc.AnyContent;

public class Assets {
    public static Action at(String path, String file) {
        return controllers.Assets.at(path, file);
    }
}

and now update the route in the test.routes accordingly:
GET     /assets/*file               controllers.test.Assets.at(path="/public", file)

Make change in the stylesheet from top level project and you will see it applied. Now change the color in the stylesheet from test project and nothing happens. WTF?!?!

Well, as it turns out, Play uses the same classloader to load the files, so if the file name from top level and subproject is the same, it is not going to work. Thus, it is important that the assets from subprojects (public folders from subprojects) have unique names. You can follow the same scheme as for routes file and have public/main.css in top level project and public/test.main.css in the test subprojects. Then you will need to make sure you use the proper reverse routing in the template. Example: @controllers.test.routes.Assets.at("stylesheets/test.main.css")

Bottom line, yes you can split your application in sub-projects and be organized, but I am a bit disappointed in the number of hoops I have to jump to make it go. It should be simpler, Play!. Please make it more straight-forward! I would suggest that the console should know about sub-projects and pre-create the right structure and naming... Can't be that hard to make the console do all the renaming steps above.

Links:
  • http://www.playframework.com/documentation/2.2.x/SBTSubProjects
  • https://github.com/playframework/playframework/issues/1181