How to use the PlayFramework asset pipeline

PlayFramework 2.3 comes with a nifty asset pipeline. In this article, I would like to give you a quick introduction.

The asset pipeline processes all files which you reference with @routes.Assets.versioned(...). The pipeline uses plugins which are build on sbt-web. There are already many plugins available. A full list can be found here: sbt-web/README.md.

As an example, we would like to set up an asset pipeline which minifies CSS and JS files. Fingerprints them for better caching and finally creates a gzipped version of those files.

Activate pipeline plugins

The first step is to activate the necessary sbt-web plugins. Add those lines to your project's plugins.sbt file.

addSbtPlugin("net.ground5hark.sbt" % "sbt-css-compress" % "0.1.3")

addSbtPlugin("net.ground5hark.sbt" % "sbt-closure" % "0.1.3")

addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0")

addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0")

Configure the pipeline

We now need to configure the asset pipeline and the plugins. This needs to be done in the file build.sbt.

First we add all the plugins to the asset pipeline. The order is important as it specifies how those plugins are executed. In our case, we first start to minify our JS files with the closure plugin and gzip all assets in the last step.

pipelineStages := Seq(closure, cssCompress, digest, gzip)

Unfortunately, the sbt-gzip plugin has a problem when a project contains already a file with the gzipped version. This is a common issue when using webjars. Because of this issue we add an exclude filter to exclude those files from being processed by the gzip plugin to our build.sbt file.

excludeFilter in gzip := (excludeFilter in gzip).value || new SimpleFileFilter(file => new File(file.getAbsolutePath + ".gz").exists)

It probably makes sense to add more filters so that only your own project files are being processed by the CSS and JS minify plugins. This can be done by adding a filter. There is no need to filter for .js or .css, as this is already done by the plugins. In our case, we would like to  minify only files from our own app.

includeFilter in closure := (includeFilter in closure).value && new SimpleFileFilter(f => f.getName.contains("myapp"))

includeFilter in cssCompress := (includeFilter in cssCompress).value && new SimpleFileFilter(f => f.getName.contains("myapp"))

There is also a sbt-filter plugin in case you would like to include or exclude files on a more global pipeline level.

In the end, your build.sbt should contain:

pipelineStages := Seq(closure, cssCompress, digest, gzip)

excludeFilter in gzip := (excludeFilter in gzip).value || new SimpleFileFilter(file => new File(file.getAbsolutePath + ".gz").exists)

includeFilter in closure := (includeFilter in closure).value && new SimpleFileFilter(f => f.getName.contains("myapp"))

includeFilter in cssCompress := (includeFilter in cssCompress).value && new SimpleFileFilter(f => f.getName.contains("myapp"))

Use the correct router

We are not done yet. In order for Play to use the asset pipeline, you need to change all calls of @routes.Assets.at("...") in your HTML to @routes.Assets.versioned("...").

Additionally you need to change the current asset router configuration in your routes file. Remove:

GET    /assets/*file                                controllers.Assets.at(path="/public", file)

and add the following replacement:

GET    /assets/*file                                controllers.Assets.versioned(path="/public", file: Asset)

Hint: Make sure you didn't forget file: Asset otherwise it won't work.

How to test in development mode?

The asset pipeline is used only in production mode. If you would like to use the asset pipeline in development mode then you can add the following line to your build.sbt file.

pipelineStages in Assets := Seq(closure, cssCompress, digest, gzip)

I recommend to enable the pipeline in development mode for debugging purposes only because it makes project compilation painfully slow.

Resources