Skip to content

Instantly share code, notes, and snippets.

@divarvel
Last active August 29, 2015 14:02
Show Gist options
  • Save divarvel/b2cf02d6e155c484fe91 to your computer and use it in GitHub Desktop.
Save divarvel/b2cf02d6e155c484fe91 to your computer and use it in GitHub Desktop.

Play - Grunt integration

Gist does not let me put directories names in file names, so make sure to put:

  • assets-Gruntfile.js in assets/Gruntfile.js
  • assets-package.json in assets/package.json
  • project-Grunt.scala in project/Grunt.scala
  • project-JavaScriptBuild.scala in project/JavaSCriptBuild.scala

The goal is to let grunt handle everything in the assets directory.

In the build.sbt, you add JavaScriptBuild.javaScriptUiSettings to include the JS build config to the sbt config.

Effectively, a grunt build generates compiled assets from the /assets directories and copies it into /public, where static assets are accessible.

Additionally, grunt watch is added as a hook when play run is launched (not as a dependency, since it's a long-running process).

So when you do play stage, npm install && grunt build are executed, the assets are compiled and copied to /public.

make sure to git-ignore compiled assets in /public

For grunt-watch to work locally, you may have to manually do npm install in /assets. Don't worry, it's only needed locally, as the stage command triggers it automatically

module.exports = function(grunt) {
grunt.initConfig({
less: {
styles: {
files: {
'../public/stylesheets/frontend.css': 'less/all.less',
'../public/stylesheets/backend.css': 'less/all-backend.less',
}
}
},
watch: {
styles: {
files: 'less/**.less',
tasks: [ 'less:styles', 'cssmin' ]
},
js: {
files: 'js/**.js',
tasks: [ 'concat:jsscripts' ]
}
},
concat: {
jsscripts: {
src: [ 'js/*.js' ],
dest: '../public/javascripts/scripts.js'
},
jslibs: {
src: [ 'js/libs/*.js' ],
dest: '../public/javascripts/libs.js'
},
jslibsie: {
src: [ 'js/libs-ie/*.js' ],
dest: '../public/javascripts/libs-ie.js'
}
},
cssmin: {
frontend: {
src: '../public/stylesheets/frontend.css',
dest: '../public/stylesheets/frontend.min.css',
},
backend: {
src: '../public/stylesheets/backend.css',
dest: '../public/stylesheets/backend.min.css',
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['less', 'concat', 'cssmin']);
};
{
"dependencies": {
"grunt": "*",
"grunt-cli": "*",
"grunt-contrib-less": "*",
"grunt-contrib-watch": "*",
"grunt-contrib-concat": "*",
"grunt-contrib-cssmin": "*"
}
}
name := "project-name"
version := "1.0-SNAPSHOT"
libraryDependencies ++= Seq(
jdbc,
anorm,
cache
)
playScalaSettings
JavaScriptBuild.javaScriptUiSettings
import sbt._
import java.net._
import java.io.File
import play.PlayRunHook
object Grunt {
def apply(base: File): PlayRunHook = {
object GruntProcess extends PlayRunHook {
var process: Option[Process] = None
override def afterStarted(addr: InetSocketAddress): Unit = {
// call grunt to generate public assets
gruntProcess(base).run
// watch for modifications
process = Some(gruntProcess(base, "watch").run)
}
override def afterStopped(): Unit = {
// Stop grunt when play run stops
process.map(p => p.destroy())
process = None
}
}
GruntProcess
}
def gruntCommand(base: File) = Command.args("grunt", "<grunt-command>") { (state, args) =>
gruntProcess(base, args:_*) !;
state
}
def gruntProcess(base: File, args: String*) = Process("node" :: "node_modules/grunt-cli/bin/grunt" :: args.toList, base)
}
import play.Project._
import sbt._
import sbt.Keys._
import com.typesafe.sbt.packager.Keys._
object JavaScriptBuild {
val assetsDirectory = SettingKey[File]("assets-directory")
val gruntBuild = TaskKey[Int]("grunt-build")
val npmInstall = TaskKey[Int]("npm-install")
val javaScriptUiSettings = Seq(
// the JavaScript application resides in "ui"
assetsDirectory <<= (baseDirectory in Compile) { _ / "assets" },
// add "npm" and "grunt" commands in sbt
commands <++= assetsDirectory { base => Seq(Grunt.gruntCommand(base), npmCommand(base))},
gruntBuild := Grunt.gruntProcess(assetsDirectory.value).run().exitValue(),
npmInstall := Process(List("npm", "install"), assetsDirectory.value).run().exitValue(),
gruntBuild <<= gruntBuild dependsOn (npmInstall),
// runs grunt before staging the application
stage <<= stage dependsOn (gruntBuild),
// Turn off play's internal less compiler
lessEntryPoints := Nil,
// Turn off play's internal JavaScript and CoffeeScript compiler
javascriptEntryPoints := Nil,
coffeescriptEntryPoints := Nil,
// integrate JavaScript build into play build
playRunHooks <+= assetsDirectory.map(a => Grunt(a))
)
def npmCommand(base: File) = Command.args("npm", "<npm-command>") { (state, args) =>
Process("npm" :: args.toList, base) !;
state
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment