Joerg Viola

Lean. Web. Development.

Using Subprojects

Decomposing your application into subprojects can reduce build times a lot. Also, it adds structure to your source tree. The example app is a very simple two-pager, adding twitter bootstrap via WebJars as the only goodie. We want to hide the (very small) complexity of bootstrap/webjars in a sub-project called “pages”:

The sbt build

Lets start as Build.scala, where you define the sub-project. In general, you can have all types of sbt-projects here, but we are interested in Play-like sub-projects.

The only external dependency is twitter bootstrap, which is very conveniently added via the WebJars-repository:

Build.scala Github
1
2
3
4
  val appDependencies = Seq(
    "org.webjars" % "webjars-play" % "2.1-RC1",
    "org.webjars" % "bootstrap" % "2.2.1"
  )

The more interesting part is of course the actual definition of the sub-project, pages in this case. It looks like the usual Play Project definition in build files, despite the path argument, that specifies its location in the source tree.

Afterwards, the sub-project is used in the dependsOn-clause of the actual main project:

Build.scala Github
1
2
3
4
5
  val pages = play.Project(appName+"-pages", appVersion,
      appDependencies, path = file("modules/pages")).settings()

  val main = play.Project(appName, appVersion,
      appDependencies).settings().dependsOn(pages)

Note how the dependencies, namely the webjars, are passed to both projects here. This is a little awkward and I’ll explain it later.

Routes

Now that we have declared the sub-project, we want to define the routes:

routes Github
1
2
3
4
5
6
7
8
9
10
11
12
13
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /                           controllers.Application.index
GET     /one                        controllers.Application.one

->     /pages                      pages.Routes

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)
GET     /webjars/*file              controllers.WebJarAssets.at(file)

Note how requests to URIs staring with /pages are delegates to the router of the sub-project. (Note also that we are declaring a webjars asset helper, which is why we need the dependency to webjars in the main project.)

The routes file of the sub-project, modules/pages/conf/pages.routes, looks more or less boring:

pages.routes Github
1
2
3
4
5
6
7
8
9
10
11
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /terms                      controllers.pages.Pages.terms
GET     /terms2                     controllers.pages.Pages.terms2

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.at(path="/public", file)
GET     /webjars/*file              controllers.WebJarAssets.at(file)

(Note that again, the webjars asset helper is included, which is one reason for requiring the dependency for the sub-project. We need this because we want to add bootstrap to the main template, which is defined in the sub-project.)

Controller

The controllers of sub-projects are the same as for main projects, only that they should be put in the correct namespace, e.g. controllers.pages:

Pages.scala Github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package controllers.pages

import play.api._
import play.api.mvc._

object Pages extends Controller {

  def terms = Action {
    Ok(views.html.pages.terms("hi there"))
  }

  def terms2 = Action {
    Ok(views.html.pages.terms("hi there 2"))
  }

}

View

The only interesting view is the main template in the sub project:

First, the bootstrap ressources are imported via webjar:

main.scala.html Github
1
2
     <link rel='stylesheet' href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))'>
      <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("jquery.min.js"))'></script>

Afterwards, the usual bootstrap code can be used. Note how a reverse route into the sub project is formulated:

main.scala.html Github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="navbar-inner">
          <div class="container">
            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </a>
            <a class="brand" href="/">Modules Test</a>
            <div class="nav-collapse collapse">
              <ul class="nav">
                <li class="active"><a href="/">Home</a></li>
                <li><a href="@controllers.pages.routes.Pages.terms">Terms</a></li>
              </ul>
            </div><!--/.nav-collapse -->
          </div>
        </div>
      </div>
      <div class="container">
              <div class="hero-unit">
              @content
          </div>

          <hr>
  
          <footer>
              <p>&copy; jv 2012</p>
          </footer>
  
      </div>

This template can now very easily be used from the main project:

index.scala.html Github
1
2
3
4
5
@(message: String)

@pages.main("Index") {
    Hello, world
}

References:

Comments