Play Framework 2.2.x: Static assets location not working in production
-
21-12-2019 - |
Question
Having trouble accessing the compiled assets location in production.
My strategy has been to serve my assets in "app/assets/ui" when in development and "public" when in production this is done as shown below in my conf/routes file
#{if(play.Play.mode.isDev())}
GET /assets/*file controllers.common.Assets.at(path="/app/assets/ui", file)
#{/}
#{else}
GET /assets/*file controllers.common.Assets.at(path="/public", file)
#{/}
Since i have defined asset mappings outside “public,”I have added the following line in my Build.scala
playAssetsDirectories <+= baseDirectory / "app/assets/ui"
As an example my scripts are loaded conditionaly depending on the environment as shown below
@if(play.Play.isDev()) {<script src="@routes.Assets.at("/app/assets/ui", "javascripts/application.js")"type="text/javascript"></script>} else {<script src="@.routes.Assets.at("/public", "javascripts/application.min.js")" type="text/javascript"></script>}
I'm using Grunt for my frontend workflow and when the application builds it copies the distribution files to the application's public folder.
I start the app in production using "sbt clean compile stage"
and then run the packaged app.
My problem appears that the routes are still referring to the "app/assets/ui" folder instead of the distribution "public" folder.
Any tips on how i can debug this? My working background is as a front end developer so i'm very new to Play! and scala.
Solution
As mentioned by @estmatic, your conditional in routes
won't be evaluated.
As it's generally extremely useful to consolidate the differences between application Mode
s into files, I'd suggest you extend GlobalSettings
(if you aren't already) and override the onLoadConfig
method:
class Settings extends GlobalSettings {
override def onLoadConfig(config: Configuration, path: File, classloader: ClassLoader, mode: Mode.Mode): Configuration = {
val specificConfig:Config = // ... Use the mode param to load appropriate file
super.onLoadConfig(specificConfig, path, classloader, mode)
}
...
}
You could then have appropriately-named files (dev.conf
and production.conf
spring to mind) that contain suitable values, one of them being the base path
for the Assets controller to use.
EDIT turns out doing it this way makes usage in routes
awkward, here's another approach:
This approach does not use a configuration file per-environment, which means that if something changes in the frontend configuration (e.g. it's no longer served up from /public
) you'll have to change this code and re-deploy it. However, it fits into Play 2.x pretty nicely:
package controllers
object EnvironmentSpecificAssets extends AssetsBuilder {
val modeToAssetsPathMap = Map(
Mode.Dev -> "/app/assets/ui",
Mode.Prod -> "/public")
lazy val modePath = modeToAssetsPathMap(Play.current.mode)
/** New single-argument `at`, determines its path from the current app mode */
def at(file:String): Action[AnyContent] = at(modePath, file)
}
The code is pretty self-explanatory, the only "trick" is probably the lazy val
which means we only have to evaluate the current operating mode and do the map lookup once.
Now your routes
file just looks like this:
GET /assets/*file controllers.EnvironmentSpecificAssets.at(file)
OTHER TIPS
Playframework 2.x doesn't support conditional statements in the routes file. The 1.x versions had this but it was removed.
What you have in your routes file is simply two routes with the same URI pattern, /assets/file*
. The other lines are just being ignored as comments since they begin with the pound character, #
. I think since the pattern is the same for both the first route is catching everything and the second isn't doing anything.
It's not exactly what you're trying to do but I think you can just make the route patterns a bit different and it should work.
GET /assets/dev/*file controllers.common.Assets.at(path="/app/assets/ui", file)
GET /assets/*file controllers.common.Assets.at(path="/public", file)