Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get assets to compile when running via "activator run" in Play

I am using Sass as my CSS preprocesser, and I'm trying to have it run via the asset pipeline. I've tried implementing this sassTask as a source file task and as a web asset task, but I'm running into problems both ways.

If I run Sass as a source task (see below), it gets triggered during activator run when a page is requested and updated files are found upon page reloads. The problem I'm running into is that the resulting CSS files are all getting dumped directly into target/web/public/main/lib, instead of into the subdirectories reflecting the ones they are getting built into under the resources-managed directory. I can't figure out how to make this happen.

Instead, I tried implementing Sass compilation as a web asset task (see below). Working this way, as far as I can tell, resources-managed does not come into play, and so I compile my files directly into target/web/public/main/lib. I'm sure I'm not doing this dynamically enough, but I don't know how to do it any better. But the biggest problem here is that the pipeline apparently does not run when working through activator run. I can get it to run using activator stage, but I really need this to work in the regular development workflow so that I can change style files as the dev server is running, same as with Scala files.

I have tried combing through these forums, through the sbt-web docs, and through some of the existing plugins, but I am finding this process to be highly frustrating, due to the complexity of SBT and the opaqueness of what is actually happening in the build process.

Sass compilation as a source file task:

lazy val sassTask = TaskKey[Seq[java.io.File]]("sassTask", "Compiles Sass files")

sassTask := {
  import sys.process._
  val x = (WebKeys.nodeModules in Assets).value
  val sourceDir = (sourceDirectory in Assets).value
  val targetDir = (resourceManaged in Assets).value
  Seq("sass", "-I", "target/web/web-modules/main/webjars/lib/susy/sass", "--update", s"$sourceDir:$targetDir").!
  val sources = sourceDir ** "*.scss"
  val mappings = sources pair relativeTo(sourceDir)
  val renamed = mappings map { case (file, path) => file -> path.replaceAll("scss", "css") }
  val copies = renamed map { case (file, path) => file -> targetDir / path }
  copies map (_._2)
}

sourceGenerators in Assets <+= sassTask

Sass compilation as web asset task:

lazy val sassTask = taskKey[Pipeline.Stage]("Compiles Sass files")

sassTask := {
  (mappings: Seq[PathMapping]) =>
    import sys.process._
    val sourceDir = (sourceDirectory in Assets).value
    val targetDir = target.value / "web" / "public" / "main"
    val libDir = (target.value / "web" / "web-modules" / "main" / "webjars" / "lib" / "susy" / "sass").toString
    Seq("sass", "-I", libDir, "--update", s"$sourceDir:$targetDir").!
    val sources = sourceDir ** "*.scss"
    val mappings = sources pair relativeTo(sourceDir)
    val renamed = mappings map { case (file, path) => file -> path.replaceAll("scss", "css") }
    renamed
}

pipelineStages := Seq(sassTask)
like image 979
acjay Avatar asked Jun 18 '14 13:06

acjay


1 Answers

I think that according to the documentation related to the Asset Pipeline, a Source File task is a way to go:

Examples of source file tasks as plugins are CoffeeScript, LESS and JSHint. Some of these take a source file and produce a target web asset e.g. CoffeeScript produces JS files. Plugins in this category are mutually exclusive to each other in terms of their function i.e. only one CoffeeScript plugin will take CoffeeScript sources and produce target JS files. In summary, source file plugins produce web assets.

I think what you try to achieve falls into this category.

TL;DR; - build.sbt

val sassTask = taskKey[Seq[File]]("Compiles Sass files")

val sassOutputDir = settingKey[File]("Output directory for Sass generated files")

sassOutputDir := target.value / "web" / "sass" / "main"

resourceDirectories in Assets += sassOutputDir.value

sassTask := {
  val sourceDir = (sourceDirectory in Assets).value
  val outputDir = sassOutputDir.value
  val sourceFiles = (sourceDir ** "*.scss").get
  Seq("sass", "--update", s"$sourceDir:$outputDir").!
  (outputDir ** "*.css").get
}

sourceGenerators in Assets += sassTask.taskValue

Explanation

Assuming you have sass file in a app/assets/<whatever> directory, and that you want to create css files in web/public/main/<whatever> directory, this is what you could do.

Create a task, which will read in files in the app/assets/<whatever> directory and subdirectories, and output them to our defined sassOutputDir.

val sassTask = taskKey[Seq[File]]("Compiles Sass files")

val sassOutputDir = settingKey[File]("Output directory for Sass generated files")

sassOutputDir := target.value / "web" / "sass" / "main"

resourceDirectories in Assets += sassOutputDir.value

sassTask := {
  val sourceDir = (sourceDirectory in Assets).value
  val outputDir = sassOutputDir.value
  val sourceFiles = (sourceDir ** "*.scss").get
  Seq("sass", "--update", s"$sourceDir:$outputDir").!
  (outputDir ** "*.css").get
}

This is not enough though. If you want to keep the directory structure you have to add your sassOutputDir to the resourceDirectories in Assets. This is because mappings in sbt-web are declared like this:

mappings := {
  val files = (sources.value ++ resources.value ++ webModules.value) ---
    (sourceDirectories.value ++ resourceDirectories.value ++ webModuleDirectories.value)
  files pair relativeTo(sourceDirectories.value ++ resourceDirectories.value ++ webModuleDirectories.value) | flat
}

which means that all unmapped files are mapped using an alternative flat strategy. However the fix for it is simple, just add this to your build.sbt

resourceDirectories in Assets += sassOutputDir.value

This will make sure the directory structure is preserved.

like image 81
lpiepiora Avatar answered Nov 03 '22 05:11

lpiepiora