= Play Framework
:page-aliases: Plugin_Play.adoc

This module adds basic Play Framework support to mill:

* configures mill for Play default directory layout,
* integrates the Play routes compiler,
* provides helpers for commonly used framework libraries,
* optionally: integrates the Twirl template engine,
* optionally: configures mill for single module play applications.

There is no specific Play Java support, building a Play Java application will require a bit
of customization (mostly adding the proper dependencies).

== Using the plugin

There are 2 base modules and 2 helper traits in this plugin, all of which can be found
in `mill.playlib`.

The base modules:

* `PlayModule` applies the default Play configuration (layout, dependencies, routes compilation,
Twirl compilation and Akka HTTP server)
* `PlayApiModule` applies the default Play configuration without `Twirl` templating. This is useful
if your Play app is a pure API server or if you want to use a different templating engine.

The helper trait:

* `RouterModule` allows you to use the Play router without the rest of the configuration (see
<<_using_the_router_module_directly>>.)

== Using `PlayModule`

In order to use the `PlayModule` for your application, you need to provide the scala, Play and
Twirl versions. You also need to define your own test object which extends the provided
`PlayTests` trait.

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.*
import mill.playlib.*

object core extends PlayModule {
    // config
    override def scalaVersion = Task { "2.13.12" }
    override def playVersion = Task { "2.8.20" }

    object test extends PlayTests
}
----

Using the above definition, your build will be configured to use the default Play layout:

[source,text]
----
.
├── build.mill
└── core
    ├── app
    │   ├── controllers
    │   └── views
    ├── conf
    │   └── application.conf
    │   └── routes
    │   └── ...
    ├── logs
    ├── public
    │   ├── images
    │   ├── javascripts
    │   └── stylesheets
    └── test
        └── controllers
----

The following compile dependencies will automatically be added to your build:

[source,scala]
----
Seq(
  mvn"com.typesafe.play::play:${playVersion()}",
  mvn"com.typesafe.play::play-guice:${playVersion()}",
  mvn"com.typesafe.play::play-server:${playVersion()}",
  mvn"com.typesafe.play::play-logback:${playVersion()}"
)
----

Scala test will be setup as the default test framework and the following test dependencies will be
added (the actual version depends on the version of Play you are pulling `2.6.x` or `2.7.x`):

[source,scala]
----
mvn"org.scalatestplus.play::scalatestplus-play::5.1.0"
----

In order to have a working `start` command the following runtime dependency is also added:

----
mvn"com.typesafe.play::play-akka-http-server:${playVersion()}"
----
or
----
mvn"com.typesafe.play::play-pekko-http-server:${playVersion()}"
----
depending on the play version

=== Using `PlayApiModule`

The `PlayApiModule` trait behaves the same as the `PlayModule` trait but it won't process .scala
.html files:

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.*
import mill.playlib.*

object core extends PlayApiModule {
    // config
    override def scalaVersion = Task { "2.13.12" }
    override def playVersion = Task { "2.8.20" }

    object test extends PlayTests
}
----

== Play configuration options

The Play modules themselves don't have specific configuration options at this point but the <<_router_configuration_options,router
module configuration options>> and the <<contrib/twirllib.adoc#_twirl_configuration_options>> are applicable.

== Additional play libraries

The following helpers are available to provide additional Play Framework dependencies:

* `core()` - added by default ,
* `guice()` - added by default,
* `server()` - added by default,
* `logback()` - added by default,
* `evolutions()` - optional,
* `jdbc()` - optional,
* `filters()` - optional,
* `ws()` - optional,
* `caffeine()` - optional.

If you want to add an optional library using the helper you can do so by overriding `mvnDeps`
like in the following example build:

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.*
import mill.playlib.*

object core extends PlayApiModule {
    // config
    override def scalaVersion = Task { "2.13.12" }
    override def playVersion = Task { "2.8.20" }

    object test extends PlayTests

    override def mvnDeps = Task { super.mvnDeps() ++ Seq(ws(), filters()) }
}
----

== Commands equivalence

Mill commands are tasks on a named build. For example if your build is called `core`:

* compile: `core.compile`
* run: _NOT Implemented yet_. It can be approximated with `mill -w core.runBackground` but this
starts a server in _PROD_ mode which:
* doesn't do any kind of classloading magic (meaning potentially slower restarts)
* returns less detailed error messages (no source code extract and line numbers)
* can sometimes fail because of a leftover RUNNING_PID file
* start: `core.start` or `core.run` both start the server in _PROD_ mode.
* test: `core.test`
* dist: _NOT Implemented yet_. However you can use the equivalent `core.assembly`
command to get a runnable fat jar of the project. The packaging is slightly different but should
be find for a production deployment.

== Using `RootModule`

The `RootModule` abstract class allows you to have the build descriptor at the same level as the
source code on the filesystem. You can move from there to a multi-module build either by refactoring
your directory layout into multiple subdirectories or by using mill's nested modules feature.

Looking back at the sample build definition in <<_using_playmodule>>:

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.*
import mill.playlib.*

object core extends PlayModule {
    // config
    override def scalaVersion = Task { "2.13.12" }
    override def playVersion = Task { "2.8.20" }

    object test extends PlayTests
}
----

The directory layout was:

[source,text]
----
.
├── build.mill
└── core
    ├── app
    │   ├── controllers
    │   └── views
    ├── conf
    │   └── application.conf
    │   └── routes
    │   └── ...
    ├── logs
    ├── public
    │   ├── images
    │   ├── javascripts
    │   └── stylesheets
    └── test
        └── controllers
----

by extending `RootModule` in your build:

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.*
import mill.playlib.*

object build extends RootModule with PlayModule {
	// config
	override def scalaVersion = Task { "2.13.12" }
	override def playVersion = Task { "2.8.20" }

	object test extends PlayTests
}
----

the layout becomes:

[source,text]
----
.
├── build.mill
├── app
│   ├── controllers
│   └── views
├── conf
│   └── application.conf
│   └── routes
│   └── ...
├── logs
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
└── test
    └── controllers
----

=== Using the router module directly

If you want to use the router module in a project which doesn't use the default Play layout, you
can mix-in the `mill.playlib.routesModule` trait directly when defining your module. Your app must
define `playVersion` and `scalaVersion`.

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.*
import mill.playlib.*

object app extends ScalaModule with RouterModule {
  def playVersion = Task { "2.8.20" }
  def scalaVersion = Task { "2.13.12" }
}
----

==== Router Configuration options

* `def playVersion: T[String]` (mandatory) - The version of Play to use to compile the routes file.
* `def scalaVersion: T[String]` - The scalaVersion in use in your project.
* `def routes: T[Seq[Sources]]` - The directory which contains your route files. (Defaults to : `routes/`)
* `def routesAdditionalImport: Seq[String]` - Additional imports to use in the generated routers.
(Defaults to `Seq("controllers.Assets.Asset", "play.libs.F")`
* `def generateForwardsRouter: Boolean = true` - Enables the forward router generation.
* `def generateReverseRouter: Boolean = true` - Enables the reverse router generation.
* `def namespaceReverseRouter: Boolean = false` - Enables the namespacing of reverse routers.
* `def generatorType: RouteCompilerType = RouteCompilerType.InjectedGenerator` - The routes
compiler type, one of RouteCompilerType.InjectedGenerator or RouteCompilerType.StaticGenerator

==== Details

The following filesystem layout is expected by default:

[source,text]
----
.
├── app
│   └── routes
│       └── routes
└── build.mill
----

`RouterModule` adds the `compileRouter` task to the module:

[source,console]
----
$ mill app.compileRouter
----

(it will be automatically run whenever you compile your module)

This task will compile `routes` templates into the `out/app/compileRouter/dest`
directory. This directory must be added to the generated sources of the module to be compiled and
made accessible from the rest of the code. This is done by default in the trait, but if you need
to have a custom override for `generatedSources` you can get the list of files from `routerClasses`

To add additional imports to all of the routes:

.`build.mill`
[source,scala]
----
//| mvnDeps: ["com.lihaoyi::mill-contrib-playlib:$MILL_VERSION"]
package build

import mill.scalalib.*
import mill.playlib.*

object app extends ScalaModule with RouterModule {
  def playVersion = "2.8.20"
  override def routesAdditionalImport = Seq("my.additional.stuff._", "my.other.stuff._")
}
----
